Merge "Properly setting up haptics on IconView when MSDL is enabled." into main
diff --git a/Android.bp b/Android.bp
index 6bd8602..5b986ab 100644
--- a/Android.bp
+++ b/Android.bp
@@ -389,6 +389,7 @@
         "//frameworks/libs/systemui:contextualeducationlib",
         "//frameworks/libs/systemui:msdl",
         "SystemUI-statsd",
+        "WindowManager-Shell-shared-AOSP",
         "launcher-testing-shared",
         "androidx.lifecycle_lifecycle-common-java8",
         "androidx.lifecycle_lifecycle-extensions",
diff --git a/aconfig/Android.bp b/aconfig/Android.bp
index 5413601..bca7494 100644
--- a/aconfig/Android.bp
+++ b/aconfig/Android.bp
@@ -20,7 +20,7 @@
 aconfig_declarations {
     name: "com_android_launcher3_flags",
     package: "com.android.launcher3",
-    container: "system",
+    container: "system_ext",
     srcs: ["**/*.aconfig"],
 }
 
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index c3fb150..1726eca 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -1,5 +1,5 @@
 package: "com.android.launcher3"
-container: "system"
+container: "system_ext"
 
 flag {
     name: "enable_expanding_pause_work_button"
@@ -564,3 +564,23 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "restore_archived_shortcuts"
+  namespace: "launcher"
+  description: "Makes sure pre-archived pinned shortcuts also get restored"
+  bug: "375414891"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "restore_archived_app_icons_from_db"
+  namespace: "launcher"
+  description: "Restores pre-archived icons from db when available, mimicing promise icons"
+  bug: "391913214"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/aconfig/launcher_overview.aconfig b/aconfig/launcher_overview.aconfig
index e0a597c..f379e22 100644
--- a/aconfig/launcher_overview.aconfig
+++ b/aconfig/launcher_overview.aconfig
@@ -1,5 +1,5 @@
 package: "com.android.launcher3"
-container: "system"
+container: "system_ext"
 
 flag {
     name: "enable_grid_only_overview"
@@ -79,3 +79,20 @@
     description: "Enables expressive motion and animations for dismissing a task in Overview."
     bug: "381239462"
 }
+
+flag {
+    name: "enable_separate_external_display_tasks"
+    namespace: "launcher_overview"
+    description: "Enables separating external display tasks in Overview."
+    bug: "391311008"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
+    name: "enable_overview_on_connected_displays"
+    namespace: "launcher_overview"
+    description: "Enable overview on connected displays."
+    bug: "363251602"
+}
diff --git a/aconfig/launcher_search.aconfig b/aconfig/launcher_search.aconfig
index 72f654e..b98eee6 100644
--- a/aconfig/launcher_search.aconfig
+++ b/aconfig/launcher_search.aconfig
@@ -1,5 +1,5 @@
 package: "com.android.launcher3"
-container: "system"
+container: "system_ext"
 
 flag {
     name: "enable_private_space"
diff --git a/go/quickstep/res/values-fa/strings.xml b/go/quickstep/res/values-fa/strings.xml
index 8453d4e..f0e4a57 100644
--- a/go/quickstep/res/values-fa/strings.xml
+++ b/go/quickstep/res/values-fa/strings.xml
@@ -5,7 +5,7 @@
     <string name="action_listen" msgid="2370304050784689486">"گوش دادن"</string>
     <string name="action_translate" msgid="8028378961867277746">"ترجمه"</string>
     <string name="action_search" msgid="6269564710943755464">"لنز"</string>
-    <string name="dialog_acknowledge" msgid="2804025517675853172">"متوجه‌ام"</string>
+    <string name="dialog_acknowledge" msgid="2804025517675853172">"متوجهم"</string>
     <string name="dialog_cancel" msgid="6464336969134856366">"لغو"</string>
     <string name="dialog_settings" msgid="6564397136021186148">"تنظیمات"</string>
     <string name="niu_actions_confirmation_title" msgid="3863451714863526143">"ترجمه نوشتار روی صفحه‌نمایش یا گوش دادن به آن"</string>
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 201c5f6..5ca7143 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -119,6 +119,7 @@
             android:autoRemoveFromRecents="true"
             android:excludeFromRecents="true"
             android:theme="@style/GestureTutorialActivity"
+            android:label="@string/gesture_tutorial_title"
             android:exported="true"
             android:configChanges="orientation">
             <intent-filter>
diff --git a/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt b/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt
new file mode 100644
index 0000000..29586c4
--- /dev/null
+++ b/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2025 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.dagger
+
+import dagger.Module
+
+/**
+ * Module containing bindings for the final derivative app, an implementation of this module should
+ * be included in the final app code.
+ */
+@Module abstract class AppModule {}
diff --git a/quickstep/res/layout/overview_add_desktop_button.xml b/quickstep/res/layout/overview_add_desktop_button.xml
index 2333dd1..e36cf72 100644
--- a/quickstep/res/layout/overview_add_desktop_button.xml
+++ b/quickstep/res/layout/overview_add_desktop_button.xml
@@ -18,7 +18,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:launcher="http://schemas.android.com/apgk/res-auto"
     android:id="@+id/add_desktop_button"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
+    android:layout_width="@dimen/add_desktop_button_size"
+    android:layout_height="@dimen/add_desktop_button_size"
     android:src="@drawable/ic_desktop_add"
     android:padding="10dp" />
\ No newline at end of file
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index cf7b938..eba4ae6 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Programvoorstelle is geaktiveer"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Programvoorstelle is gedeaktiveer"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Voorspelde app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Draai jou toestel"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Draai asseblief jou toestel om die tutoriaal oor gebaarnavigasie te voltooi"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Maak seker dat jy van die rand heel regs of heel links af swiep"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"vou <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> uit"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"vou <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> in"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Omkring en Soek"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Appikoon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Apptitel"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Maak Toe-knoppie"</string>
 </resources>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index 60c04dc..019850d 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"የመተግበሪያ አስተያየት ጥቆማዎች ነቅቷል"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"የመተግበሪያ አስተያየቶች ቦዝነዋል"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"የተገመተው መተግበሪያ፦ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"መሣሪያዎን ያሽከርክሩ"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"የእጅ ምልክት ዳሰሳ አጋዥ ሥልጠናን ለማጠናቀቅ እባክዎ መሣሪያዎን ያሽከርክሩ"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ከቀኝ ጥግ ወይም ከግራ ጥግ ጠርዝ ጀምሮ ማንሸራተትዎን ያረጋግጡ"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ን ዘርጋ"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ን ሰብስብ"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"ለመፈለግ ክበብ"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"የመተግበሪያ አዶ"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"የመተግበሪያ ርዕስ"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"የዝጋ አዝራር"</string>
 </resources>
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index dc756d4..a77335f 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"تم تفعيل ميزة \"التطبيقات المقترحة\"."</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ميزة \"التطبيقات المقترحة\" غير مفعّلة."</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"التطبيق المتوقع: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"يُرجى تدوير الجهاز"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"يُرجى تدوير جهازك لإكمال الدليل التوجيهي للتنقُّل بالإيماءات."</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"تأكَّد من التمرير سريعًا من أقصى الحافة اليسرى أو اليمنى."</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"توسيع <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"تصغير <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"دائرة البحث"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"رمز التطبيق"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"عنوان التطبيق"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"زر الإغلاق"</string>
 </resources>
diff --git a/quickstep/res/values-as/strings.xml b/quickstep/res/values-as/strings.xml
index ae16f96..a7a66e9 100644
--- a/quickstep/res/values-as/strings.xml
+++ b/quickstep/res/values-as/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"এপৰ পৰামৰ্শসমূহ সক্ষম কৰা আছে"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"এপৰ পৰামৰ্শসমূহ অক্ষম কৰা আছে"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"পূৰ্বানুমান কৰা এপ্: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"আপোনাৰ ডিভাইচটো ঘূৰাওক"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"আঙুলিৰ স্পৰ্শৰ নিৰ্দেশেৰে কৰা নেভিগেশ্বনৰ টিউট’ৰিয়েল শেষ কৰিবলৈ অনুগ্ৰহ কৰি আপোনাৰ ডিভাইচটো ঘূৰাওক"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"আপুনি সোঁ অথবা বাওঁ কাষৰ একেবাৰে সীমাৰ পৰা ছোৱাইপ কৰাটো নিশ্চিত কৰক"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> বিস্তাৰ কৰক"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> সংকোচন কৰক"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"সন্ধান কৰিবৰ বাবে বৃত্ত"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"এপৰ আইকন"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"এপৰ শিৰোনাম"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"বন্ধ কৰা বুটাম"</string>
 </resources>
diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml
index df1b828..0e0181a 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Tətbiq təklifləri aktivdir"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Tətbiq təklifləri deaktivdir"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Proqnozlaşdırılan tətbiq: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Cihazı fırladın"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Jest naviqasiyası təlimatını tamamlamaq üçün cihazı fırladın"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Ən sağ və ya sol kənardan sürüşdürün"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"genişləndirin: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"yığcamlaşdırın: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Dairəyə alaraq axtarın"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Tətbiq ikonası"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Tətbiq başlığı"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Qapatma düyməsi"</string>
 </resources>
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index bad8811..95767e2 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Predlozi aplikacija su omogućeni"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Predlozi aplikacija su onemogućeni"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predviđamo aplikaciju: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotirajte uređaj"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rotirajte uređaj da biste dovršili vodič za navigaciju pomoću pokreta"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Obavezno prevucite od same desne ili leve ivice"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"proširite oblačić <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"skupite oblačić <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Pretraga zaokruživanjem"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikona aplikacije"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Naziv aplikacije"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Dugme Zatvori"</string>
 </resources>
diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml
index f61b337..0261c0f 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Прапановы праграм уключаны"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Прапановы праграм выключаны"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Праграма з падказкі: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Павярніце прыладу"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Каб пачаць азнаямленне з дапаможнікам па навігацыі жэстамі, павярніце прыладу"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Правядзіце пальцам справа налева ці злева направа ад самага краю экрана"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>: разгарнуць"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>: згарнуць"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Абвесці для пошуку"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Значок праграмы"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Назва праграмы"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Кнопка \"Закрыць\""</string>
 </resources>
diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index 12339d6..660d816 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Предложенията за приложения са активирани"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Функцията „Предложения за приложения“ е деактивирана"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Предвидено приложение: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Завъртете устройството си"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Моля, завъртете устройството си, за да завършите урока за навигиране с жестове"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Трябва да плъзнете пръст от най-дясната или най-лявата част на екрана"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"разгъване на <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"свиване на <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Търсене с ограждане"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Икона на приложението"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Име на приложението"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Бутон за затваряне"</string>
 </resources>
diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index 70cd5d1..b5b0a5e 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"অ্যাপ সাজেশন চালু করা আছে"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"অ্যাপ সাজেশন বন্ধ করা আছে"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"আপনার প্রয়োজন হতে পারে এমন অ্যাপ: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"আপনার ডিভাইস ঘোরান"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"জেসচার নেভিগেশন টিউটোরিয়াল সম্পূর্ণ করতে আপনার ডিভাইসটি ঘোরান"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"স্ক্রিনের একেবারে ডান বা বাঁদিকের প্রান্ত থেকে সোয়াইপ করেছেন কিনা তা দেখে নিন"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> বড় করুন"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> আড়াল করুন"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"খোঁজার জন্য সার্কেল বানান"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"অ্যাপ আইকন"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"অ্যাপের শিরোনাম"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"বন্ধ করার বোতাম"</string>
 </resources>
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index 430dd9d..69a5cdb 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Prijedlozi aplikacija su omogućeni"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Prijedlozi aplikacija su onemogućeni"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predviđena aplikacija: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotirajte uređaj"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rotirajte uređaj da završite vodič za navigaciju pokretima"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Prevucite s krajnjeg desnog ili lijevog ruba"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"proširivanje oblačića <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"sužavanje oblačića <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Pretraživanje zaokruživanjem"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikona aplikacije"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Naslov aplikacije"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Dugme za zatvaranje"</string>
 </resources>
diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml
index 5226ddc..cc643a6 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Els suggeriments d\'aplicacions estan activats"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Els suggeriments d\'aplicacions estan desactivats"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicció d\'aplicació: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Gira el dispositiu"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Gira el dispositiu per completar el tutorial de navegació amb gestos"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Assegura\'t de lliscar des de l\'extrem dret o esquerre de la pantalla."</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"desplega <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"replega <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Encercla per cercar"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Icona de l\'aplicació"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Títol de l\'aplicació"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Botó Tanca"</string>
 </resources>
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index a381ac6..eb0e8c8 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Návrhy aplikací jsou povoleny"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Návrhy aplikací jsou zakázány"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Předpokládaná aplikace: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Otočte zařízení"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Pokud chcete dokončit výukový program navigace gesty, otočte zařízení"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Přejeďte prstem z úplného pravého nebo levého okraje obrazovky"</string>
@@ -134,7 +136,7 @@
     <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Panel a bubliny jsou skryty"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigační panel"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Vždy zobrazovat panel aplikací"</string>
-    <string name="change_navigation_mode" msgid="9088393078736808968">"Změnit režim navigace"</string>
+    <string name="change_navigation_mode" msgid="9088393078736808968">"Změnit navigační režim"</string>
     <string name="taskbar_divider_a11y_title" msgid="6608690309720242080">"Rozdělovač panelu aplikací"</string>
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Přetečení panelu aplikací"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Přesunout doleva nahoru"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"rozbalit <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"sbalit <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Zakroužkuj a hledej"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikona aplikace"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Název aplikace"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Tlačítko Zavřít"</string>
 </resources>
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index fbe81f7..a7b7d04 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -23,7 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Frit format"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Computertilstand"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Flyt til ekstern skærm"</string>
-    <string name="recent_task_desktop" msgid="8081113562549637334">"Computer"</string>
+    <string name="recent_task_desktop" msgid="8081113562549637334">"Computertilstand"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Ingen nye elementer"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Indstillinger for appforbrug"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Ryd alt"</string>
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Appforslag er aktiveret"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Appforslag er deaktiveret"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"App, du forventes at skulle bruge: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotér din enhed"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rotér enheden for at gennemgå vejledningen i navigation med bevægelser"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Stryg fra kanten yderst til højre eller venstre"</string>
@@ -140,7 +142,7 @@
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Flyt til toppen eller venstre side"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Flyt til bunden eller højre side"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{yderligere app}one{yderligere app}other{yderligere apps}}"</string>
-    <string name="quick_switch_desktop" msgid="8393802056024499749">"Computer"</string>
+    <string name="quick_switch_desktop" msgid="8393802056024499749">"Computertilstand"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> og <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
     <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Boble"</string>
     <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overløb"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"udvid <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"skjul <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Appikon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Apptitel"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Knappen Luk"</string>
 </resources>
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index 30f2cfb..dd7b467 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Funktion „App-Vorschläge“ aktiviert"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Funktion \"App-Vorschläge\" deaktiviert"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Vorgeschlagene App: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Gerät drehen"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Bitte drehe dein Gerät, um die Anleitung für die Bedienung über Gesten abzuschließen"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Wische vom äußersten rechten oder linken Displayrand"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"„<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>“ maximieren"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"„<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>“ minimieren"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"App-Symbol"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Titel der App"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Schaltfläche „Schließen“"</string>
 </resources>
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index ecd8604..058d7ba 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Οι προτεινόμενες εφαρμογές ενεργοποιήθηκαν"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Οι προτεινόμενες εφαρμογές είναι απενεργοποιημένες"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Εφαρμογή από πρόβλεψη: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Περιστρέψτε τη συσκευή σας"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Περιστρέψτε τη συσκευή σας για να ολοκληρώσετε τον οδηγό πλοήγησης με κινήσεις"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Φροντίστε να σύρετε από το άκρο της δεξιάς ή της αριστερής πλευράς."</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"ανάπτυξη <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"σύμπτυξη <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Κυκλώστε για αναζήτηση"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Εικονίδιο εφαρμογής"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Τίτλος εφαρμογής"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Κουμπί κλεισίματος"</string>
 </resources>
diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index a799d0a..506e36d 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"App suggestions enabled"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"App suggestions are disabled"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicted app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotate your device"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Please rotate your device to complete the gesture navigation tutorial"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Make sure you swipe from the far-right or far-left edge"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expand <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"collapse <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"App icon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"App title"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Close button"</string>
 </resources>
diff --git a/quickstep/res/values-en-rCA/strings.xml b/quickstep/res/values-en-rCA/strings.xml
index 3528cd6..f91f4ee 100644
--- a/quickstep/res/values-en-rCA/strings.xml
+++ b/quickstep/res/values-en-rCA/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"App suggestions enabled"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"App suggestions are disabled"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicted app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotate your device"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Please rotate your device to complete the gesture navigation tutorial"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Make sure you swipe from the far-right or far-left edge"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expand <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"collapse <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"App icon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"App title"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Close button"</string>
 </resources>
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index a799d0a..506e36d 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"App suggestions enabled"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"App suggestions are disabled"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicted app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotate your device"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Please rotate your device to complete the gesture navigation tutorial"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Make sure you swipe from the far-right or far-left edge"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expand <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"collapse <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"App icon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"App title"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Close button"</string>
 </resources>
diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index a799d0a..506e36d 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"App suggestions enabled"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"App suggestions are disabled"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicted app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotate your device"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Please rotate your device to complete the gesture navigation tutorial"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Make sure you swipe from the far-right or far-left edge"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expand <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"collapse <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"App icon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"App title"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Close button"</string>
 </resources>
diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index f51cdea..dd969d7 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Sugerencias de apps habilitadas"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Las sugerencias de aplicaciones están inhabilitadas"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicción de app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rota el dispositivo"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rota el dispositivo para completar el instructivo de la navegación por gestos"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Asegúrate de deslizar desde el extremo derecho o izquierdo"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expandir <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"contraer <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Busca con un círculo"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ícono de la app"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Título de la app"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Botón de cerrar"</string>
 </resources>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index 06c2d50..1da35e6 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Sugerencias de aplicaciones habilitadas"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Las sugerencias de aplicaciones están inhabilitadas"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Aplicación sugerida: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Gira el dispositivo"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Gira el dispositivo para completar el tutorial de navegación por gestos"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Asegúrate de deslizar desde el borde derecho o izquierdo de la pantalla"</string>
@@ -139,7 +141,7 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Barra de tareas ampliada"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover arriba/a la izquierda"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover abajo/a la derecha"</string>
-    <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{aplicación más}other{aplicaciones más}}"</string>
+    <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{app más}other{apps más}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Ordenador"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> y <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
     <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Burbuja"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"desplegar <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"contraer <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Rodea para buscar"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Icono de la aplicación"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Título de la aplicación"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Botón de cerrar"</string>
 </resources>
diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml
index d394c2a..70acc52 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Rakenduste soovitused on lubatud"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Rakenduste soovitused on keelatud"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Ennustatud rakendus: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Pöörake seadet"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Pöörake seadet, et liigutustega navigeerimise õpetused lõpetada"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pühkige kindlasti parem- või vasakpoolsest servast"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"Toiminguriba <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> laiendamine"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"Toiminguriba <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ahendamine"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Ring otsimiseks"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Rakenduse ikoon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Rakenduse pealkiri"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Sulgemisnupp"</string>
 </resources>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index 02ea865..af5962a 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Gaituta daude aplikazioen iradokizunak"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Desgaituta daude aplikazioen iradokizunak"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Iragarritako aplikazioa: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Biratu gailua"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Keinu bidezko nabigazioaren tutoriala osatzeko, biratu gailua"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Ziurtatu hatza pantailaren eskuineko edo ezkerreko ertzetik hasten zarela pasatzen"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"zabaldu <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"tolestu <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Inguratu bilatzeko"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Aplikazioaren ikonoa"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Aplikazioaren izena"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Ixteko botoia"</string>
 </resources>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index f2f58ce..40469f5 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"«پیشنهاد برنامه» فعال است"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"«پیشنهاد برنامه» غیرفعال است"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"برنامه پیش‌بینی‌شده: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"دستگاهتان را بچرخانید"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"لطفاً برای تکمیل آموزش گام‌به‌گام پیمایش اشاره‌ای، دستگاهتان را بچرخانید"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"دقت کنید که از انتهای لبه سمت راست یا سمت چپ تند بکشید"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"ازهم باز کردن <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"جمع کردن <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"حلقه جستجو"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"نماد برنامه"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"عنوان برنامه"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"دکمه بستن"</string>
 </resources>
diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index d5540f4..6907e49 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Sovellusehdotukset käytössä"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Sovellusehdotukset on poistettu käytöstä"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Ennakoitu sovellus: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Käännä laite"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Käännä laite, niin voit katsoa esittelyn eleillä navigoinnista"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pyyhkäise aivan oikeasta tai vasemmasta reunasta"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"laajenna <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"tiivistä <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Sovelluskuvake"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Sovelluksen nimi"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Sulje-painike"</string>
 </resources>
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index c087fae..d57479d 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -21,9 +21,9 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Épingler"</string>
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Forme libre"</string>
-    <string name="recent_task_option_desktop" msgid="8280879717125435668">"Ordinateur de bureau"</string>
+    <string name="recent_task_option_desktop" msgid="8280879717125435668">"Bureau"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Passer à un écran externe"</string>
-    <string name="recent_task_desktop" msgid="8081113562549637334">"Ordinateur de bureau"</string>
+    <string name="recent_task_desktop" msgid="8081113562549637334">"Bureau"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Aucun élément récent"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres d\'utilisation de l\'appli"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Tout effacer"</string>
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Les suggestions d\'applis sont activées"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Les suggestions d\'applis sont désactivées"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Appli prédite : <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Faites pivoter votre appareil"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Veuillez faire pivoter votre appareil pour terminer le tutoriel de navigation par gestes"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Assurez-vous de balayer l\'écran à partir de l\'extrémité droite ou gauche"</string>
@@ -140,7 +142,7 @@
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Déplacer vers le coin supérieur gauche de l\'écran"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Déplacer vers le coin inférieur droit de l\'écran"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{autre appli}one{autre appli}other{autres applis}}"</string>
-    <string name="quick_switch_desktop" msgid="8393802056024499749">"Ordinateur de bureau"</string>
+    <string name="quick_switch_desktop" msgid="8393802056024499749">"Bureau"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> et <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
     <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bulle"</string>
     <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Bulle à développer"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"Développer <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"Réduire <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Encercler et rechercher"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Icône de l\'appli"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Nom de l\'appli"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Bouton Fermer"</string>
 </resources>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index ddda716..b185e0c 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -21,9 +21,9 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Épingler"</string>
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Format libre"</string>
-    <string name="recent_task_option_desktop" msgid="8280879717125435668">"Ordinateur"</string>
+    <string name="recent_task_option_desktop" msgid="8280879717125435668">"Mode ordinateur"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Déplacer vers l\'écran externe"</string>
-    <string name="recent_task_desktop" msgid="8081113562549637334">"Ordinateur"</string>
+    <string name="recent_task_desktop" msgid="8081113562549637334">"Mode ordinateur"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Aucun élément récent"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres de consommation de l\'application"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Tout effacer"</string>
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Suggestions d\'applications activées"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Les suggestions d\'applications sont désactivées"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Application prédite : <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Faire pivoter l\'appareil"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Veuillez faire pivoter votre appareil pour effectuer le tutoriel de navigation par gestes"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Veillez à bien balayer l\'écran depuis le bord gauche ou droit"</string>
@@ -140,7 +142,7 @@
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Déplacer en haut ou à gauche"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Déplacer en bas ou à droite"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{autre application}one{autre application}other{autres applications}}"</string>
-    <string name="quick_switch_desktop" msgid="8393802056024499749">"Ordinateur"</string>
+    <string name="quick_switch_desktop" msgid="8393802056024499749">"Mode ordinateur"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> et <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
     <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bulle"</string>
     <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Dépassement"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"Développer <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"Réduire <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Entourer pour chercher"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Icône de l\'application"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Titre de l\'application"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Bouton \"Fermer\""</string>
 </resources>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index ac02be0..8990a5f 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"As suxestións de aplicacións están activadas"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"As suxestións de aplicacións están desactivadas"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Aplicación predita: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Xira o dispositivo"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Xira o dispositivo para completar o titorial de navegación con xestos"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Asegúrate de pasar o dedo desde o bordo dereito ou esquerdo"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"despregar <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"contraer <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Rodear para buscar"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Icona da aplicación"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Título da aplicación"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Botón Pechar"</string>
 </resources>
diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml
index 57551d6..cb8a35a 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ઍપના સુઝાવો ચાલુ છે"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ઍપના સુઝાવો બંધ છે"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"પૂર્વાનુમાનિત ઍપ: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"તમારા ડિવાઇસને ફેરવો"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"સંકેતથી નૅવિગેશન ટ્યૂટૉરિઅલ પૂર્ણ કરવા માટે કૃપા કરીને તમારા ડિવાઇસને ફેરવો"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ખાતરી કરો કે તમે એકદમ દૂરની જમણી કે ડાબી કિનારીએથી સ્વાઇપ કરો છો"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> મોટો કરો"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> નાનો કરો"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"શોધવા માટે વર્તુળ દોરો"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ઍપનું આઇકન"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ઍપનું શીર્ષક"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"\'બંધ કરો\' બટન"</string>
 </resources>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index 4d6a91b..b7ca582 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"सुझाए गए ऐप्लिकेशन की सुविधा चालू है"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"सुझाए गए ऐप्लिकेशन की सुविधा बंद है"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"सुझाया गया ऐप्लिकेशन: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"अपना डिवाइस घुमाएं"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"जेस्चर वाले नेविगेशन से जुड़े ट्यूटोरियल को पूरा करने के लिए अपने डिवाइस को घुमाएं"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"स्क्रीन पर बिलकुल दाएं या बाएं किनारे से स्वाइप करें"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> को बड़ा करें"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> को छोटा करें"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"सर्कल बनाकर ढूंढें"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ऐप्लिकेशन आइकॉन"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ऐप्लिकेशन का नाम"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"\'बंद करें\' बटन"</string>
 </resources>
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index dc66c49..cb7d9b9 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Predlaganje apl. omogućeno"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Predlaganje apl. onemogućeno"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predviđena aplikacija: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Zakrenite uređaj"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Zakrenite uređaj da biste dovršili vodič o navigaciji pokretima"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Prijeđite prstom od krajnjeg desnog ili krajnjeg lijevog ruba"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"proširite oblačić <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"sažmite oblačić <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Zaokružite i potražite"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikona aplikacije"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Naziv aplikacije"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Gumb Zatvori"</string>
 </resources>
diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index 4a2b9d2..184fef0 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Alkalmazásjavaslatok engedélyezve"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Alkalmazásjavaslatok letiltva"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Várható alkalmazás: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Forgassa el eszközét"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Forgassa el eszközét a kézmozdulatokkal való navigáció útmutatójának befejezéséhez"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Csúsztasson a képernyő jobb vagy bal széléről."</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> kibontása"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> összecsukása"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Bekarikázással keresés"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Alkalmazásikon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Alkalmazás neve"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Bezárás gomb"</string>
 </resources>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index b7e2800..8da1a6d 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"«Առաջարկվող հավելվածներ» գործառույթը միացված է"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"«Առաջարկվող հավելվածներ» գործառույթն անջատված է"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Առաջարկվող հավելված՝ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Պտտեք սարքը"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Պտտեք սարքը՝ ժեստերով նավիգացիայի ուղեցույցն ավարտելու համար"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Համոզվեք, որ մատը սահեցնում եք էկրանի աջ կամ ձախ եզրից"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>. ծավալել"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>. ծալել"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Շրջագծել որոնելու համար"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Հավելվածի պատկերակ"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Հավելվածի անվանում"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"«Փակել» կոճակ"</string>
 </resources>
diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml
index 243b428..eb592db 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Saran aplikasi diaktifkan"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Saran aplikasi dinonaktifkan"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Aplikasi yang diprediksi: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Putar perangkat Anda"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Putar perangkat Anda untuk menyelesaikan tutorial navigasi gestur"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pastikan Anda menggeser dari tepi ujung kanan atau ujung kiri"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"luaskan <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"ciutkan <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Lingkari untuk Menelusuri"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikon aplikasi"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Judul aplikasi"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Tombol tutup"</string>
 </resources>
diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml
index 8265027..3500001 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Kveikt á tillögum að forritum"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Slökkt er á tillögðum forritum"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Tillaga að forriti: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Snúðu tækinu"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Snúðu tækinu til að ljúka leiðsögn um bendingastjórnun"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Passaðu að strjúka frá jaðri hægri eða vinstri brúnar"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"stækka <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"minnka <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Forritstákn"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Titil forrits"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Hnappur til að loka"</string>
 </resources>
diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml
index e7771d9..b8fa813 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"La funzionalità app suggerite è attiva"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"La funzionalità app suggerite è disattivata"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"App prevista: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Ruota il dispositivo"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Ruota il dispositivo per completare il tutorial relativo alla navigazione tramite gesti"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Assicurati di scorrere dal bordo all\'estrema destra o all\'estrema sinistra"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"espandi <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"comprimi <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Cerchia e Cerca"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Icona dell\'app"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Titolo dell\'app"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Pulsante Chiudi"</string>
 </resources>
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index ae84699..c6fc845 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"התכונה \'הצעות לאפליקציות\' מופעלת"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ההצעות לאפליקציות מושבתות"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"האפליקציות החזויות: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"צריך לסובב את המכשיר"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"צריך לסובב את המכשיר כדי להשלים את המדריך לניווט באמצעות תנועות"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"חשוב להחליק מהקצה השמאלי או הימני"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"הרחבה של <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"כיווץ של <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"מקיפים ומחפשים"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"סמל האפליקציה"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"שם האפליקציה"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"כפתור הסגירה"</string>
 </resources>
diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml
index 3be39b9..5fdcf4a 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -23,7 +23,7 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"フリーフォーム"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"デスクトップ"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"外部ディスプレイに移動する"</string>
-    <string name="recent_task_desktop" msgid="8081113562549637334">"パソコン"</string>
+    <string name="recent_task_desktop" msgid="8081113562549637334">"デスクトップ"</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>
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"アプリの候補表示が有効です"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"アプリの候補は無効です"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"予測されたアプリ: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"デバイスを回転してください"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ジェスチャー ナビゲーションのチュートリアルを終了するには、デバイスを回転してください"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"右端または左端からスワイプしてください"</string>
@@ -140,7 +142,7 @@
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"上 / 左に移動"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"下 / 右に移動"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{個のその他のアプリ}other{個のその他のアプリ}}"</string>
-    <string name="quick_switch_desktop" msgid="8393802056024499749">"パソコン"</string>
+    <string name="quick_switch_desktop" msgid="8393802056024499749">"デスクトップ"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> と <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
     <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ふきだし"</string>
     <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"オーバーフロー"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>を開きます"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>を閉じます"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"かこって検索"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"アプリのアイコン"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"アプリのタイトル"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"閉じるボタン"</string>
 </resources>
diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml
index d6797d0..01becd7 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"აპის შეთავაზებები ჩართულია"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"აპის შეთავაზებები გათიშულია"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ნაწინასწარმეტყველები აპი: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"შეატრიალეთ მოწყობილობა"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ჟესტებით ნავიგაციის სახელმძღვანელოს დასასრულებლად შეატრიალეთ თქვენი მოწყობილობა"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"გადაფურცლეთ უკიდურესი მარჯვენა ან მარცხენა ბოლოდან"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>-ის გაფართოება"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>-ის ჩაკეცვა"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"ძიება წრის მოხაზვით"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"აპის ხატულა"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"აპის სათაური"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"დახურვის ღილაკი"</string>
 </resources>
diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index 632ed16..3280908 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"\"Қолданба ұсыныстары\" функциясы қосылды."</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"\"Қолданба ұсыныстары\" функциясы өшірулі."</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Болжалды қолданба: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Құрылғыны бұрыңыз"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Қимылмен басқару нұсқаулығын аяқтау үшін құрылғыны бұрыңыз."</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Экранның оң немесе сол жиегінен сырғытыңыз."</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>: жаю"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>: жию"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Қоршау арқылы іздеу"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Қолданба белгішесі"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Қолданба атауы"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"\"Жабу\" түймесі"</string>
 </resources>
diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml
index b5158db..d05ef16 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"បានបើក​ការណែនាំ​កម្មវិធី"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"បានបិទ​ការណែនាំ​កម្មវិធី"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"កម្មវិធី​ដែលបាន​ព្យាករ៖ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"បង្វិលឧបករណ៍របស់អ្នក"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"សូមបង្វិលឧបករណ៍របស់អ្នក ដើម្បីបញ្ចប់មេរៀនអំពីការរុករកដោយប្រើចលនា"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ត្រូវប្រាកដថា​អ្នកអូសពី​គែមខាងស្ដាំ ឬ​ខាងឆ្វេង"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"ពង្រីក <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"បង្រួម <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"គូររង្វង់ដើម្បីស្វែងរក"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"រូបកម្មវិធី"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ចំណងជើងកម្មវិធី"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"ប៊ូតុងបិទ"</string>
 </resources>
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index 438bbf0..3240b2a 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ಆ್ಯಪ್ ಸಲಹೆಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ಆ್ಯಪ್ ಸಲಹೆಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ಶಿಫಾರಸು ಮಾಡಿದ ಆ್ಯಪ್: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"ನಿಮ್ಮ ಸಾಧನವನ್ನು ತಿರುಗಿಸಿ"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ಗೆಸ್ಚರ್ ನ್ಯಾವಿಗೇಶನ್ ಟುಟೋರಿಯಲ್ ಅನ್ನು ಪೂರ್ಣಗೊಳಿಸಲು, ನಿಮ್ಮ ಸಾಧನವನ್ನು ತಿರುಗಿಸಿ"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ನೀವು ಬಲಕೊನೆಯ ಅಂಚಿನಿಂದ ಅಥವಾ ಎಡಕೊನೆಯ ಅಂಚಿನಿಂದ ಸ್ವೈಪ್ ಮಾಡುತ್ತಿದ್ದೀರಿ ಎಂದು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ಅನ್ನು ವಿಸ್ತೃತಗೊಳಿಸಿ"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ಅನ್ನು ಕುಗ್ಗಿಸಿ"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"ಹುಡುಕಲು ಒಂದು ಸರ್ಕಲ್ ರಚಿಸಿ"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ಆ್ಯಪ್ ಐಕಾನ್"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ಆ್ಯಪ್ ಶೀರ್ಷಿಕೆ"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"ಮುಚ್ಚುವ ಬಟನ್"</string>
 </resources>
diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index b50bd03..c8b8674 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"앱 제안이 사용 설정됨"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"앱 제안이 사용 중지됨"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"예상 앱: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"기기를 회전시키세요"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"동작 탐색 튜토리얼을 완료하려면 기기를 회전시키세요."</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"오른쪽 또는 왼쪽 가장자리 끝에서 스와이프하세요."</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> 펼치기"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> 접기"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"서클 투 서치"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"앱 아이콘"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"앱 제목"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"닫기 버튼"</string>
 </resources>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index f9ac3ca..6dafcd0 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Сунушталган колдонмолор функциясы иштетилди"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Сунушталган колдонмолор функциясы өчүрүлгөн"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Божомолдонгон колдонмо: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Түзмөгүңүздү буруңуз"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Жаңсап чабыттоо үйрөткүчүнүн аягына чыгуу үчүн түзмөктү буруңуз"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Экранды эң четинен оңдон солго же солдон оңго карай сүрүңүз"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> жайып көрсөтүү"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> жыйыштыруу"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Тегеректеп издөө"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Колдонмонун сүрөтчөсү"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Колдонмонун аталышы"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Жабуу баскычы"</string>
 </resources>
diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index ec1622f..a13da69 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ເປີດການນຳໃຊ້ການແນະນຳແອັບແລ້ວ"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ປິດການນຳໃຊ້ການແນະນຳແອັບແລ້ວ"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ແອັບທີ່ຄາດເດົາໄວ້: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"ໝຸນອຸປະກອນຂອງທ່ານ"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ກະລຸນາໝຸນອຸປະກອນຂອງທ່ານເພື່ອເຮັດຕາມການສອນການນຳໃຊ້ກ່ຽວກັບການນຳທາງແບບທ່າທາງໃຫ້ສຳເລັດ"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ກະລຸນາກວດສອບວ່າທ່ານປັດຈາກຂອບຂວາສຸດ ຫຼື ຊ້າຍສຸດ"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"ຂະຫຍາຍ <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"ຫຍໍ້ <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ລົງ"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"ແຕ້ມວົງມົນເພື່ອຊອກຫາ"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ໄອຄອນແອັບ"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ຊື່ແອັບ"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"ປຸ່ມປິດ"</string>
 </resources>
diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index cc44886..7334b0a 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Siūlomų programų funkcija įgalinta"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Siūlomų programų funkcija išjungta"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Numatoma programa: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Pasukite įrenginį"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Pasukite įrenginį, kad pereitumėte į naršymo gestais mokomąją medžiagą"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Turite perbraukti nuo dešiniojo ar kairiojo krašto"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"išskleisti „<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>“"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"sutraukti „<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>“"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Paieška apibrėžiant"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Programos piktograma"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Programos pavadinimas"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Mygtukas „Uždaryti“"</string>
 </resources>
diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml
index c57db9c..bbf45c3 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Ieteicamās lietotnes ir iespējotas"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Ieteicamās lietotnes ir atspējotas"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Prognozētā lietotne: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Pagrieziet ierīci"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Lūdzu, pagrieziet savu ierīci, lai pabeigtu žestu navigācijas apmācību."</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Jāvelk no pašas labās vai kreisās malas."</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"izvērst “<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>”"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"sakļaut “<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>”"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Apvilkt un meklēt"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Lietotnes ikona"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Lietotnes nosaukums"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Poga Aizvērt"</string>
 </resources>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index 186153d..5340854 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Предлозите за апликации се овозможени"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Предлозите за апликации се оневозможени"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Предвидена апликација: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Ротирајте го уредот"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Ротирајте го уредот за да го завршите упатството за навигација со движење"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Повлечете од крајниот десен или крајниот лев раб"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"прошири <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"собери <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Пребарување со заокружување"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Икона за апликацијата"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Наслов на апликацијата"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Копче за затворање"</string>
 </resources>
diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index b25ce07..d92ddbc 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ആപ്പ് നിർദ്ദേശങ്ങൾ പ്രവർത്തനക്ഷമമാക്കി"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ആപ്പ് നിർദ്ദേശങ്ങൾ പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"പ്രവചിച്ച ആപ്പ്: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"നിങ്ങളുടെ ഉപകരണം റൊട്ടേറ്റ് ചെയ്യുക"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ജെസ്ച്ചർ നാവിഗേഷൻ ട്യൂട്ടോറിയൽ പൂർത്തിയാക്കാൻ നിങ്ങളുടെ ഉപകരണം റൊട്ടേറ്റ് ചെയ്യുക"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"വലത്തേയറ്റത്തെയോ ഇടത്തേയറ്റത്തെയോ അരികിൽ നിന്നാണ് സ്വെെപ്പ് ചെയ്യുന്നതെന്ന് ഉറപ്പാക്കുക"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> വികസിപ്പിക്കുക"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ചുരുക്കുക"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"തിരയാൻ വട്ടം വരയ്ക്കൽ"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ആപ്പ് ഐക്കൺ"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ആപ്പിന്റെ പേര്"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"അടയ്ക്കുക ബട്ടൺ"</string>
 </resources>
diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index b45bfa4..2b335a9 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Санал болгож буй аппуудыг идэвхжүүлсэн"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Санал болгож буй аппуудыг идэвхгүй болгосон"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Таамаглаж буй апп: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Төхөөрөмжөө эргүүлэх"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Зангааны навигацын практик хичээлийг дуусгахын тулд төхөөрөмжөө эргүүлнэ үү"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Та баруун зах эсвэл зүүн захын ирмэгээс шударна уу"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>-г дэлгэх"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>-г хураах"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Тойруулж зураад хай"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Aппын дүрс тэмдэг"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Аппын нэр"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Хаах товч"</string>
 </resources>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index 06b8bd8..647e88d 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"अ‍ॅप सूचना सुरू केल्या आहेत"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"अ‍ॅप सूचना बंद केल्या आहेत"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"पूर्वानुमान केलेले अ‍ॅप: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"तुमचे डिव्हाइस फिरवा"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"कृपया जेश्चर नेव्हिगेशन ट्यूटोरियल पूर्ण करण्यासाठी तुमचे डिव्हाइस फिरवा"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"तुम्ही स्क्रीनच्या अगदी उजव्या किंवा अगदी डाव्या कडेपासून स्‍वाइप करत आहात खात्री करा"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> चा विस्तार करा"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> कोलॅप्स करा"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"शोधण्यासाठी वर्तुळ करा"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"अ‍ॅपचा आयकन"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"अ‍ॅपचे शीर्षक"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"बंद करा बटण"</string>
 </resources>
diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml
index 82839e2..baffcbb 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Cadangan apl didayakan"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Cadangan apl dilumpuhkan"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Apl yang diramalkan: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Putarkan peranti anda"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Sila putarkan peranti anda untuk melengkapkan tutorial navigasi gerak isyarat"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pastikan anda meleret dari hujung sebelah kanan atau hujung sebelah kiri"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"kembangkan <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"kuncupkan <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Bulatkan untuk Membuat Carian"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikon apl"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Tajuk apl"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Butang tutup"</string>
 </resources>
diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml
index 2951a09..f89920b 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"အက်ပ်အကြံပြုချက်များ ဖွင့်ထားသည်"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"အက်ပ်အကြံပြုချက်များကို ပိတ်ထားသည်"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ကြိုတင်မှန်းဆထားသော အက်ပ်− <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"သင့်စက်ကို လှည့်ပါ"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"လက်ဟန်ဖြင့် လမ်းညွှန်ခြင်း ရှင်းလင်းပို့ချချက်အား အပြီးသတ်ရန် သင့်စက်ကို လှည့်ပါ"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ညာ (သို့) ဘယ်ဘက်အစွန်ဆုံးမှ ပွတ်ဆွဲကြောင်း သေချာပါစေ"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ကို ပိုပြပါ"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ကို လျှော့ပြပါ"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"ရှာရန် ကွက်၍ဝိုင်းလိုက်ပါ"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"အက်ပ်သင်္ကေတ"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"အက်ပ်ခေါင်းစဉ်"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"အပိတ် ခလုတ်"</string>
 </resources>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index c485ecf..3be8d68 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Appforslag er på"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Appforslag er slått av"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Foreslått app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Roter enheten"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Roter enheten for å fullføre veiledningen for navigasjon med bevegelser"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Sørg for at du sveiper fra kanten helt til høyre eller venstre"</string>
@@ -140,7 +142,7 @@
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Flytt til øverst/venstre"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Flytt til nederst/høyre"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{app til}other{apper til}}"</string>
-    <string name="quick_switch_desktop" msgid="8393802056024499749">"Datamaskin"</string>
+    <string name="quick_switch_desktop" msgid="8393802056024499749">"Skrivebord"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> og <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
     <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Boble"</string>
     <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overflyt"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"vis <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"skjul <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Appikon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Apptittel"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Lukkeknapp"</string>
 </resources>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index 4d0e82a..4ed8bb9 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"सिफारिस गरिएका एप देखाउने सुविधा अन गरिएको छ"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"सिफारिस गरिएका एपहरू देखाउने सुविधा असक्षम पारिएको छ"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"पूर्वानुमान गरिएको एप: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"आफ्नो डिभाइस रोटेट गर्नुहोस्"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"जेस्चर नेभिगेसनको ट्युटोरियल पूरा गर्न कृपया आफ्नो डिभाइस रोटेट गर्नुहोस्"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"स्क्रिनको सबैभन्दा दायाँ किनारा वा सबैभन्दा बायाँ किनाराबाट स्वाइप गर्नुहोस्"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> एक्स्पान्ड गर्नुहोस्"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> कोल्याप्स गर्नुहोस्"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"खोज्न सर्कल बनाउनुहोस्"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"एप जनाउने आइकन"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"एपको शीर्षक"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"\"बन्द गर्नुहोस्\" बटन"</string>
 </resources>
diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml
index 20b132f..c1c1a22 100644
--- a/quickstep/res/values-nl/strings.xml
+++ b/quickstep/res/values-nl/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"App-suggesties staan aan"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"App-suggesties staan uit"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Voorspelde app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Het apparaat draaien"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Draai het apparaat om de tutorial voor navigatie met gebaren af te ronden"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Swipe vanaf de rechter- of linkerrand"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> uitvouwen"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> samenvouwen"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Icoon van app"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Titel van app"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Knop Sluiten"</string>
 </resources>
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index fe71252..789ba0a 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ଆପ୍ ପରାମର୍ଶଗୁଡ଼ିକୁ ସକ୍ଷମ କରାଯାଇଛି"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ଆପ୍ ପରାମର୍ଶଗୁଡ଼ିକୁ ଅକ୍ଷମ କରାଯାଇଛି"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ପୂର୍ବାନୁମାନ କରାଯାଇଥିବା ଆପ୍: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"ଆପଣଙ୍କ ଡିଭାଇସକୁ ରୋଟେଟ କରନ୍ତୁ"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ଜେଶ୍ଚର ନାଭିଗେସନ ଟ୍ୟୁଟୋରିଆଲ ସମ୍ପୂର୍ଣ୍ଣ କରିବାକୁ ଦୟାକରି ଆପଣଙ୍କ ଡିଭାଇସ ରୋଟେଟ କରନ୍ତୁ"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ଆପଣ ସ୍କ୍ରିନର ଏକଦମ-ଡାହାଣ ବା ବାମ ଧାରରୁ ସ୍ୱାଇପ କରୁଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ।"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ବିସ୍ତାର କରନ୍ତୁ"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"ସର୍ଚ୍ଚ କରିବାକୁ ସର୍କଲ କରନ୍ତୁ"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ଆପ ଆଇକନ"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ଆପ ଟାଇଟେଲ"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"\"ବନ୍ଦ କରନ୍ତୁ\" ବଟନ"</string>
 </resources>
diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index 73b971f..2392890 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ਐਪ ਸੁਝਾਵਾਂ ਨੂੰ ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ਐਪ ਸੁਝਾਵਾਂ ਨੂੰ ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ਪੂਰਵ ਅਨੁਮਾਨਿਤ ਐਪ: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਘੁੰਮਾਓ"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ਕਿਰਪਾ ਕਰਕੇ ਇਸ਼ਾਰਾ ਨੈਵੀਗੇਸ਼ਨ ਟਿਊਟੋਰੀਅਲ ਨੂੰ ਪੂਰਾ ਕਰਨ ਲਈ ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਘੁੰਮਾਓ"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ਇਹ ਪੱਕਾ ਕਰੋ ਕਿ ਤੁਸੀਂ ਸੱਜੇ ਜਾਂ ਖੱਬੇ ਪਾਸੇ ਦੇ ਬਿਲਕੁਲ ਕਿਨਾਰੇ ਤੋਂ ਸਵਾਈਪ ਕਰਦੇ ਹੋ"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ਦਾ ਵਿਸਤਾਰ ਕਰੋ"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ਨੂੰ ਸਮੇਟੋ"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"ਖੋਜਣ ਲਈ ਚੱਕਰ ਬਣਾਓ"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ਐਪ ਪ੍ਰਤੀਕ"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ਐਪ ਸਿਰਲੇਖ"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"\'ਬੰਦ ਕਰੋ\' ਬਟਨ"</string>
 </resources>
diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index 4eb9c45..74aec66 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Włączono sugestie aplikacji"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Sugestie aplikacji są wyłączone"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Przewidywana aplikacja: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Obróć urządzenie"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Obróć urządzenie, aby ukończyć samouczek nawigacji przy użyciu gestów"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pamiętaj, aby przesuwać palcem od samej krawędzi (prawej lub lewej)"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"rozwiń dymek: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"zwiń dymek: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Zaznacz, aby wyszukać"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikona aplikacji"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Tytuł aplikacji"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Przycisk Zamknij"</string>
 </resources>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index a2e3a43..56d5514 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Sugestões de apps ativadas"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"As sugestões de apps estão desativadas"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"App prevista: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rode o dispositivo"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rode o seu dispositivo para concluir o tutorial de navegação por gestos"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Deslize a partir da extremidade mais à direita ou mais à esquerda"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expandir <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"reduzir <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circundar para Pesquisar"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ícone da app"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Título da app"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Botão Fechar"</string>
 </resources>
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index f7022ec..bdcad13 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"O recurso \"sugestões de apps\" está ativado"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"O recurso \"sugestões de apps\" está desativado"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"App previsto: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Gire o dispositivo"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Gire o dispositivo para concluir o tutorial da navegação por gestos"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Deslize da borda direita ou esquerda"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"abrir <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"fechar <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circule para pesquisar"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ícone do app"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Título do app"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Botão \"Fechar\""</string>
 </resources>
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index 4ad6846..6a05909 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Sugestiile de aplicații au fost activate"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Sugestiile de aplicații au fost dezactivate"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Aplicația estimată: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotește dispozitivul"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rotește dispozitivul pentru a încheia tutorialul de navigare prin gesturi"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Glisează dinspre marginea dreaptă îndepărtată sau dinspre marginea stângă îndepărtată"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"extinde <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"restrânge <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Încercuiește și caută"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Pictograma aplicației"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Titlul aplicației"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Buton de închidere"</string>
 </resources>
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index 2a52261..69f560d 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Функция \"Рекомендуемые приложения\" включена."</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Функция \"Рекомендуемые приложения\" отключена."</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Рекомендуемое приложение: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Поверните устройство"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Чтобы перейти к руководству по жестам, нужно повернуть устройство."</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Проведите справа налево или слева направо от самого края экрана."</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"Развернуто: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"Свернуто: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Обвести и найти"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Значок приложения"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Название приложения"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Кнопка \"Закрыть\""</string>
 </resources>
diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml
index bb95988..2b1f147 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"යෙදුම් යෝජනා සබලිතයි"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"යෙදුම් යෝජනා අබල කර ඇත"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"පුරෝකථනය කළ යෙදුම: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"ඔබේ උපාංගය කරකවන්න"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"අභින සංචාලන නිබන්ධනය සම්පූර්ණ කිරීම සඳහා ඔබේ උපාංගය කරකවන්න"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ඔබ ඈත දකුණු හෝ ඈත වම් දාරයේ සිට ස්වයිප් කරන බව සහතික කර ගන්න"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> දිග හරින්න"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> හකුළන්න"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"සෙවීමට කවයසෙවීමට කවය අදින්න"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"යෙදුම් නිරූපකය"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"යෙදුම් මාතෘකාව"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"වැසීමේ බොත්තම"</string>
 </resources>
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index bd22b29..c2e6b85 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Návrhy aplikácií zapnuté"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Návrhy aplikácií vypnuté"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predpovedaná aplikácia: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Otočte zariadenie"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Otočte zariadenie a dokončite tak návod, ako navigovať gestami"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Musíte potiahnuť úplne z pravého alebo ľavého okraja."</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"rozbaliť <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"zbaliť <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Vyhľadávanie krúžením"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikona aplikácie"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Názov aplikácie"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Tlačidlo Zavrieť"</string>
 </resources>
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index 740d95e..0922f24 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Predlogi aplikacij so omogočeni."</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Predlogi aplikacij so onemogočeni."</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predvidena aplikacija: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Zasukajte napravo"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Zasukajte napravo, če si želite ogledati vadnico za krmarjenje s potezami"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pazite, da povlečete s skrajno desnega ali skrajno levega roba."</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"razširitev oblačka <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"strnitev oblačka <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Iskanje z obkroževanjem"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikona aplikacije"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Ime aplikacije"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Gumb za zapiranje"</string>
 </resources>
diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index 12d824f..50b9fa1 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Aplikacionet e sugjeruara janë aktivizuar"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Sugjerimet e aplikacioneve janë çaktivizuar"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Aplikacioni i parashikuar: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rrotullo pajisjen"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rrotullo pajisjen për të përfunduar udhëzuesin e navigimit me gjeste"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Sigurohu që të rrëshqasësh shpejt nga skaji më i djathtë ose më i majtë"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"zgjero <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"palos <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Qarko për të kërkuar"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikona e aplikacionit"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Titulli i aplikacionit"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Butoni i mbylljes"</string>
 </resources>
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index b6830aa..a20e927 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Предлози апликација су омогућени"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Предлози апликација су онемогућени"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Предвиђамо апликацију: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Ротирајте уређај"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Ротирајте уређај да бисте довршили водич за навигацију помоћу покрета"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Обавезно превуците од саме десне или леве ивице"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"проширите облачић <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"скупите облачић <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Претрага заокруживањем"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Икона апликације"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Назив апликације"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Дугме Затвори"</string>
 </resources>
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index c752a79..7c826cd 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -21,9 +21,9 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="recent_task_option_pin" msgid="7929860679018978258">"Fäst"</string>
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Fritt format"</string>
-    <string name="recent_task_option_desktop" msgid="8280879717125435668">"Dator"</string>
+    <string name="recent_task_option_desktop" msgid="8280879717125435668">"Skrivbordsläge"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Flytta till extern skärm"</string>
-    <string name="recent_task_desktop" msgid="8081113562549637334">"Dator"</string>
+    <string name="recent_task_desktop" msgid="8081113562549637334">"Skrivbordsläge"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Listan är tom"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Inställningar för appanvändning"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Rensa alla"</string>
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Appförslag har aktiverats"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Appförslag har inaktiverats"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Appförslag: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotera enheten"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rotera enheten för att slutföra guiden för navigering med rörelser"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Se till att du sveper ända från högerkanten eller vänsterkanten"</string>
@@ -140,7 +142,7 @@
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Flytta högst upp/till vänster"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Flytta längst ned/till höger"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{app till}other{appar till}}"</string>
-    <string name="quick_switch_desktop" msgid="8393802056024499749">"Dator"</string>
+    <string name="quick_switch_desktop" msgid="8393802056024499749">"Skrivbordsläge"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> och <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
     <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubbla"</string>
     <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Fler alternativ"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"utöka <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"komprimera <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Appikon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Apptitel"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Knappen Stäng"</string>
 </resources>
diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml
index 05d120c..9181599 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Mapendekezo ya programu yamewashwa"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Umezima mapendekezo ya programu"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Programu iliyotabiriwa: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Zungusha kifaa chako"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Tafadhali zungusha kifaa chako ili ukamilishe mafunzo ya usogezaji kwa kutumia ishara"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Hakikisha unatelezesha kidole kutoka ukingo wa kulia au kushoto kabisa"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"panua <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"kunja <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Chora Mviringo ili Kutafuta"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Aikoni ya programu"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Kichwa cha programu"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Kitufe cha kufunga"</string>
 </resources>
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index 75ef7f0..4e3a075 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ஆப்ஸ் பரிந்துரைகள் இயக்கப்பட்டுள்ளன"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ஆப்ஸ் பரிந்துரைகள் முடக்கப்பட்டுள்ளன"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"கணித்த ஆப்ஸ்: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"உங்கள் சாதனத்தைச் சுழற்றுங்கள்"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"சைகை வழிசெலுத்தல் பயிற்சியை நிறைவுசெய்ய உங்கள் சாதனத்தைச் சுழற்றுங்கள்"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"வலது அல்லது இடது ஓரத்தின் விளிம்பிலிருந்து ஸ்வைப் செய்வதை உறுதிசெய்துகொள்ளுங்கள்"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ஐ விரிவாக்கும்"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ஐச் சுருக்கும்"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"வட்டமிட்டுத் தேடல்"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ஆப்ஸ் ஐகான்"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ஆப்ஸ் தலைப்பு"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"மூடுவதற்கான பட்டன்"</string>
 </resources>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index ac5479e..94fac1b 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"యాప్ సలహాలు ఎనేబుల్ చేయబడ్డాయి"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"యాప్ సూచ‌న‌లు డిజేబుల్‌ చేయబడ్డాయి"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"సూచించబడిన యాప్: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"మీ పరికరాన్ని రొటేట్ చేయండి"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"సంజ్ఞ నావిగేషన్ ట్యుటోరియల్‌ను పూర్తి చేయడానికి దయచేసి మీ పరికరాన్ని రొటేట్ చేయండి"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"కుడి వైపు చిట్ట చివరి లేదా ఎడమ వైపు చిట్ట చివరి అంచు నుండి స్వైప్ చేస్తున్నారని నిర్ధారించుకోండి"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ను విస్తరించండి"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ను కుదించండి"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"సెర్చ్ చేయడానికి సర్కిల్ గీయండి"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"యాప్ చిహ్నం"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"యాప్ టైటిల్"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"\'మూసివేయండి\' బటన్"</string>
 </resources>
diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml
index c9dadd7..ee515b9 100644
--- a/quickstep/res/values-th/strings.xml
+++ b/quickstep/res/values-th/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"เปิดใช้แอปแนะนำแล้ว"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ปิดใช้คำแนะนำเกี่ยวกับแอปอยู่"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"แอปที่คาดว่าจะใช้: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"หมุนอุปกรณ์ของคุณ"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"โปรดหมุนอุปกรณ์เพื่อทำตามบทแนะนำการนำทางด้วยท่าทางสัมผัสให้เสร็จสมบูรณ์"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ปัดจากขอบด้านขวาสุดหรือซ้ายสุด"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"ขยาย <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"ยุบ <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"วงเพื่อค้นหา"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ไอคอนแอป"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ชื่อแอป"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"ปุ่มปิด"</string>
 </resources>
diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml
index c72e38b..1bc4a3d 100644
--- a/quickstep/res/values-tl/strings.xml
+++ b/quickstep/res/values-tl/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Naka-enable ang mga iminumungkahing app"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Naka-disable ang mga iminumungkahing app"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Hinulaang app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"I-rotate ang iyong device"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Paki-rotate ang iyong device para tapusin ang tutorial sa navigation gamit ang galaw"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Tiyaking magsa-swipe ka mula sa dulong kanan o dulong kaliwang gilid"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"i-expand ang <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"i-collapse ang <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Icon ng app"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Pamagat ng app"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Button na isara"</string>
 </resources>
diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index 645ef54..691e6c0 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Uygulama önerileri etkinleştirildi"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Uygulama önerileri devre dışı bırakıldı"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Tahmin edilen uygulama: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Cihazınızı döndürün"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Hareketle gezinme eğitimini tamamlamak için lütfen cihazınızı döndürün"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"En sağ veya en sol kenardan kaydırdığınızdan emin olun"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"genişlet: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"daralt: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Seçerek Arat"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Uygulama simgesi"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Uygulama başlığı"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Kapat düğmesi"</string>
 </resources>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index 8629d49..1668b4f 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Рекомендовані додатки ввімкнено"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Рекомендовані додатки вимкнено"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Передбачений додаток: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Оберніть пристрій"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Обертайте пристрій, щоб ознайомитися з посібником із навігації за допомогою жестів"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Проведіть пальцем від самого краю екрана (правого або лівого)"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"розгорнути \"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>\""</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"згорнути \"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>\""</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Обвести й знайти"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Значок додатка"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Назва додатка"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Кнопка \"Закрити\""</string>
 </resources>
diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml
index 3ca1fb8..5b780c3 100644
--- a/quickstep/res/values-ur/strings.xml
+++ b/quickstep/res/values-ur/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ایپ کی تجاویز فعال ہیں"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ایپ کی تجاویز غیر فعال ہیں"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"پیشن گوئی کردہ ایپ: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"اپنا آلہ گھمائیں"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"براہ کرم اشاروں والی نیویگیشن کا ٹیوٹوریل مکمل کرنے کے لیے اپنا آلہ گھمائیں"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"یقینی بنائیں کہ آپ دائیں یا بائیں کنارے سے دور سے سوائپ کریں"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> کو پھیلائیں"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> کو سکیڑیں"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"تلاش کرنے کیلئے دائرہ بنائیں"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ایپ آئیکن"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ایپ کا عنوان"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"\'بند کریں\' بٹن"</string>
 </resources>
diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index b4c2ef1..7de3648 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Ilova tavsiyalari yoqildi"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Endi ilova takliflari chiqmaydi"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Taklif etilgan ilova: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Qurilmangizni buring"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Ishorali navigatsiya darsligini tugatish uchun qurilmani buring"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Ekran chetidan boshlab oʻngdan yoki chapdan suring"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ni yoyish"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ni yigʻish"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Chizib qidirish"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ilova belgisi"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Ilova nomi"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Yopish tugmasi"</string>
 </resources>
diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index a27b053..dffcd6c 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Đã bật tính năng Ứng dụng đề xuất"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Tính năng Ứng dụng đề xuất bị tắt"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Ứng dụng dự đoán: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Xoay thiết bị của bạn"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Vui lòng xoay thiết bị của bạn để hoàn tất hướng dẫn thao tác bằng cử chỉ"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Hãy vuốt từ mép ngoài cùng bên phải hoặc ngoài cùng bên trái"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"mở rộng <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"thu gọn <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Khoanh tròn để tìm kiếm"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Biểu tượng ứng dụng"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Tên ứng dụng"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Nút đóng"</string>
 </resources>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index 048d675..4d35d39 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"已启用应用建议"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"已停用应用建议"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"预测的应用:<xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"请旋转设备"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"请旋转设备,完成手势导航教程"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"确保从最右侧或最左侧边缘开始滑动"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"展开“<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>”"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"收起“<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>”"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"圈定即搜"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"应用图标"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"应用名称"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"“关闭”按钮"</string>
 </resources>
diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index f1127da..fd11ba1 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"已啟用應用程式建議"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"已停用應用程式建議"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"預測應用程式:<xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"旋轉裝置方向"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"請旋轉裝置方向以完成手勢導覽教學課程"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"請確保從螢幕最右側或最左側邊緣滑動"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"打開<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"收埋<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"一圈即搜"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"應用程式圖示"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"應用程式名稱"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"關閉按鈕"</string>
 </resources>
diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index b524414..729f7fd 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -21,9 +21,9 @@
     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="recent_task_option_desktop" msgid="8280879717125435668">"桌面"</string>
+    <string name="recent_task_option_desktop" msgid="8280879717125435668">"電腦模式"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"移至外接螢幕"</string>
-    <string name="recent_task_desktop" msgid="8081113562549637334">"電腦"</string>
+    <string name="recent_task_desktop" msgid="8081113562549637334">"電腦模式"</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>
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"應用程式建議功能已啟用"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"應用程式建議功能已停用"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"預測的應用程式:<xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"旋轉裝置螢幕方向"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"如要完成手勢操作教學課程,請旋轉裝置螢幕方向"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"請務必從螢幕最右側或最左側滑動"</string>
@@ -140,7 +142,7 @@
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"移到上方/左側"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"移到底部/右側"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{個其他應用程式}other{個其他應用程式}}"</string>
-    <string name="quick_switch_desktop" msgid="8393802056024499749">"電腦"</string>
+    <string name="quick_switch_desktop" msgid="8393802056024499749">"電腦模式"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"「<xliff:g id="APP_NAME_1">%1$s</xliff:g>」和「<xliff:g id="APP_NAME_2">%2$s</xliff:g>」"</string>
     <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"泡泡"</string>
     <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"溢位"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"展開「<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>」"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"收合「<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>」"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"畫圈搜尋"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"應用程式圖示"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"應用程式標題"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"關閉按鈕"</string>
 </resources>
diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index 4340857..5209ac1 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -46,6 +46,8 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Iziphakamiso zohlelo lokusebenza zinikwe amandla"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Iziphakamiso zohlelo lokusebenza zikhutshaziwe"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Uhlelo lokusebenza olubikezelwe: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <!-- no translation found for gesture_tutorial_title (2750751261768388354) -->
+    <skip />
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Zungezisa idivayisi yakho"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Sicela uzungezise idivayisi yakho ukuze uqedele okokufundisa kokufuna ngokuthinta"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Qinisekisa ukuthi uswayipha ukusuka onqenqemeni olukude ngakwesokudla noma olukude ngakwesokunxele"</string>
@@ -152,4 +154,7 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"nweba <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"goqa <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Khethela Ukusesha"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Isithonjana se-app"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Isihloko se-app"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Inkinobho yokuvala"</string>
 </resources>
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index 1f33e08..a530325 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -34,7 +34,6 @@
     <string name="launcher_restore_event_logger_class" translatable="false">com.android.quickstep.LauncherRestoreEventLoggerImpl</string>
     <string name="taskbar_edu_tooltip_controller_class" translatable="false">com.android.launcher3.taskbar.TaskbarEduTooltipController</string>
     <string name="nav_handle_long_press_handler_class" translatable="false"></string>
-    <string name="contextual_search_state_manager_class" translatable="false"></string>
 
     <!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also
          determines how many thumbnails will be fetched in the background. -->
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 493a5b8..b253343 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -106,6 +106,9 @@
     <dimen name="recents_clear_all_outline_radius">24dp</dimen>
     <dimen name="recents_clear_all_outline_padding">2dp</dimen>
 
+    <!-- Recents add desktop button -->
+    <dimen name="add_desktop_button_size">56dp</dimen>
+
     <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
              loading full resolution screenshots. -->
     <dimen name="recents_fast_fling_velocity">600dp</dimen>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 324ea31..d2a7029 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -98,6 +98,9 @@
     <!-- content description for hotseat items -->
     <string name="hotseat_prediction_content_description">Predicted app: <xliff:g id="title" example="Chrome">%1$s</xliff:g></string>
 
+    <!-- Title of the Gesture Navigation Tutorial page [CHAR LIMIT=NONE] -->
+    <string name="gesture_tutorial_title">Gesture Navigation Tutorial</string>
+
     <!-- Title of prompt shown before the gesture navigation tutorial to users who need to rotate their screen. [CHAR LIMIT=100] -->
     <string name="gesture_tutorial_rotation_prompt_title">Rotate your device</string>
     <!-- Prompt shown before the gesture navigation tutorial to users who need to rotate their screen to begin. [CHAR LIMIT=100] -->
@@ -301,10 +304,6 @@
     <string name="taskbar_a11y_shown_with_bubbles_left_title">Taskbar &#38; bubbles left shown</string>
     <!-- Accessibility title for the Taskbar window appearing together with bubble bar on right. [CHAR_LIMIT=30] -->
     <string name="taskbar_a11y_shown_with_bubbles_right_title">Taskbar &#38; bubbles right shown</string>
-    <!-- Accessibility title for the Taskbar window being closed. [CHAR_LIMIT=30] -->
-    <string name="taskbar_a11y_hidden_title">Taskbar hidden</string>
-    <!-- Accessibility title for the Taskbar window being closed together with bubble bar. [CHAR_LIMIT=30] -->
-    <string name="taskbar_a11y_hidden_with_bubbles_title">Taskbar &#38; bubbles hidden</string>
     <!-- Accessibility title for the Taskbar window on phones. [CHAR_LIMIT=NONE] -->
     <string name="taskbar_phone_a11y_title">Navigation bar</string>
     <!-- Text in popup dialog for user to switch between always showing Taskbar or not. [CHAR LIMIT=30] -->
diff --git a/quickstep/src/com/android/launcher3/dagger/Modules.kt b/quickstep/src/com/android/launcher3/dagger/Modules.kt
index 04c1d5e..52be413 100644
--- a/quickstep/src/com/android/launcher3/dagger/Modules.kt
+++ b/quickstep/src/com/android/launcher3/dagger/Modules.kt
@@ -21,9 +21,11 @@
 import com.android.launcher3.util.ApiWrapper
 import com.android.launcher3.util.PluginManagerWrapper
 import com.android.launcher3.util.window.WindowManagerProxy
+import com.android.quickstep.util.GestureExclusionManager
 import com.android.quickstep.util.SystemWindowManagerProxy
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 
 private object Modules {}
 
@@ -42,3 +44,11 @@
     @Binds
     abstract fun bindPluginManagerWrapper(impl: PluginManagerWrapperImpl): PluginManagerWrapper
 }
+
+@Module
+object StaticObjectModule {
+
+    @Provides
+    @JvmStatic
+    fun provideGestureExclusionManager(): GestureExclusionManager = GestureExclusionManager.INSTANCE
+}
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
index 87a82f0..2406fb6 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
@@ -22,6 +22,7 @@
 import android.content.Context
 import android.graphics.Rect
 import android.os.IBinder
+import android.view.Choreographer
 import android.view.SurfaceControl.Transaction
 import android.view.WindowManager.TRANSIT_OPEN
 import android.view.WindowManager.TRANSIT_TO_BACK
@@ -32,6 +33,8 @@
 import android.window.TransitionInfo.Change
 import androidx.core.animation.addListener
 import com.android.app.animation.Interpolators
+import com.android.internal.jank.Cuj
+import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.policy.ScreenDecorationsUtils
 import com.android.quickstep.RemoteRunnable
 import com.android.wm.shell.shared.animation.MinimizeAnimator
@@ -49,8 +52,11 @@
     private val context: Context,
     private val mainExecutor: Executor,
     private val launchType: AppLaunchType,
+    @Cuj.CujType private val cujType: Int,
 ) : RemoteTransitionStub() {
 
+    private val interactionJankMonitor = InteractionJankMonitor.getInstance()
+
     enum class AppLaunchType(
         val boundsAnimationParams: WindowAnimator.BoundsAnimationParams,
         val alphaDurationMs: Long,
@@ -127,7 +133,10 @@
                 duration = launchType.alphaDurationMs
                 interpolator = Interpolators.LINEAR
                 addUpdateListener { animation ->
-                    transaction.setAlpha(change.leash, animation.animatedValue as Float).apply()
+                    transaction
+                        .setAlpha(change.leash, animation.animatedValue as Float)
+                        .setFrameTimeline(Choreographer.getInstance().vsyncId)
+                        .apply()
                 }
             }
         val clipRect = Rect(change.endAbsBounds).apply { offsetTo(0, 0) }
@@ -137,8 +146,14 @@
             ScreenDecorationsUtils.getWindowCornerRadius(context),
         )
         return AnimatorSet().apply {
+            interactionJankMonitor.begin(change.leash, context, context.mainThreadHandler, cujType)
             playTogether(boundsAnimator, alphaAnimator)
-            addListener(onEnd = { animation -> onAnimFinish(animation) })
+            addListener(
+                onEnd = { animation ->
+                    onAnimFinish(animation)
+                    interactionJankMonitor.end(cujType)
+                }
+            )
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
index 6e36305..6cf9b9e 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
@@ -23,6 +23,7 @@
 import android.window.RemoteTransition
 import android.window.TransitionFilter
 import android.window.TransitionFilter.CONTAINER_ORDER_TOP
+import com.android.internal.jank.Cuj
 import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType
 import com.android.launcher3.util.Executors.MAIN_EXECUTOR
 import com.android.quickstep.SystemUiProxy
@@ -45,8 +46,13 @@
         }
         remoteWindowLimitUnminimizeTransition =
             RemoteTransition(
-                DesktopAppLaunchTransition(context, MAIN_EXECUTOR, AppLaunchType.UNMINIMIZE),
-                "DesktopWindowLimitUnminimize"
+                DesktopAppLaunchTransition(
+                    context,
+                    MAIN_EXECUTOR,
+                    AppLaunchType.UNMINIMIZE,
+                    Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_INTENT,
+                ),
+                "DesktopWindowLimitUnminimize",
             )
         systemUiProxy.registerRemoteTransition(
             remoteWindowLimitUnminimizeTransition,
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
index 8b064d3..010a5c8 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
@@ -64,11 +64,16 @@
     }
 
     /** Launch desktop tasks from recents view */
-    fun moveToDesktop(taskContainer: TaskContainer, transitionSource: DesktopModeTransitionSource) {
+    fun moveToDesktop(
+        taskContainer: TaskContainer,
+        transitionSource: DesktopModeTransitionSource,
+        successCallback: Runnable,
+    ) {
         systemUiProxy.moveToDesktop(
             taskContainer.task.key.id,
             transitionSource,
             /* transition = */ null,
+            successCallback,
         )
     }
 
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
index 56945ba..70868c5 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.model.PredictionHelper.getAppTargetFromItemInfo;
-import static com.android.launcher3.model.PredictionHelper.isTrackedForHotseatPrediction;
 import static com.android.launcher3.model.PredictionHelper.wrapAppTargetWithItemLocation;
 
 import android.app.prediction.AppTarget;
@@ -27,9 +26,12 @@
 
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.PredictionHelper;
 import com.android.launcher3.model.data.ItemInfo;
 
 import java.util.ArrayList;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * Model helper for app predictions in workspace
@@ -43,13 +45,18 @@
      */
     public static Bundle convertDataModelToAppTargetBundle(Context context, BgDataModel dataModel) {
         Bundle bundle = new Bundle();
-        ArrayList<AppTargetEvent> events = new ArrayList<>();
-        ArrayList<ItemInfo> workspaceItems = dataModel.getAllWorkspaceItems();
-        for (ItemInfo item : workspaceItems) {
-            AppTarget target = getAppTargetFromItemInfo(context, item);
-            if (target != null && !isTrackedForHotseatPrediction(item)) continue;
-            events.add(wrapAppTargetWithItemLocation(target, AppTargetEvent.ACTION_PIN, item));
-        }
+        ArrayList<AppTargetEvent> events = dataModel.itemsIdMap
+                .stream()
+                .filter(PredictionHelper::isTrackedForHotseatPrediction)
+                .map(item -> {
+                    AppTarget target = getAppTargetFromItemInfo(context, item);
+                    return target != null
+                            ? wrapAppTargetWithItemLocation(target, AppTargetEvent.ACTION_PIN, item)
+                            : null;
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toCollection(ArrayList::new));
+
         ArrayList<AppTarget> currentTargets = new ArrayList<>();
         FixedContainerItems hotseatItems = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
         if (hotseatItems != null) {
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 25e1813..40e8fc2 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -462,7 +462,7 @@
     private Bundle getBundleForWidgetsOnWorkspace(Context context, BgDataModel dataModel) {
         Bundle bundle = new Bundle();
         ArrayList<AppTargetEvent> widgetEvents =
-                dataModel.getAllWorkspaceItems().stream()
+                dataModel.itemsIdMap.stream()
                         .filter(PredictionHelper::isTrackedForWidgetPrediction)
                         .map(item -> {
                             AppTarget target = getAppTargetFromItemInfo(context, item);
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
index 9d9054e..40e1c10 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
 import static com.android.launcher3.Flags.enableTieredWidgetsByDefaultInPicker;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
 
 import static java.util.stream.Collectors.groupingBy;
 import static java.util.stream.Collectors.toMap;
@@ -69,9 +70,11 @@
             @NonNull AllAppsList apps) {
         Predicate<WidgetItem> predictedWidgetsFilter = enableTieredWidgetsByDefaultInPicker()
                 ? dataModel.widgetsModel.getPredictedWidgetsFilter() : null;
-        Set<ComponentKey> widgetsInWorkspace = dataModel.appWidgets.stream().map(
-                widget -> new ComponentKey(widget.providerName, widget.user)).collect(
-                Collectors.toSet());
+        Set<ComponentKey> widgetsInWorkspace = dataModel.itemsIdMap
+                .stream()
+                .filter(WIDGET_FILTER)
+                .map(item -> new ComponentKey(item.getTargetComponent(), item.user))
+                .collect(Collectors.toSet());
 
         // Widgets (excluding shortcuts & already added widgets) that belong to apps eligible for
         // being in predictions.
diff --git a/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java b/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
index a833ccf..b33fd38 100644
--- a/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 
@@ -60,9 +61,9 @@
     }
 
     @Override
-    public void showAppBubble(Intent intent) {
+    public void showAppBubble(Intent intent, UserHandle user) {
         if (intent == null || intent.getPackage() == null) return;
-        SystemUiProxy.INSTANCE.get(this).showAppBubble(intent);
+        SystemUiProxy.INSTANCE.get(this).showAppBubble(intent, user);
     }
 
     /** Callback invoked when a drag is initiated within this context. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index cb16345..3736e6d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -28,10 +28,12 @@
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
 import com.android.launcher3.util.TouchController;
+import com.android.quickstep.RecentsFilterState;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.SingleTask;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -153,6 +155,8 @@
                 // Skip the task reload if the list is not changed.
                 if (!mModel.isTaskListValid(mTaskListChangeId) || !taskIdsToExclude.equals(
                         mExcludedTaskIds)) {
+                    final boolean shouldShowDesktopTasks = mControllers.taskbarDesktopModeController
+                            .shouldShowDesktopTasksInTaskbar();
                     mExcludedTaskIds = taskIdsToExclude;
                     mTaskListChangeId = mModel.getTasks((tasks) -> {
                         processLoadedTasks(tasks, taskIdsToExclude);
@@ -162,7 +166,8 @@
                                 currentFocusIndexOverride,
                                 mHasDesktopTask,
                                 mWasDesktopTaskFilteredOut);
-                    });
+                    }, shouldShowDesktopTasks ? RecentsFilterState.EMPTY_FILTER
+                            : RecentsFilterState.getEmptyDesktopTaskFilter());
                 }
 
                 mQuickSwitchViewController.updateLayoutForSurface(wasOpenedFromTaskbar,
@@ -188,8 +193,8 @@
         mQuickSwitchViewController = new KeyboardQuickSwitchViewController(
                 mControllers, mOverlayContext, keyboardQuickSwitchView, mControllerCallbacks);
 
-        final boolean onDesktop = mControllers.taskbarDesktopModeController
-                .getAreDesktopTasksVisibleAndNotInOverview();
+        final boolean shouldShowDesktopTasks = mControllers.taskbarDesktopModeController
+                .shouldShowDesktopTasksInTaskbar();
 
         if (mModel.isTaskListValid(mTaskListChangeId)
                 && taskIdsToExclude.equals(mExcludedTaskIds)) {
@@ -201,7 +206,7 @@
                     /* updateTasks= */ false,
                     currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
                             ? 0 : currentFocusedIndex,
-                    onDesktop,
+                    shouldShowDesktopTasks,
                     mHasDesktopTask,
                     mWasDesktopTaskFilteredOut,
                     wasOpenedFromTaskbar);
@@ -219,11 +224,12 @@
                     /* updateTasks= */ true,
                     currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
                             ? 0 : currentFocusedIndex,
-                    onDesktop,
+                    shouldShowDesktopTasks,
                     mHasDesktopTask,
                     mWasDesktopTaskFilteredOut,
                     wasOpenedFromTaskbar);
-        });
+        }, shouldShowDesktopTasks ? RecentsFilterState.EMPTY_FILTER
+                : RecentsFilterState.getEmptyDesktopTaskFilter());
     }
 
     private boolean shouldExcludeTask(GroupTask task, Set<Integer> taskIdsToExclude) {
@@ -233,7 +239,7 @@
     private void processLoadedTasks(List<GroupTask> tasks, Set<Integer> taskIdsToExclude) {
         mHasDesktopTask = false;
         mWasDesktopTaskFilteredOut = false;
-        if (mControllers.taskbarDesktopModeController.getAreDesktopTasksVisibleAndNotInOverview()) {
+        if (mControllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()) {
             processLoadedTasksOnDesktop(tasks, taskIdsToExclude);
         } else {
             processLoadedTasksOutsideDesktop(tasks, taskIdsToExclude);
@@ -270,7 +276,7 @@
 
         if (desktopTask != null) {
             mTasks = desktopTask.getTasks().stream()
-                    .map(GroupTask::new)
+                    .map(SingleTask::new)
                     .filter(task -> !shouldExcludeTask(task, taskIdsToExclude))
                     .collect(Collectors.toList());
             // All other tasks, apart from the grouped desktop task, are hidden
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index cb811d6..8cb43d2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -38,7 +38,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.desktop.DesktopAppLaunchTransition;
-import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
 import com.android.launcher3.util.DisplayController;
@@ -50,6 +49,7 @@
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
+import com.android.wm.shell.shared.desktopmode.DesktopTaskToFrontReason;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -286,13 +286,19 @@
         ) {
             // This app is being unminimized - use our own transition runner.
             remoteTransition = new RemoteTransition(
-                    new DesktopAppLaunchTransition(context, MAIN_EXECUTOR, UNMINIMIZE),
+                    new DesktopAppLaunchTransition(
+                            context,
+                            MAIN_EXECUTOR,
+                            UNMINIMIZE,
+                            Cuj.CUJ_DESKTOP_MODE_KEYBOARD_QUICK_SWITCH_APP_LAUNCH
+                    ),
                     "DesktopKeyboardQuickSwitchUnminimize");
         }
         mControllers.taskbarActivityContext.handleGroupTaskLaunch(
                 task,
                 remoteTransition,
                 mOnDesktop,
+                DesktopTaskToFrontReason.ALT_TAB,
                 onStartCallback,
                 onFinishCallback);
         return -1;
diff --git a/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt b/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
index 75ce7c3..bfd93dd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
@@ -27,7 +27,6 @@
 import com.android.launcher3.popup.SystemShortcut
 import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext
-import com.android.launcher3.util.Themes
 import com.android.launcher3.util.TouchController
 import com.android.launcher3.views.ActivityContext
 import com.android.quickstep.RecentsModel
@@ -35,9 +34,8 @@
 import com.android.quickstep.util.DesktopTask
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.wm.shell.shared.desktopmode.DesktopTaskToFrontReason
 import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer
-import java.util.Collections
-import java.util.function.Predicate
 
 /**
  * A single menu item shortcut to execute displaying open instances of an app. Default interaction
@@ -72,7 +70,7 @@
             val packageDesktopTasks =
                 (desktopTask?.tasks ?: emptyList()).filter(isTargetPackageTask)
             val nonDesktopPackageTasks =
-                tasks.filter { isTargetPackageTask(it.task1) }.map { it.task1 }
+                tasks.flatMap { it.tasks }.filter { isTargetPackageTask(it) }
 
             // Add tasks from the fetched tasks, deduplicating by task ID
             val packageTasks =
@@ -120,7 +118,12 @@
             ({ taskId: Int? ->
                 taskbarShortcutAllWindowsView.animateClose()
                 if (taskId != null) {
-                    SystemUiProxy.INSTANCE.get(target).showDesktopApp(taskId, null)
+                    SystemUiProxy.INSTANCE.get(target)
+                        .showDesktopApp(
+                            taskId,
+                            /* transition= */ null,
+                            DesktopTaskToFrontReason.TASKBAR_MANAGE_WINDOW,
+                        )
                 }
             })
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 1144ac5..2e48910 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -40,9 +40,10 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISMISS_IME;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
@@ -129,9 +130,18 @@
 
     private final Rect mTempRect = new Rect();
 
-    private static final int FLAG_SWITCHER_SHOWING = 1 << 0;
+    /** Whether the IME Switcher button is visible. */
+    private static final int FLAG_IME_SWITCHER_BUTTON_VISIBLE = 1 << 0;
+    /** Whether the IME is visible. */
     private static final int FLAG_IME_VISIBLE = 1 << 1;
-    private static final int FLAG_ROTATION_BUTTON_VISIBLE = 1 << 2;
+    /**
+     * The back button is visually adjusted to indicate that it will dismiss the IME when pressed.
+     * This only takes effect while the IME is visible. By default, it is set while the IME is
+     * visible, but may be overridden by the
+     * {@link android.inputmethodservice.InputMethodService.BackDispositionMode backDispositionMode}
+     * set by the IME.
+     */
+    private static final int FLAG_BACK_DISMISS_IME = 1 << 2;
     private static final int FLAG_A11Y_VISIBLE = 1 << 3;
     private static final int FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE = 1 << 4;
     private static final int FLAG_KEYGUARD_VISIBLE = 1 << 5;
@@ -273,13 +283,14 @@
     }
 
     protected void setupController() {
-        boolean isThreeButtonNav = mContext.isThreeButtonNav();
+        final boolean isThreeButtonNav = mContext.isThreeButtonNav();
+        final boolean isPhoneMode = mContext.isPhoneMode();
         DeviceProfile deviceProfile = mContext.getDeviceProfile();
         Resources resources = mContext.getResources();
 
         int setupSize = mControllers.taskbarActivityContext.getSetupWindowSize();
-        Point p = DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources,
-                mContext.isPhoneMode(), mContext.isGestureNav());
+        Point p = DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources, isPhoneMode,
+                mContext.isGestureNav());
         ViewGroup.LayoutParams navButtonsViewLayoutParams = mNavButtonsView.getLayoutParams();
         navButtonsViewLayoutParams.width = p.x;
         if (!mContext.isUserSetupComplete()) {
@@ -300,9 +311,10 @@
             mImeSwitcherButton = addButton(switcherResId, BUTTON_IME_SWITCH,
                     isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
                     mControllers.navButtonController, R.id.ime_switcher);
+            // A11y and IME Switcher buttons overlap on phone mode, show only a11y if both visible.
             mPropertyHolders.add(new StatePropertyHolder(mImeSwitcherButton,
-                    flags -> ((flags & FLAG_SWITCHER_SHOWING) != 0)
-                            && ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)));
+                    flags -> (flags & FLAG_IME_SWITCHER_BUTTON_VISIBLE) != 0
+                            && !(isPhoneMode && (flags & FLAG_A11Y_VISIBLE) != 0)));
         }
 
         mPropertyHolders.add(new StatePropertyHolder(
@@ -316,7 +328,7 @@
                         .get(ALPHA_INDEX_SMALL_SCREEN),
                 flags -> (flags & FLAG_SMALL_SCREEN) == 0));
 
-        if (!mContext.isPhoneMode()) {
+        if (!isPhoneMode) {
             mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController
                     .getKeyguardBgTaskbar(), flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0));
         }
@@ -331,10 +343,10 @@
 
         // Make sure to remove nav bar buttons translation when any of the following occur:
         // - Notification shade is expanded
-        // - IME is showing (add separate translation for IME)
+        // - IME is visible (add separate translation for IME)
         // - VoiceInteractionWindow (assistant) is showing
         // - Keyboard shortcuts helper is showing
-        if (!mContext.isPhoneMode()) {
+        if (!isPhoneMode) {
             int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE
                     | FLAG_VOICE_INTERACTION_WINDOW_SHOWING | FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING;
             mPropertyHolders.add(new StatePropertyHolder(mNavButtonInAppDisplayProgressForSysui,
@@ -362,9 +374,9 @@
             initButtons(mNavButtonContainer, mEndContextualContainer,
                     mControllers.navButtonController);
             updateButtonLayoutSpacing();
-            updateStateForFlag(FLAG_SMALL_SCREEN, mContext.isPhoneMode());
+            updateStateForFlag(FLAG_SMALL_SCREEN, isPhoneMode);
 
-            if (!mContext.isPhoneMode()) {
+            if (!isPhoneMode) {
                 mPropertyHolders.add(new StatePropertyHolder(
                         mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
                         flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));
@@ -391,7 +403,7 @@
                 R.bool.floating_rotation_button_position_left);
         mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
                 mRotationButtonListener);
-        if (mContext.isPhoneMode()) {
+        if (isPhoneMode) {
             mTaskbarTransitions.init();
         }
 
@@ -447,7 +459,7 @@
                     flags -> (flags & FLAG_IME_VISIBLE) == 0));
         }
         mPropertyHolders.add(new StatePropertyHolder(mBackButton,
-                flags -> (flags & FLAG_IME_VISIBLE) != 0,
+                flags -> (flags & FLAG_BACK_DISMISS_IME) != 0,
                 ROTATION_DRAWABLE_PERCENT, 1f, 0f));
         // Translate back button to be at end/start of other buttons for keyguard (only after SUW
         // since it is laid to align with SUW actions while in that state)
@@ -496,8 +508,7 @@
                 endContainer, navButtonController, R.id.accessibility_button,
                 R.layout.taskbar_contextual_button);
         mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
-                flags -> (flags & FLAG_A11Y_VISIBLE) != 0
-                        && (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));
+                flags -> (flags & FLAG_A11Y_VISIBLE) != 0));
 
         mSpace = new Space(mNavButtonsView.getContext());
         mSpace.setOnClickListener(view -> navButtonController.onButtonClick(BUTTON_SPACE, view));
@@ -507,8 +518,10 @@
 
     private void parseSystemUiFlags(@SystemUiStateFlags long sysUiStateFlags) {
         mSysuiStateFlags = sysUiStateFlags;
-        boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
-        boolean isImeSwitcherShowing = (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0;
+        boolean isImeSwitcherButtonVisible =
+                (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE) != 0;
+        boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_VISIBLE) != 0;
+        boolean isBackDismissIme = (sysUiStateFlags & SYSUI_STATE_BACK_DISMISS_IME) != 0;
         boolean a11yVisible = (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
         boolean isHomeDisabled = (sysUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
         boolean isRecentsDisabled = (sysUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
@@ -522,9 +535,9 @@
         boolean isKeyboardShortcutHelperShowing =
                 (sysUiStateFlags & SYSUI_STATE_SHORTCUT_HELPER_SHOWING) != 0;
 
-        // TODO(b/202218289) we're getting IME as not visible on lockscreen from system
+        updateStateForFlag(FLAG_IME_SWITCHER_BUTTON_VISIBLE, isImeSwitcherButtonVisible);
         updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
-        updateStateForFlag(FLAG_SWITCHER_SHOWING, isImeSwitcherShowing);
+        updateStateForFlag(FLAG_BACK_DISMISS_IME, isBackDismissIme);
         updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible);
         updateStateForFlag(FLAG_DISABLE_HOME, isHomeDisabled);
         updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled);
@@ -1226,9 +1239,10 @@
 
     private static String getStateString(int flags) {
         StringJoiner str = new StringJoiner("|");
-        appendFlag(str, flags, FLAG_SWITCHER_SHOWING, "FLAG_SWITCHER_SHOWING");
+        appendFlag(str, flags, FLAG_IME_SWITCHER_BUTTON_VISIBLE,
+                "FLAG_IME_SWITCHER_BUTTON_VISIBLE");
         appendFlag(str, flags, FLAG_IME_VISIBLE, "FLAG_IME_VISIBLE");
-        appendFlag(str, flags, FLAG_ROTATION_BUTTON_VISIBLE, "FLAG_ROTATION_BUTTON_VISIBLE");
+        appendFlag(str, flags, FLAG_BACK_DISMISS_IME, "FLAG_BACK_DISMISS_IME");
         appendFlag(str, flags, FLAG_A11Y_VISIBLE, "FLAG_A11Y_VISIBLE");
         appendFlag(str, flags, FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE,
                 "FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE");
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 2745129..a109a40 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -43,6 +43,7 @@
 import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
+import static com.android.window.flags.Flags.enableStartLaunchTransitionFromTaskbarBugfix;
 import static com.android.wm.shell.Flags.enableTinyTaskbar;
 
 import static java.lang.invoke.MethodHandles.Lookup.PROTECTED;
@@ -59,7 +60,6 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
-import android.os.Bundle;
 import android.os.IRemoteCallback;
 import android.os.Process;
 import android.os.Trace;
@@ -82,8 +82,10 @@
 import androidx.core.graphics.Insets;
 import androidx.core.view.WindowInsetsCompat;
 
+import com.android.internal.jank.Cuj;
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.BubbleTextView.RunningAppState;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -164,6 +166,7 @@
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 import com.android.systemui.unfold.updates.RotationChangeProvider;
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
+import com.android.wm.shell.shared.desktopmode.DesktopTaskToFrontReason;
 
 import java.io.PrintWriter;
 import java.util.Collections;
@@ -230,6 +233,7 @@
     private DeviceProfile mPersistentTaskbarDeviceProfile;
 
     private final LauncherPrefs mLauncherPrefs;
+    private final SystemUiProxy mSysUiProxy;
 
     private TaskbarFeatureEvaluator mTaskbarFeatureEvaluator;
 
@@ -239,10 +243,11 @@
             @Nullable Context navigationBarPanelContext, DeviceProfile launcherDp,
             TaskbarNavButtonController buttonController,
             ScopedUnfoldTransitionProgressProvider unfoldTransitionProgressProvider,
-            boolean isPrimaryDisplay) {
+            boolean isPrimaryDisplay, SystemUiProxy sysUiProxy) {
         super(windowContext);
         mIsPrimaryDisplay = isPrimaryDisplay;
         mNavigationBarPanelContext = navigationBarPanelContext;
+        mSysUiProxy = sysUiProxy;
         applyDeviceProfile(launcherDp);
         final Resources resources = getResources();
         mTaskbarFeatureEvaluator = TaskbarFeatureEvaluator.getInstance(this);
@@ -363,7 +368,8 @@
                 new KeyboardQuickSwitchController(),
                 new TaskbarPinningController(this),
                 bubbleControllersOptional,
-                new TaskbarDesktopModeController(DesktopVisibilityController.INSTANCE.get(this)));
+                new TaskbarDesktopModeController(this,
+                        DesktopVisibilityController.INSTANCE.get(this)));
 
         mLauncherPrefs = LauncherPrefs.get(this);
     }
@@ -886,32 +892,10 @@
         return makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED);
     }
 
-    private ActivityOptionsWrapper getActivityLaunchDesktopOptions(ItemInfo info) {
-        if (!DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue()
-                && !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue()) {
-            return null;
-        }
-        if (!areDesktopTasksVisible()) {
-            return null;
-        }
-        BubbleTextView.RunningAppState appState =
-                mControllers.taskbarRecentAppsController.getDesktopItemState(info);
-        AppLaunchType launchType = null;
-        switch (appState) {
-            case RUNNING:
-                return null;
-            case MINIMIZED:
-                launchType = AppLaunchType.UNMINIMIZE;
-                break;
-            case NOT_RUNNING:
-                launchType = AppLaunchType.LAUNCH;
-                break;
-        }
+    private ActivityOptionsWrapper getActivityLaunchDesktopOptions() {
         ActivityOptions options = ActivityOptions.makeRemoteTransition(
-                new RemoteTransition(
-                        new DesktopAppLaunchTransition(
-                                /* context= */ this, getMainExecutor(), launchType),
-                        "TaskbarDesktopLaunch"));
+                createDesktopAppLaunchRemoteTransition(
+                        AppLaunchType.LAUNCH, Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON));
         return new ActivityOptionsWrapper(options, new RunnableList());
     }
 
@@ -1313,7 +1297,9 @@
         if (tag instanceof GroupTask groupTask) {
             RemoteTransition remoteTransition =
                     (areDesktopTasksVisible() && canUnminimizeDesktopTask(groupTask.task1.key.id))
-                            ? createUnminimizeRemoteTransition() : null;
+                            ? createDesktopAppLaunchRemoteTransition(AppLaunchType.UNMINIMIZE,
+                                    Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
+                            : null;
             if (areDesktopTasksVisible() && mControllers.uiController.isInOverviewUi()) {
                 RunnableList runnableList = recents.launchRunningDesktopTaskView();
                 // Wrapping it in runnable so we post after DW is ready for the app
@@ -1321,10 +1307,12 @@
                 if (runnableList != null) {
                     runnableList.add(() -> UI_HELPER_EXECUTOR.execute(
                             () -> handleGroupTaskLaunch(groupTask, remoteTransition,
-                                    areDesktopTasksVisible())));
+                                    areDesktopTasksVisible(),
+                                    DesktopTaskToFrontReason.TASKBAR_TAP)));
                 }
             } else {
-                handleGroupTaskLaunch(groupTask, remoteTransition, areDesktopTasksVisible());
+                handleGroupTaskLaunch(groupTask, remoteTransition, areDesktopTasksVisible(),
+                        DesktopTaskToFrontReason.TASKBAR_TAP);
             }
             mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
         } else if (tag instanceof FolderInfo) {
@@ -1344,14 +1332,17 @@
             }
         } else if (tag instanceof TaskItemInfo info) {
             RemoteTransition remoteTransition = canUnminimizeDesktopTask(info.getTaskId())
-                    ? createUnminimizeRemoteTransition() : null;
+                    ? createDesktopAppLaunchRemoteTransition(
+                            AppLaunchType.UNMINIMIZE, Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
+                    : null;
 
             TaskView taskView = null;
             if (recents != null) {
                 taskView = recents.getTaskViewByTaskId(info.getTaskId());
             }
 
-            if (areDesktopTasksVisible() && taskView != null) {
+            if (areDesktopTasksVisible() && taskView != null
+                    && mControllers.uiController.isInOverviewUi()) {
                 RunnableList runnableList = taskView.launchWithAnimation();
                 if (runnableList != null) {
                     runnableList.add(() ->
@@ -1360,12 +1351,14 @@
                             // task will show.
                             UI_HELPER_EXECUTOR.execute(() ->
                                     SystemUiProxy.INSTANCE.get(this).showDesktopApp(
-                                            info.getTaskId(), remoteTransition)));
+                                            info.getTaskId(), remoteTransition,
+                                            DesktopTaskToFrontReason.TASKBAR_TAP)));
                 }
             } else {
                 UI_HELPER_EXECUTOR.execute(() ->
                         SystemUiProxy.INSTANCE.get(this).showDesktopApp(
-                                info.getTaskId(), remoteTransition));
+                                info.getTaskId(), remoteTransition,
+                                DesktopTaskToFrontReason.TASKBAR_TAP));
             }
 
             mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(
@@ -1458,8 +1451,9 @@
     public void handleGroupTaskLaunch(
             GroupTask task,
             @Nullable RemoteTransition remoteTransition,
-            boolean onDesktop) {
-        handleGroupTaskLaunch(task, remoteTransition, onDesktop,
+            boolean onDesktop,
+            DesktopTaskToFrontReason toFrontReason) {
+        handleGroupTaskLaunch(task, remoteTransition, onDesktop, toFrontReason,
                 /* onStartCallback= */ null, /* onFinishCallback= */ null);
     }
 
@@ -1478,6 +1472,7 @@
             GroupTask task,
             @Nullable RemoteTransition remoteTransition,
             boolean onDesktop,
+            DesktopTaskToFrontReason toFrontReason,
             @Nullable Runnable onStartCallback,
             @Nullable Runnable onFinishCallback) {
         if (task instanceof DesktopTask) {
@@ -1492,8 +1487,8 @@
                 if (onStartCallback != null) {
                     onStartCallback.run();
                 }
-                SystemUiProxy.INSTANCE.get(this).showDesktopApp(
-                        task.task1.key.id, useRemoteTransition ? remoteTransition : null);
+                SystemUiProxy.INSTANCE.get(this).showDesktopApp(task.task1.key.id,
+                        useRemoteTransition ? remoteTransition : null, toFrontReason);
                 if (onFinishCallback != null) {
                     onFinishCallback.run();
                 }
@@ -1518,16 +1513,22 @@
     public boolean canUnminimizeDesktopTask(int taskId) {
         BubbleTextView.RunningAppState runningAppState =
                 mControllers.taskbarRecentAppsController.getRunningAppState(taskId);
-        return runningAppState == BubbleTextView.RunningAppState.MINIMIZED
+        return runningAppState == RunningAppState.MINIMIZED
                 && (DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS.isTrue()
                     || DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX.isTrue()
                     );
     }
 
-    private RemoteTransition createUnminimizeRemoteTransition() {
+    private RemoteTransition createDesktopAppLaunchRemoteTransition(
+            AppLaunchType appLaunchType, @Cuj.CujType int cujType) {
         return new RemoteTransition(
-                new DesktopAppLaunchTransition(this, getMainExecutor(), AppLaunchType.UNMINIMIZE),
-                "TaskbarDesktopUnminimize");
+                new DesktopAppLaunchTransition(
+                        this,
+                        getMainExecutor(),
+                        appLaunchType,
+                        cujType
+                ),
+                "TaskbarDesktopAppLaunch");
     }
 
     /**
@@ -1645,12 +1646,12 @@
                         intent.getComponent(), info.user, intent.getSourceBounds(), null);
                 return;
             }
+            int displayId = getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId();
             // TODO(b/216683257): Use startActivityForResult for search results that require it.
             if (taskInRecents != null) {
                 // Re launch instance from recents
                 ActivityOptionsWrapper opts = getActivityLaunchOptions(null, info);
-                opts.options.setLaunchDisplayId(
-                        getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
+                opts.options.setLaunchDisplayId(displayId);
                 if (ActivityManagerWrapper.getInstance()
                         .startActivityFromRecents(taskInRecents.key, opts.options)) {
                     mControllers.uiController.getRecentsView()
@@ -1658,12 +1659,12 @@
                     return;
                 }
             }
-            ActivityOptionsWrapper opts = null;
-            if (areDesktopTasksVisible()) {
-                opts = getActivityLaunchDesktopOptions(info);
+            if (areDesktopTasksVisible()
+                    && DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue()) {
+                launchDesktopApp(intent, info, displayId);
+            } else {
+                startActivity(intent, null);
             }
-            Bundle optionsBundle = opts == null ? null : opts.options.toBundle();
-            startActivity(intent, optionsBundle);
         } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT)
                     .show();
@@ -1671,6 +1672,31 @@
         }
     }
 
+    private void launchDesktopApp(Intent intent, ItemInfo info, int displayId) {
+        TaskbarRecentAppsController.TaskState taskState =
+                mControllers.taskbarRecentAppsController.getDesktopItemState(info);
+        RunningAppState appState = taskState.getRunningAppState();
+        if (appState == RunningAppState.RUNNING || appState == RunningAppState.MINIMIZED) {
+            // We only need a custom animation (a RemoteTransition) if the task is minimized - if
+            // it's already visible it will just be brought forward.
+            RemoteTransition remoteTransition = (appState == RunningAppState.MINIMIZED)
+                    ? createDesktopAppLaunchRemoteTransition(
+                            AppLaunchType.UNMINIMIZE, Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
+                    : null;
+            UI_HELPER_EXECUTOR.execute(() ->
+                    SystemUiProxy.INSTANCE.get(this).showDesktopApp(taskState.getTaskId(),
+                            remoteTransition, DesktopTaskToFrontReason.TASKBAR_TAP));
+            return;
+        }
+        // There is no task associated with this launch - launch a new task through an intent
+        ActivityOptionsWrapper opts = getActivityLaunchDesktopOptions();
+        if (enableStartLaunchTransitionFromTaskbarBugfix()) {
+            mSysUiProxy.startLaunchIntentTransition(intent, opts.options.toBundle(), displayId);
+        } else {
+            startActivity(intent, opts.options.toBundle());
+        }
+    }
+
     /** Expands a folder icon when it is clicked */
     private void expandFolder(FolderIcon folderIcon) {
         Folder folder = folderIcon.getFolder();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
index a7c7381..8806bc6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
@@ -16,13 +16,16 @@
 
 package com.android.launcher3.taskbar
 
+import android.content.Context
 import com.android.launcher3.statehandlers.DesktopVisibilityController
 import com.android.launcher3.statehandlers.DesktopVisibilityController.TaskbarDesktopModeListener
 import com.android.launcher3.taskbar.TaskbarBackgroundRenderer.Companion.MAX_ROUNDNESS
+import com.android.launcher3.util.DisplayController
 
 /** Handles Taskbar in Desktop Windowing mode. */
 class TaskbarDesktopModeController(
-    private val desktopVisibilityController: DesktopVisibilityController
+    private val context: Context,
+    private val desktopVisibilityController: DesktopVisibilityController,
 ) : TaskbarDesktopModeListener {
     private lateinit var taskbarControllers: TaskbarControllers
     private lateinit var taskbarSharedState: TaskbarSharedState
@@ -45,6 +48,12 @@
         taskbarControllers.taskbarCornerRoundness.animateToValue(cornerRadius).start()
     }
 
+    fun shouldShowDesktopTasksInTaskbar(): Boolean {
+        return desktopVisibilityController.areDesktopTasksVisible() ||
+            DisplayController.showLockedTaskbarOnHome(context) &&
+                taskbarControllers.taskbarStashController.isOnHome
+    }
+
     fun getTaskbarCornerRoundness(doesAnyTaskRequireTaskbarRounding: Boolean): Float {
         return if (doesAnyTaskRequireTaskbarRounding) {
             MAX_ROUNDNESS
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
index 3bff31f..b7000db 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
@@ -108,7 +108,7 @@
             revealHoverToolTip();
             mActivity.setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, true);
         }
-        return true;
+        return false;
     }
 
     private void revealHoverToolTip() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 7c6c7ac..ebd4ee5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -89,6 +89,8 @@
 public class TaskbarManager {
     private static final String TAG = "TaskbarManager";
     private static final boolean DEBUG = false;
+    // TODO(b/382378283) remove all logs with this tag
+    public static final String NULL_TASKBAR_ROOT_LAYOUT_TAG = "b/382378283";
 
     /**
      * All the configurations which do not initiate taskbar recreation.
@@ -151,6 +153,20 @@
     private class RecreationListener implements DisplayController.DisplayInfoChangeListener {
         @Override
         public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+
+            if ((flags & CHANGE_DENSITY) != 0) {
+                Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "Display density changed");
+            }
+            if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
+                Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "Navigation mode changed");
+            }
+            if ((flags & CHANGE_DESKTOP_MODE) != 0) {
+                Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "Desktop mode changed");
+            }
+            if ((flags & CHANGE_TASKBAR_PINNING) != 0) {
+                Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "Taskbar pinning changed");
+            }
+
             if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE | CHANGE_DESKTOP_MODE
                     | CHANGE_TASKBAR_PINNING)) != 0) {
                 recreateTaskbar();
@@ -345,13 +361,13 @@
 
     private void destroyTaskbarForDisplay(int displayId) {
         TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
-        debugWhyTaskbarNotDestroyed(
-                "destroyTaskbarForDisplay: " + taskbar + " displayId=" + displayId);
+        debugWhyTaskbarNotDestroyed("destroyTaskbarForDisplay: " + taskbar, displayId);
         if (taskbar != null) {
             taskbar.onDestroy();
             // remove all defaults that we store
             removeTaskbarFromMap(displayId);
         }
+        // make this display-specific
         DeviceProfile dp = mUserUnlocked ?
                 LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext) : null;
         if (dp == null || !isTaskbarEnabled(dp)) {
@@ -506,6 +522,7 @@
     private void recreateTaskbarForDisplay(int displayId) {
         Trace.beginSection("recreateTaskbar");
         try {
+            // TODO: make this code display specific
             DeviceProfile dp = mUserUnlocked ?
                     LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext) : null;
 
@@ -547,9 +564,14 @@
             if (enableTaskbarNoRecreate()) {
                 addTaskbarRootViewToWindow(displayId);
                 FrameLayout taskbarRootLayout = getTaskbarRootLayoutForDisplay(displayId);
-                taskbarRootLayout.removeAllViews();
-                taskbarRootLayout.addView(taskbar.getDragLayer());
-                taskbar.notifyUpdateLayoutParams();
+                if (taskbarRootLayout != null) {
+                    taskbarRootLayout.removeAllViews();
+                    taskbarRootLayout.addView(taskbar.getDragLayer());
+                    taskbar.notifyUpdateLayoutParams();
+                } else {
+                    Log.e(NULL_TASKBAR_ROOT_LAYOUT_TAG,
+                            "taskbarRootLayout is null for displayId=" + displayId);
+                }
             }
         } finally {
             Trace.endSection();
@@ -687,6 +709,25 @@
         }
     }
 
+    /**
+     * Signal from SysUI indicating that a non-mirroring display was just connected to the
+     * primary device.
+     */
+    public void onDisplayReady(int displayId) {
+    }
+
+    /**
+     * Signal from SysUI indicating that a previously connected non-mirroring display was just
+     * removed from the primary device.
+     */
+    public void onDisplayRemoved(int displayId) {
+    }
+
+    /**
+     * Signal from SysUI indicating that system decorations should be removed from the display.
+     */
+    public void onDisplayRemoveSystemDecorations(int displayId) {}
+
     private void removeActivityCallbacksAndListeners() {
         if (mActivity != null) {
             mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
@@ -730,17 +771,25 @@
 
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarManager:");
-        TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
-        if (taskbar == null) {
-            pw.println(prefix + "\tTaskbarActivityContext: null");
-        } else {
-            taskbar.dumpLogs(prefix + "\t", pw);
+        // iterate through taskbars and do the dump for each
+        for (int i = 0; i < mTaskbars.size(); i++) {
+            int displayId = mTaskbars.keyAt(i);
+            TaskbarActivityContext taskbar = mTaskbars.get(i);
+            pw.println(prefix + "\tTaskbar at display " + displayId + ":");
+            if (taskbar == null) {
+                pw.println(prefix + "\t\tTaskbarActivityContext: null");
+            } else {
+                taskbar.dumpLogs(prefix + "\t\t", pw);
+            }
         }
+
     }
 
     private void addTaskbarRootViewToWindow(int displayId) {
         TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
         if (!enableTaskbarNoRecreate() || taskbar == null) {
+            Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG,
+                    "addTaskbarRootViewToWindow - taskbar null | displayId=" + displayId);
             return;
         }
 
@@ -748,6 +797,10 @@
             mWindowManager.addView(getTaskbarRootLayoutForDisplay(displayId),
                     taskbar.getWindowLayoutParams());
             mAddedRootLayouts.put(displayId, true);
+        } else {
+            Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG,
+                    "addTaskbarRootViewToWindow - root layout already added | displayId="
+                            + displayId);
         }
     }
 
@@ -793,7 +846,8 @@
     private TaskbarActivityContext createTaskbarActivityContext(DeviceProfile dp, int displayId) {
         TaskbarActivityContext newTaskbar = new TaskbarActivityContext(mWindowContext,
                 mNavigationBarPanelContext, dp, mDefaultNavButtonController,
-                mUnfoldProgressProvider, isDefaultDisplay(displayId));
+                mUnfoldProgressProvider, isDefaultDisplay(displayId),
+                SystemUiProxy.INSTANCE.get(mWindowContext));
 
         addTaskbarToMap(displayId, newTaskbar);
         return newTaskbar;
@@ -839,6 +893,7 @@
             }
         };
         addTaskbarRootLayoutToMap(displayId, newTaskbarRootLayout);
+        Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "created new root layout - displayId=" + displayId);
     }
 
     private boolean isDefaultDisplay(int displayId) {
@@ -852,7 +907,14 @@
      * @return The taskbar root layout {@link FrameLayout} for a given display or {@code null}.
      */
     private FrameLayout getTaskbarRootLayoutForDisplay(int displayId) {
-        return mRootLayouts.get(displayId);
+        FrameLayout frameLayout = mRootLayouts.get(displayId);
+        if (frameLayout != null) {
+            return frameLayout;
+        } else {
+            Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG,
+                    "getTaskbarRootLayoutForDisplay == null | displayId=" + displayId);
+            return null;
+        }
     }
 
     /**
@@ -865,6 +927,8 @@
         if (!mRootLayouts.contains(displayId) && rootLayout != null) {
             mRootLayouts.put(displayId, rootLayout);
         }
+
+        Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "mRootLayouts.size()=" + mRootLayouts.size());
     }
 
     /**
@@ -877,6 +941,8 @@
             mAddedRootLayouts.delete(displayId);
             mRootLayouts.delete(displayId);
         }
+
+        Log.d(NULL_TASKBAR_ROOT_LAYOUT_TAG, "mRootLayouts.size()=" + mRootLayouts.size());
     }
 
     private int getDefaultDisplayId() {
@@ -885,11 +951,17 @@
 
     /** Temp logs for b/254119092. */
     public void debugWhyTaskbarNotDestroyed(String debugReason) {
+        debugWhyTaskbarNotDestroyed(debugReason, getDefaultDisplayId());
+    }
+
+    /** Temp logs for b/254119092. */
+    public void debugWhyTaskbarNotDestroyed(String debugReason, int displayId) {
         StringJoiner log = new StringJoiner("\n");
-        log.add(debugReason);
+        log.add(debugReason  + " displayId=" + displayId);
 
         boolean activityTaskbarPresent = mActivity != null
                 && mActivity.getDeviceProfile().isTaskbarPresent;
+        // TODO: make this display specific
         boolean contextTaskbarPresent = mUserUnlocked && LauncherAppState.getIDP(mWindowContext)
                 .getDeviceProfile(mWindowContext).isTaskbarPresent;
         if (activityTaskbarPresent == contextTaskbarPresent) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
index 0ed6669..017a12c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
@@ -39,7 +39,6 @@
 import androidx.core.graphics.ColorUtils;
 
 import com.android.app.animation.Interpolators;
-import com.android.launcher3.R;
 import com.android.launcher3.Reorderable;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.IconNormalizer;
@@ -243,7 +242,9 @@
     private void init() {
         mIsRtlLayout = Utilities.isRtl(getResources());
         mItemBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mItemBackgroundColor = getContext().getColor(R.color.taskbar_background);
+        mItemBackgroundColor = getContext().getColor(Utilities.isDarkTheme(getContext())
+                ? com.android.internal.R.color.materialColorSurface
+                : com.android.internal.R.color.materialColorInverseOnSurface);
         mLeaveBehindColor = Themes.getAttrColor(getContext(), android.R.attr.textColorTertiary);
 
         setWillNotDraw(false);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index e704691..feb9b33 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -56,6 +56,7 @@
 import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.LogUtils;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 
 import java.io.PrintWriter;
@@ -219,7 +220,7 @@
                 .getAreDesktopTasksVisibleAndNotInOverview()) {
             shortcuts.addAll(mControllers.uiController.getSplitMenuOptions().toList());
         }
-        if (com.android.wm.shell.Flags.enableBubbleAnything()) {
+        if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
             shortcuts.add(BUBBLE);
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index a059b22..6047999 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -19,15 +19,18 @@
 import android.window.DesktopModeFlags
 import androidx.annotation.VisibleForTesting
 import com.android.launcher3.BubbleTextView.RunningAppState
+import com.android.launcher3.Flags
 import com.android.launcher3.Flags.enableRecentsInTaskbar
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.TaskItemInfo
 import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
 import com.android.launcher3.util.CancellableTask
+import com.android.quickstep.RecentsFilterState
 import com.android.quickstep.RecentsModel
 import com.android.quickstep.util.DesktopTask
 import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.SingleTask
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import java.io.PrintWriter
 
@@ -74,30 +77,43 @@
         private set
 
     /**
+     * The task-state of an app, i.e. whether the app has a task and what state
+     * that task is in.
+     *
+     * @property taskId The ID of the task if one exists (i.e. if the state is
+     * RUNNING or MINIMIZED), null otherwise (NOT_RUNNING).
+     */
+    data class TaskState(val runningAppState: RunningAppState, val taskId: Int? = null)
+
+    /**
      * Returns the state of the most active Desktop task represented by the given [ItemInfo].
      *
      * If there are several tasks represented by the same [ItemInfo] we return the most active one,
      * i.e. we return [DesktopAppState.RUNNING] over [DesktopAppState.MINIMIZED], and
      * [DesktopAppState.MINIMIZED] over [DesktopAppState.NOT_RUNNING].
      */
-    fun getDesktopItemState(itemInfo: ItemInfo?): RunningAppState {
-        val packageName = itemInfo?.getTargetPackage() ?: return RunningAppState.NOT_RUNNING
-        return getDesktopAppState(packageName, itemInfo.user.identifier)
+    fun getDesktopItemState(itemInfo: ItemInfo?): TaskState {
+        val packageName =
+            itemInfo?.getTargetPackage() ?: return TaskState(RunningAppState.NOT_RUNNING)
+        return getDesktopTaskState(packageName, itemInfo.user.identifier)
     }
 
-    private fun getDesktopAppState(packageName: String, userId: Int): RunningAppState {
-        val tasks = desktopTask?.tasks ?: return RunningAppState.NOT_RUNNING
+    private fun getDesktopTaskState(packageName: String, userId: Int): TaskState {
+        val tasks = desktopTask?.tasks ?: return TaskState(RunningAppState.NOT_RUNNING)
         val appTasks =
             tasks.filter { task ->
                 packageName == task.key.packageName && task.key.userId == userId
             }
-        if (appTasks.find { getRunningAppState(it.key.id) == RunningAppState.RUNNING } != null) {
-            return RunningAppState.RUNNING
+        val runningTask = appTasks.find { getRunningAppState(it.key.id) == RunningAppState.RUNNING }
+        if (runningTask != null) {
+            return TaskState(RunningAppState.RUNNING, runningTask.key.id)
         }
-        if (appTasks.find { getRunningAppState(it.key.id) == RunningAppState.MINIMIZED } != null) {
-            return RunningAppState.MINIMIZED
+        val minimizedTask =
+            appTasks.find { getRunningAppState(it.key.id) == RunningAppState.MINIMIZED }
+        if (minimizedTask != null) {
+            return TaskState(RunningAppState.MINIMIZED, taskId = minimizedTask.key.id)
         }
-        return RunningAppState.NOT_RUNNING
+        return TaskState(RunningAppState.NOT_RUNNING)
     }
 
     /** Get the [RunningAppState] for the given task. */
@@ -118,7 +134,7 @@
         get() {
             if (
                 !canShowRunningApps ||
-                    !controllers.taskbarDesktopModeController.areDesktopTasksVisible
+                    !controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()
             ) {
                 return emptySet()
             }
@@ -134,7 +150,7 @@
         get() {
             if (
                 !canShowRunningApps ||
-                    !controllers.taskbarDesktopModeController.areDesktopTasksVisible
+                    !controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()
             ) {
                 return emptySet()
             }
@@ -170,10 +186,10 @@
     /** Called to update hotseatItems, in order to de-dupe them from Recent/Running tasks later. */
     fun updateHotseatItemInfos(hotseatItems: Array<ItemInfo?>): Array<ItemInfo?> {
         // Ignore predicted apps - we show running or recent apps instead.
-        val areDesktopTasksVisible = controllers.taskbarDesktopModeController.areDesktopTasksVisible
+        val showDesktopTasks =
+            controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()
         val removePredictions =
-            (areDesktopTasksVisible && canShowRunningApps) ||
-                (!areDesktopTasksVisible && canShowRecentApps)
+            (showDesktopTasks && canShowRunningApps) || (!showDesktopTasks && canShowRecentApps)
         if (!removePredictions) {
             shownHotseatItems = hotseatItems.filterNotNull()
             onRecentsOrHotseatChanged()
@@ -185,7 +201,7 @@
                 .filter { itemInfo -> !itemInfo.isPredictedItem }
                 .toMutableList()
 
-        if (areDesktopTasksVisible && canShowRunningApps) {
+        if (showDesktopTasks && canShowRunningApps) {
             shownHotseatItems =
                 updateHotseatItemsFromRunningTasks(
                     getOrderedAndWrappedDesktopTasks(),
@@ -203,25 +219,30 @@
         // Kind of hacky, we wrap each single task in the Desktop as a GroupTask.
         val orderFromId = orderedRunningTaskIds.withIndex().associate { (index, id) -> id to index }
         val sortedTasks = tasks.sortedWith(compareBy(nullsLast()) { orderFromId[it.key.id] })
-        return sortedTasks.map { GroupTask(it) }
+        return sortedTasks.map { SingleTask(it) }
     }
 
     private fun reloadRecentTasksIfNeeded() {
         if (!recentsModel.isTaskListValid(taskListChangeId)) {
             taskListChangeId =
-                recentsModel.getTasks { tasks ->
-                    allRecentTasks = tasks
-                    val oldRunningTaskdIds = runningTaskIds
-                    val oldMinimizedTaskIds = minimizedTaskIds
-                    desktopTask = allRecentTasks.filterIsInstance<DesktopTask>().firstOrNull()
-                    val runningTasksChanged = oldRunningTaskdIds != runningTaskIds
-                    val minimizedTasksChanged = oldMinimizedTaskIds != minimizedTaskIds
-                    if (
-                        onRecentsOrHotseatChanged() || runningTasksChanged || minimizedTasksChanged
-                    ) {
-                        controllers.taskbarViewController.commitRunningAppsToUI()
-                    }
-                }
+                recentsModel.getTasks(
+                    { tasks ->
+                        allRecentTasks = tasks
+                        val oldRunningTaskdIds = runningTaskIds
+                        val oldMinimizedTaskIds = minimizedTaskIds
+                        desktopTask = allRecentTasks.filterIsInstance<DesktopTask>().firstOrNull()
+                        val runningTasksChanged = oldRunningTaskdIds != runningTaskIds
+                        val minimizedTasksChanged = oldMinimizedTaskIds != minimizedTaskIds
+                        if (
+                            onRecentsOrHotseatChanged() ||
+                                runningTasksChanged ||
+                                minimizedTasksChanged
+                        ) {
+                            controllers.taskbarViewController.commitRunningAppsToUI()
+                        }
+                    },
+                    RecentsFilterState.EMPTY_FILTER,
+                )
         }
     }
 
@@ -234,7 +255,7 @@
         val oldShownTasks = shownTasks
         orderedRunningTaskIds = updateOrderedRunningTaskIds()
         shownTasks =
-            if (controllers.taskbarDesktopModeController.areDesktopTasksVisible) {
+            if (controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()) {
                 computeShownRunningTasks()
             } else {
                 computeShownRecentTasks()
@@ -265,8 +286,8 @@
     }
 
     private fun updateOrderedRunningTaskIds(): MutableList<Int> {
-        val desktopTaskAsList = getOrderedAndWrappedDesktopTasks()
-        val desktopTaskIds = desktopTaskAsList.map { it.task1.key.id }
+        val desktopTasksAsList = getOrderedAndWrappedDesktopTasks().flatMap { it.tasks }
+        val desktopTaskIds = desktopTasksAsList.map { it.key.id }
         var newOrder =
             orderedRunningTaskIds
                 .filter { it in desktopTaskIds } // Only keep the tasks that are still running
@@ -276,27 +297,60 @@
         return newOrder
     }
 
+    /**
+     * Computes the list of running tasks to be shown in the recent apps section of the taskbar in
+     * desktop mode, taking into account deduplication against hotseat items and existing tasks.
+     */
     private fun computeShownRunningTasks(): List<GroupTask> {
         if (!canShowRunningApps) {
             return emptyList()
         }
-        val desktopTaskAsList = getOrderedAndWrappedDesktopTasks()
-        val desktopTaskIds = desktopTaskAsList.map { it.task1.key.id }
-        val shownTaskIds = shownTasks.map { it.task1.key.id }
-        // TODO(b/315344726 Multi-instance support): only show one icon per package once we support
-        //  taskbar multi-instance menus
-        val shownHotseatItemTaskIds =
-            shownHotseatItems.mapNotNull { it as? TaskItemInfo }.map { it.taskId }
-        // Remove any newly-missing Tasks, and actual group-tasks
+
+        val desktopTasks = getOrderedAndWrappedDesktopTasks()
+
         val newShownTasks =
-            shownTasks
-                .filter { !it.supportsMultipleTasks() }
-                .filter { it.task1.key.id in desktopTaskIds }
-                .toMutableList()
-        // Add any new Tasks, maintaining the order from previous shownTasks.
-        newShownTasks.addAll(desktopTaskAsList.filter { it.task1.key.id !in shownTaskIds })
-        // Remove any tasks already covered by Hotseat icons
-        return newShownTasks.filter { it.task1.key.id !in shownHotseatItemTaskIds }
+            if (Flags.enableMultiInstanceMenuTaskbar()) {
+                val deduplicatedDesktopTasks =
+                    desktopTasks.distinctBy { Pair(it.task1.key.packageName, it.task1.key.userId) }
+
+                shownTasks
+                    .filter {
+                        !it.supportsMultipleTasks() &&
+                            it.task1.key.id in deduplicatedDesktopTasks.map { it.task1.key.id }
+                    }
+                    .toMutableList()
+                    .apply {
+                        addAll(
+                            deduplicatedDesktopTasks.filter { currentTask ->
+                                val currentTaskKey = currentTask.task1.key
+                                currentTaskKey.id !in shownTasks.map { it.task1.key.id } &&
+                                    shownHotseatItems.none { hotseatItem ->
+                                        hotseatItem.targetPackage == currentTaskKey.packageName &&
+                                            hotseatItem.user.identifier == currentTaskKey.userId
+                                    }
+                            }
+                        )
+                    }
+            } else {
+                val desktopTaskIds = desktopTasks.map { it.task1.key.id }
+                val shownHotseatItemTaskIds =
+                    shownHotseatItems.mapNotNull { it as? TaskItemInfo }.map { it.taskId }
+
+                shownTasks
+                    .filter { !it.supportsMultipleTasks() && it.task1.key.id in desktopTaskIds }
+                    .toMutableList()
+                    .apply {
+                        addAll(
+                            desktopTasks.filter { desktopTask ->
+                                desktopTask.task1.key.id !in
+                                    shownTasks.map { shownTask -> shownTask.task1.key.id }
+                            }
+                        )
+                        removeAll { it.task1.key.id in shownHotseatItemTaskIds }
+                    }
+            }
+
+        return newShownTasks
     }
 
     private fun computeShownRecentTasks(): List<GroupTask> {
@@ -305,7 +359,6 @@
         }
         // Remove the current task.
         val allRecentTasks = allRecentTasks.subList(0, allRecentTasks.size - 1)
-        // TODO(b/315344726 Multi-instance support): dedupe Tasks of the same package too
         var shownTasks = dedupeHotseatTasks(allRecentTasks, shownHotseatItems)
         if (shownTasks.size > MAX_RECENT_TASKS) {
             // Remove any tasks older than MAX_RECENT_TASKS.
@@ -318,10 +371,22 @@
         groupTasks: List<GroupTask>,
         shownHotseatItems: List<ItemInfo>,
     ): List<GroupTask> {
-        val hotseatPackages = shownHotseatItems.map { item -> item.targetPackage }
-        return groupTasks.filter { groupTask ->
-            groupTask.hasMultipleTasks() ||
-                !hotseatPackages.contains(groupTask.task1.key.packageName)
+        return if (Flags.enableMultiInstanceMenuTaskbar()) {
+            groupTasks.filter { groupTask ->
+                val taskKey = groupTask.task1.key
+                // Keep tasks that are group tasks or unique package name/user combinations
+                groupTask.hasMultipleTasks() ||
+                    shownHotseatItems.none {
+                        it.targetPackage == taskKey.packageName &&
+                            it.user.identifier == taskKey.userId
+                    }
+            }
+        } else {
+            val hotseatPackages = shownHotseatItems.map { it.targetPackage }
+            groupTasks.filter { groupTask ->
+                groupTask.hasMultipleTasks() ||
+                    !hotseatPackages.contains(groupTask.task1.key.packageName)
+            }
         }
     }
 
@@ -338,11 +403,13 @@
                 itemInfo
             } else {
                 val foundTask =
-                    groupTasks.find { task ->
-                        task.task1.key.packageName == itemInfo.targetPackage &&
-                            task.task1.key.userId == itemInfo.user.identifier
-                    } ?: return@map itemInfo
-                TaskItemInfo(foundTask.task1.key.id, itemInfo as WorkspaceItemInfo)
+                    groupTasks
+                        .flatMap { it.tasks }
+                        .find { task ->
+                            task.key.packageName == itemInfo.targetPackage &&
+                                task.key.userId == itemInfo.user.identifier
+                        } ?: return@map itemInfo
+                TaskItemInfo(foundTask.key.id, itemInfo as WorkspaceItemInfo)
             }
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 502c001..1ca3dfb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -34,8 +34,7 @@
 import static com.android.quickstep.util.SystemUiFlagUtils.isTaskbarHidden;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 
@@ -259,8 +258,8 @@
 
     private @Nullable AnimatorSet mAnimator;
     private boolean mIsSystemGestureInProgress;
-    private boolean mIsImeShowing;
-    private boolean mIsImeSwitcherShowing;
+    /** Whether the IME is visible. */
+    private boolean mIsImeVisible;
 
     private final Alarm mTimeoutAlarm = new Alarm();
     private boolean mEnableBlockingTimeoutDuringTests = false;
@@ -269,6 +268,8 @@
     private final long mTaskbarBackgroundDuration;
     private boolean mUserIsNotGoingHome = false;
 
+    private final boolean mInAppStateAffectsDesktopTasksVisibilityInTaskbar;
+
     // Evaluate whether the handle should be stashed
     private final LongPredicate mIsStashedPredicate = flags -> {
         boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
@@ -293,8 +294,17 @@
         mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
         mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class);
 
-        mTaskbarBackgroundDuration =
-                activity.getResources().getInteger(R.integer.taskbar_background_duration);
+        // Taskbar, via `TaskbarDesktopModeController`, depends on `TaskbarStashController` state to
+        // determine whether desktop tasks should be shown because taskbar is pinned on the home
+        // screen for freeform windowing displays. In this case, list of items shown in the taskbar
+        // needs to be updated when in-app state changes.
+        // TODO(b/390665752): Feature to "lock" pinned taskbar to home screen will be superseded by
+        //     pinning, in other launcher states, at which point this variable can be removed.
+        mInAppStateAffectsDesktopTasksVisibilityInTaskbar =
+                DisplayController.showLockedTaskbarOnHome(mActivity);
+
+        mTaskbarBackgroundDuration = activity.getResources().getInteger(
+                R.integer.taskbar_background_duration);
         if (mActivity.isPhoneMode()) {
             mUnstashedHeight = mActivity.getResources().getDimensionPixelSize(
                     R.dimen.taskbar_phone_size);
@@ -1109,7 +1119,7 @@
      */
     @VisibleForTesting
     long getTaskbarStashStartDelayForIme() {
-        if (mIsImeShowing) {
+        if (mIsImeVisible) {
             // Only delay when IME is exiting, not entering.
             return 0;
         }
@@ -1135,8 +1145,7 @@
         updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED,
                 SystemUiFlagUtils.isLocked(systemUiStateFlags));
 
-        mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
-        mIsImeSwitcherShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_SHOWING);
+        mIsImeVisible = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_VISIBLE);
         if (updateStateForFlag(FLAG_STASHED_IME, shouldStashForIme())) {
             animDuration = TASKBAR_STASH_DURATION_FOR_IME;
             startDelay = getTaskbarStashStartDelayForIme();
@@ -1152,7 +1161,7 @@
     }
 
     /**
-     * We stash when IME or IME switcher is showing.
+     * We stash when the IME is visible.
      *
      * <p>Do not stash if in small screen, with 3 button nav, and in landscape (or seascape).
      * <p>Do not stash if taskbar is transient.
@@ -1188,7 +1197,7 @@
             return false;
         }
 
-        return mIsImeShowing || mIsImeSwitcherShowing;
+        return mIsImeVisible;
     }
 
     /**
@@ -1237,6 +1246,10 @@
         if (hasAnyFlag(changedFlags, FLAG_IN_OVERVIEW | FLAG_IN_APP)) {
             mControllers.runAfterInit(() -> mControllers.taskbarInsetsController
                     .onTaskbarOrBubblebarWindowHeightOrInsetsChanged());
+            if (mInAppStateAffectsDesktopTasksVisibilityInTaskbar) {
+                mControllers.runAfterInit(
+                        () -> mControllers.taskbarViewController.commitRunningAppsToUI());
+            }
         }
         mActivity.applyForciblyShownFlagWhileTransientTaskbarUnstashed(!isStashedInApp());
     }
@@ -1359,8 +1372,7 @@
         pw.println(prefix + "\tappliedState=" + getStateString(mStatePropertyHolder.mPrevFlags));
         pw.println(prefix + "\tmState=" + getStateString(mState));
         pw.println(prefix + "\tmIsSystemGestureInProgress=" + mIsSystemGestureInProgress);
-        pw.println(prefix + "\tmIsImeShowing=" + mIsImeShowing);
-        pw.println(prefix + "\tmIsImeSwitcherShowing=" + mIsImeSwitcherShowing);
+        pw.println(prefix + "\tmIsImeVisible=" + mIsImeVisible);
     }
 
     private static String getStateString(long flags) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 6b9f5a9..457ba3d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -33,7 +33,6 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Bundle;
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.view.DisplayCutout;
@@ -41,7 +40,6 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 
 import androidx.annotation.LayoutRes;
@@ -311,16 +309,6 @@
         mShouldTryStartAlign = mActivityContext.shouldStartAlignTaskbar();
     }
 
-    @Override
-    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
-        if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) {
-            announceTaskbarShown();
-        } else if (action == AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) {
-            announceTaskbarHidden();
-        }
-        return super.performAccessibilityActionInternal(action, arguments);
-    }
-
     private void announceTaskbarShown() {
         BubbleBarLocation bubbleBarLocation = mControllerCallbacks.getBubbleBarLocationIfVisible();
         if (bubbleBarLocation == null) {
@@ -334,21 +322,12 @@
         }
     }
 
-    private void announceTaskbarHidden() {
-        BubbleBarLocation bubbleBarLocation = mControllerCallbacks.getBubbleBarLocationIfVisible();
-        if (bubbleBarLocation == null) {
-            announceForAccessibility(mContext.getString(R.string.taskbar_a11y_hidden_title));
-        } else {
-            announceForAccessibility(
-                    mContext.getString(R.string.taskbar_a11y_hidden_with_bubbles_title));
-        }
-    }
-
     protected void announceAccessibilityChanges() {
-        this.performAccessibilityAction(
-                isVisibleToUser() ? AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS
-                        : AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
-
+        // Only announce taskbar window shown. Window disappearing is generally not announce.
+        // This also aligns with talkback guidelines and unnecessary announcement to users.
+        if (isVisibleToUser()) {
+            announceTaskbarShown();
+        }
         ActivityContext.lookupContext(getContext()).getDragLayer()
                 .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
     }
@@ -402,6 +381,16 @@
         view.setTag(null);
     }
 
+    /** Loop through all {@link FolderIcon} as child views and clear listeners to avoid leak. */
+    public void removeFolderIconListeners() {
+        final int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            if (getChildAt(i) instanceof FolderIcon fi) {
+                fi.removeListeners();
+            }
+        }
+    }
+
     /** Inflates/binds the hotseat items and recent tasks to the view. */
     protected void updateItems(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
         // Filter out unsupported items.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index e0be39d..0f05887 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -365,6 +365,7 @@
         if (enableTaskbarPinning()) {
             mTaskbarView.removeOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
         }
+        mTaskbarView.removeFolderIconListeners();
         LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
         mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 7d39bf8..4e029e3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -19,8 +19,7 @@
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
@@ -34,6 +33,7 @@
 import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.widget.Toast;
 
 import com.android.launcher3.taskbar.TaskbarSharedState;
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
@@ -91,10 +91,9 @@
     private static final long MASK_HIDE_BUBBLE_BAR = SYSUI_STATE_BOUNCER_SHOWING
             | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
             | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
-            | SYSUI_STATE_IME_SHOWING
+            | SYSUI_STATE_IME_VISIBLE
             | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
-            | SYSUI_STATE_QUICK_SETTINGS_EXPANDED
-            | SYSUI_STATE_IME_SWITCHER_SHOWING;
+            | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 
     private static final long MASK_HIDE_HANDLE_VIEW = SYSUI_STATE_BOUNCER_SHOWING
             | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
@@ -238,7 +237,7 @@
 
         boolean sysuiLocked = (flags & MASK_SYSUI_LOCKED) != 0;
         mBubbleStashController.setSysuiLocked(sysuiLocked);
-        mIsImeVisible = (flags & SYSUI_STATE_IME_SHOWING) != 0;
+        mIsImeVisible = (flags & SYSUI_STATE_IME_VISIBLE) != 0;
         if (mIsImeVisible) {
             mBubbleBarViewController.onImeVisible();
         }
@@ -589,6 +588,18 @@
                 });
     }
 
+    @Override
+    public void onDragItemOverBubbleBarDragZone(BubbleBarLocation location) {
+        //TODO(b/388894910): add meaningful implementation
+        MAIN_EXECUTOR.execute(() ->
+                Toast.makeText(mContext, "onDragItemOver " + location, Toast.LENGTH_SHORT).show());
+    }
+
+    @Override
+    public void onItemDraggedOutsideBubbleBarDropZone() {
+
+    }
+
     /** Notifies WMShell to show the expanded view. */
     void showExpandedView() {
         mSystemUiProxy.showExpandedView();
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index afbc932..026f239 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -588,7 +588,7 @@
 
     /** Returns maximum height of the bubble bar with the flyout view. */
     public int getBubbleBarWithFlyoutMaximumHeight() {
-        if (!isBubbleBarVisible() && !isAnimatingNewBubble()) return 0;
+        if (!hasBubbles() && !isAnimatingNewBubble()) return 0;
         int bubbleBarTopOnHome = (int) (mBubbleStashController.getBubbleBarVerticalCenterForHome()
                 + mBarView.getBubbleBarCollapsedHeight() / 2 + mBarView.getArrowHeight());
         if (isAnimatingNewBubble()) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index caac35e..a85e5e0 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -36,6 +36,7 @@
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
+import android.util.Log;
 import android.util.Property;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
@@ -49,8 +50,8 @@
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.celllayout.DelegatedCellDrawing;
+import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.icons.FastBitmapDrawable;
-import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -110,6 +111,8 @@
     private boolean mForceHideRing = false;
     private Animator mRingScaleAnim;
 
+    private int mWidth;
+
     private static final FloatProperty<PredictedAppIcon> SLOT_MACHINE_TRANSLATION_Y =
             new FloatProperty<PredictedAppIcon>("slotMachineTranslationY") {
         @Override
@@ -139,7 +142,7 @@
         int shadowSize = context.getResources().getDimensionPixelSize(
                 R.dimen.blur_size_thin_outline);
         mShadowFilter = new BlurMaskFilter(shadowSize, BlurMaskFilter.Blur.OUTER);
-        mShapePath = GraphicsUtils.getShapePath(context, mNormalizedIconSize);
+        mShapePath = IconShape.INSTANCE.get(context).getShapeOverridePath(mNormalizedIconSize);
     }
 
     @Override
@@ -300,7 +303,13 @@
     }
 
     private int getOutlineOffsetX() {
-        return (getMeasuredWidth() - mNormalizedIconSize) / 2;
+        int measuredWidth = getMeasuredWidth();
+        if (mDisplay != DISPLAY_TASKBAR) {
+            Log.d("b/387844520", "getOutlineOffsetX: measured width = " + measuredWidth
+                    + ", mNormalizedIconSize = " + mNormalizedIconSize
+                    + ", last updated width = " + mWidth);
+        }
+        return (mWidth - mNormalizedIconSize) / 2;
     }
 
     private int getOutlineOffsetY() {
@@ -313,7 +322,11 @@
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
+        mWidth = w;
         mSlotIconBound.offsetTo((w - getIconSize()) / 2, (h - getIconSize()) / 2);
+        if (mDisplay != DISPLAY_TASKBAR) {
+            Log.d("b/387844520", "calling updateRingPath from onSizeChanged");
+        }
         updateRingPath();
     }
 
@@ -325,6 +338,7 @@
 
     private void updateRingPath() {
         mRingPath.reset();
+        mTmpMatrix.reset();
         mTmpMatrix.setTranslate(getOutlineOffsetX(), getOutlineOffsetY());
         mRingPath.addPath(mShapePath, mTmpMatrix);
 
@@ -339,6 +353,7 @@
             mTmpMatrix.preTranslate(-mNormalizedIconSize, -mNormalizedIconSize);
             mRingPath.addPath(mShapePath, mTmpMatrix);
         }
+        invalidate();
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 810325c..f672840 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -23,6 +23,7 @@
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
+import static com.android.launcher3.Flags.enableExpressiveDismissTaskMotion;
 import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
@@ -64,7 +65,6 @@
 import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
 import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
-import static com.android.wm.shell.Flags.enableBubbleAnything;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
 
 import android.animation.Animator;
@@ -85,6 +85,7 @@
 import android.os.IRemoteCallback;
 import android.os.SystemProperties;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
@@ -149,7 +150,9 @@
 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewRecentsTouchContext;
 import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchControllerDeprecated;
 import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
 import com.android.launcher3.util.ActivityOptionsWrapper;
@@ -200,6 +203,7 @@
 import com.android.systemui.unfold.dagger.UnfoldMain;
 import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver;
 import com.android.systemui.unfold.updates.RotationChangeProvider;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 
@@ -264,6 +268,25 @@
 
     private final OverviewChangeListener mOverviewChangeListener = this::onOverviewTargetChanged;
 
+    private final TaskViewRecentsTouchContext mTaskViewRecentsTouchContext =
+            new TaskViewRecentsTouchContext() {
+                @Override
+                public boolean isRecentsInteractive() {
+                    return isInState(OVERVIEW) || isInState(OVERVIEW_MODAL_TASK);
+                }
+
+                @Override
+                public boolean isRecentsModal() {
+                    return isInState(OVERVIEW_MODAL_TASK);
+                }
+
+                @Override
+                public void onUserControlledAnimationCreated(
+                        AnimatorPlaybackController animController) {
+                    getStateManager().setCurrentUserControlledAnimation(animController);
+                }
+            };
+
     public static QuickstepLauncher getLauncher(Context context) {
         return fromContext(context);
     }
@@ -467,7 +490,7 @@
         if (Flags.enablePrivateSpace()) {
             shortcuts.add(UNINSTALL_APP);
         }
-        if (enableBubbleAnything()) {
+        if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
             shortcuts.add(BUBBLE_SHORTCUT);
         }
         return shortcuts.stream();
@@ -664,7 +687,11 @@
             list.add(new StatusBarTouchController(this));
         }
 
-        list.add(new LauncherTaskViewController(this));
+        if (enableExpressiveDismissTaskMotion()) {
+            list.add(new TaskViewTouchController<>(this, mTaskViewRecentsTouchContext));
+        } else {
+            list.add(new TaskViewTouchControllerDeprecated<>(this, mTaskViewRecentsTouchContext));
+        }
         return list.toArray(new TouchController[list.size()]);
     }
 
@@ -1431,9 +1458,9 @@
     }
 
     @Override
-    public void showAppBubble(Intent intent) {
+    public void showAppBubble(Intent intent, UserHandle user) {
         if (intent == null || intent.getPackage() == null) return;
-        SystemUiProxy.INSTANCE.get(this).showAppBubble(intent);
+        SystemUiProxy.INSTANCE.get(this).showAppBubble(intent, user);
     }
 
     /** Sets the location of the bubble bar */
@@ -1441,29 +1468,6 @@
         mBubbleBarLocation = bubbleBarLocation;
     }
 
-    private static final class LauncherTaskViewController extends
-            TaskViewTouchController<QuickstepLauncher> {
-
-        LauncherTaskViewController(QuickstepLauncher activity) {
-            super(activity);
-        }
-
-        @Override
-        protected boolean isRecentsInteractive() {
-            return mContainer.isInState(OVERVIEW) || mContainer.isInState(OVERVIEW_MODAL_TASK);
-        }
-
-        @Override
-        protected boolean isRecentsModal() {
-            return mContainer.isInState(OVERVIEW_MODAL_TASK);
-        }
-
-        @Override
-        protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
-            mContainer.getStateManager().setCurrentUserControlledAnimation(animController);
-        }
-    }
-
     @Override
     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
         super.dump(prefix, fd, writer, args);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewRecentsTouchContext.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewRecentsTouchContext.java
new file mode 100644
index 0000000..e8d31c1
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewRecentsTouchContext.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides.touchcontrollers;
+
+import com.android.launcher3.anim.AnimatorPlaybackController;
+
+/** Interface providing context about the RecentsView state to a {@link TaskViewTouchController}. */
+public interface TaskViewRecentsTouchContext {
+    /** Returns whether Recents is interactive for touch. */
+    boolean isRecentsInteractive();
+
+    /** Returns if Recents is showing a single task in a modal way. */
+    boolean isRecentsModal();
+
+    /** Runs when a user controlled animation is created. */
+    default void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.kt
new file mode 100644
index 0000000..c996f34
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.kt
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides.touchcontrollers
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.content.Context
+import android.graphics.Rect
+import android.os.VibrationEffect
+import android.view.MotionEvent
+import android.view.animation.Interpolator
+import com.android.app.animation.Interpolators
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS
+import com.android.launcher3.LauncherAnimUtils.blockedFlingDurationFactor
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.anim.AnimatorPlaybackController
+import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.touch.BaseSwipeDetector
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.FlingBlockCheck
+import com.android.launcher3.util.TouchController
+import com.android.launcher3.util.VibratorWrapper
+import com.android.quickstep.util.VibrationConstants
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsViewContainer
+import com.android.quickstep.views.TaskView
+import kotlin.math.abs
+
+/** Touch controller for handling task view card swipes */
+class TaskViewTouchController<CONTAINER>(
+    private val container: CONTAINER,
+    private val taskViewRecentsTouchContext: TaskViewRecentsTouchContext,
+) : AnimatorListenerAdapter(), TouchController, SingleAxisSwipeDetector.Listener where
+CONTAINER : Context,
+CONTAINER : RecentsViewContainer {
+    private val recentsView: RecentsView<*, *> = container.getOverviewPanel()
+    private val detector: SingleAxisSwipeDetector =
+        SingleAxisSwipeDetector(
+            container as Context,
+            this,
+            recentsView.pagedOrientationHandler.upDownSwipeDirection,
+        )
+    private val tempRect = Rect()
+    private val isRtl = Utilities.isRtl(container.resources)
+    private val flingBlockCheck = FlingBlockCheck()
+
+    private var currentAnimation: AnimatorPlaybackController? = null
+    private var currentAnimationIsGoingUp = false
+    private var allowGoingUp = false
+    private var allowGoingDown = false
+    private var noIntercept = false
+    private var displacementShift = 0f
+    private var progressMultiplier = 0f
+    private var endDisplacement = 0f
+    private var draggingEnabled = true
+    private var overrideVelocity: Float? = null
+    private var taskBeingDragged: TaskView? = null
+    private var isDismissHapticRunning = false
+
+    private fun canInterceptTouch(ev: MotionEvent): Boolean {
+        val currentAnimation = currentAnimation
+        return when {
+            (ev.edgeFlags and Utilities.EDGE_NAV_BAR) != 0 -> {
+                // Don't intercept swipes on the nav bar, as user might be trying to go home
+                // during a task dismiss animation.
+                currentAnimation?.animationPlayer?.end()
+                false
+            }
+            currentAnimation != null -> {
+                currentAnimation.forceFinishIfCloseToEnd()
+                true
+            }
+            AbstractFloatingView.getTopOpenViewWithType(
+                container,
+                AbstractFloatingView.TYPE_TOUCH_CONTROLLER_NO_INTERCEPT,
+            ) != null -> false
+            else -> taskViewRecentsTouchContext.isRecentsInteractive
+        }
+    }
+
+    override fun onAnimationCancel(animation: Animator) {
+        if (animation === currentAnimation?.target) {
+            clearState()
+        }
+    }
+
+    override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
+        if (
+            (ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL) &&
+                currentAnimation == null
+        ) {
+            clearState()
+        }
+        if (ev.action == MotionEvent.ACTION_DOWN) {
+            // Disable swiping up and down if the task overlay is modal.
+            if (taskViewRecentsTouchContext.isRecentsModal) {
+                noIntercept = true
+                return false
+            }
+            noIntercept = !canInterceptTouch(ev)
+            if (noIntercept) {
+                return false
+            }
+            // Now figure out which direction scroll events the controller will start
+            // calling the callbacks.
+            var directionsToDetectScroll = 0
+            var ignoreSlopWhenSettling = false
+            if (currentAnimation != null) {
+                directionsToDetectScroll = SingleAxisSwipeDetector.DIRECTION_BOTH
+                ignoreSlopWhenSettling = true
+            } else {
+                taskBeingDragged = null
+                recentsView.taskViews.forEach { taskView ->
+                    if (
+                        recentsView.isTaskViewVisible(taskView) &&
+                            container.dragLayer.isEventOverView(taskView, ev)
+                    ) {
+                        taskBeingDragged = taskView
+                        val upDirection = recentsView.pagedOrientationHandler.getUpDirection(isRtl)
+
+                        // The task can be dragged up to dismiss it
+                        allowGoingUp = true
+
+                        // The task can be dragged down to open it if:
+                        // - It's the current page
+                        // - We support gestures to enter overview
+                        // - It's the focused task if in grid view
+                        // - The task is snapped
+                        allowGoingDown =
+                            taskView === recentsView.currentPageTaskView &&
+                                DisplayController.getNavigationMode(container).hasGestures &&
+                                (!recentsView.showAsGrid() || taskView.isLargeTile) &&
+                                recentsView.isTaskInExpectedScrollPosition(taskView)
+
+                        directionsToDetectScroll =
+                            if (allowGoingDown) SingleAxisSwipeDetector.DIRECTION_BOTH
+                            else upDirection
+                        return@forEach
+                    }
+                }
+                if (taskBeingDragged == null) {
+                    noIntercept = true
+                    return false
+                }
+            }
+            detector.setDetectableScrollConditions(directionsToDetectScroll, ignoreSlopWhenSettling)
+        }
+        if (noIntercept) {
+            return false
+        }
+        onControllerTouchEvent(ev)
+        return detector.isDraggingOrSettling
+    }
+
+    override fun onControllerTouchEvent(ev: MotionEvent): Boolean = detector.onTouchEvent(ev)
+
+    private fun reInitAnimationController(goingUp: Boolean) {
+        if (currentAnimation != null && currentAnimationIsGoingUp == goingUp) {
+            // No need to init
+            return
+        }
+        if ((goingUp && !allowGoingUp) || (!goingUp && !allowGoingDown)) {
+            // Trying to re-init in an unsupported direction.
+            return
+        }
+        val taskBeingDragged = taskBeingDragged ?: return
+        currentAnimation?.setPlayFraction(0f)
+        currentAnimation?.target?.removeListener(this)
+        currentAnimation?.dispatchOnCancel()
+
+        val orientationHandler = recentsView.pagedOrientationHandler
+        currentAnimationIsGoingUp = goingUp
+        val dl = container.dragLayer
+        val secondaryLayerDimension = orientationHandler.getSecondaryDimension(dl)
+        val maxDuration = 2L * secondaryLayerDimension
+        val verticalFactor = orientationHandler.getTaskDragDisplacementFactor(isRtl)
+        val secondaryTaskDimension = orientationHandler.getSecondaryDimension(taskBeingDragged)
+        // The interpolator controlling the most prominent visual movement. We use this to determine
+        // whether we passed SUCCESS_TRANSITION_PROGRESS.
+        val currentInterpolator: Interpolator
+        val pa: PendingAnimation
+        if (goingUp) {
+            currentInterpolator = Interpolators.LINEAR
+            pa = PendingAnimation(maxDuration)
+            recentsView.createTaskDismissAnimation(
+                pa,
+                taskBeingDragged,
+                true, /* animateTaskView */
+                true, /* removeTask */
+                maxDuration,
+                false, /* dismissingForSplitSelection*/
+            )
+
+            endDisplacement = -secondaryTaskDimension.toFloat()
+        } else {
+            currentInterpolator = Interpolators.ZOOM_IN
+            pa =
+                recentsView.createTaskLaunchAnimation(
+                    taskBeingDragged,
+                    maxDuration,
+                    currentInterpolator,
+                )
+
+            // Since the thumbnail is what is filling the screen, based the end displacement on it.
+            taskBeingDragged.getThumbnailBounds(tempRect, /* relativeToDragLayer= */ true)
+            endDisplacement = (secondaryLayerDimension - tempRect.bottom).toFloat()
+        }
+        endDisplacement *= verticalFactor.toFloat()
+        currentAnimation =
+            pa.createPlaybackController().apply {
+                // Setting this interpolator doesn't affect the visual motion, but is used to
+                // determine whether we successfully reached the target state in onDragEnd().
+                target.interpolator = currentInterpolator
+                taskViewRecentsTouchContext.onUserControlledAnimationCreated(this)
+                target.addListener(this@TaskViewTouchController)
+                dispatchOnStart()
+            }
+        progressMultiplier = 1 / endDisplacement
+    }
+
+    override fun onDragStart(start: Boolean, startDisplacement: Float) {
+        if (!draggingEnabled) return
+        val currentAnimation = currentAnimation
+
+        val orientationHandler = recentsView.pagedOrientationHandler
+        if (currentAnimation == null) {
+            reInitAnimationController(orientationHandler.isGoingUp(startDisplacement, isRtl))
+            displacementShift = 0f
+        } else {
+            displacementShift = currentAnimation.progressFraction / progressMultiplier
+            currentAnimation.pause()
+        }
+        flingBlockCheck.unblockFling()
+        overrideVelocity = null
+    }
+
+    override fun onDrag(displacement: Float): Boolean {
+        if (!draggingEnabled) return true
+        val taskBeingDragged = taskBeingDragged ?: return true
+        val currentAnimation = currentAnimation ?: return true
+
+        val orientationHandler = recentsView.pagedOrientationHandler
+        val totalDisplacement = displacement + displacementShift
+        val isGoingUp =
+            if (totalDisplacement == 0f) currentAnimationIsGoingUp
+            else orientationHandler.isGoingUp(totalDisplacement, isRtl)
+        if (isGoingUp != currentAnimationIsGoingUp) {
+            reInitAnimationController(isGoingUp)
+            flingBlockCheck.blockFling()
+        } else {
+            flingBlockCheck.onEvent()
+        }
+
+        if (isGoingUp) {
+            if (currentAnimation.progressFraction < ANIMATION_PROGRESS_FRACTION_MIDPOINT) {
+                // Halve the value when dismissing, as we are animating the drag across the full
+                // length for only the first half of the progress
+                currentAnimation.setPlayFraction(
+                    Utilities.boundToRange(totalDisplacement * progressMultiplier / 2, 0f, 1f)
+                )
+            } else {
+                // Set mOverrideVelocity to control task dismiss velocity in onDragEnd
+                var velocityDimenId = R.dimen.default_task_dismiss_drag_velocity
+                if (recentsView.showAsGrid()) {
+                    velocityDimenId =
+                        if (taskBeingDragged.isLargeTile) {
+                            R.dimen.default_task_dismiss_drag_velocity_grid_focus_task
+                        } else {
+                            R.dimen.default_task_dismiss_drag_velocity_grid
+                        }
+                }
+                overrideVelocity = -taskBeingDragged.resources.getDimension(velocityDimenId)
+
+                // Once halfway through task dismissal interpolation, switch from reversible
+                // dragging-task animation to playing the remaining task translation animations,
+                // while this is in progress disable dragging.
+                draggingEnabled = false
+            }
+        } else {
+            currentAnimation.setPlayFraction(
+                Utilities.boundToRange(totalDisplacement * progressMultiplier, 0f, 1f)
+            )
+        }
+
+        return true
+    }
+
+    override fun onDragEnd(velocity: Float) {
+        val taskBeingDragged = taskBeingDragged ?: return
+        val currentAnimation = currentAnimation ?: return
+
+        // Limit velocity, as very large scalar values make animations play too quickly
+        val maxTaskDismissDragVelocity =
+            taskBeingDragged.resources.getDimension(R.dimen.max_task_dismiss_drag_velocity)
+        val endVelocity =
+            Utilities.boundToRange(
+                overrideVelocity ?: velocity,
+                -maxTaskDismissDragVelocity,
+                maxTaskDismissDragVelocity,
+            )
+        overrideVelocity = null
+
+        var fling = draggingEnabled && detector.isFling(endVelocity)
+        val goingToEnd: Boolean
+        val blockedFling = fling && flingBlockCheck.isBlocked
+        if (blockedFling) {
+            fling = false
+        }
+        val orientationHandler = recentsView.pagedOrientationHandler
+        val goingUp = orientationHandler.isGoingUp(endVelocity, isRtl)
+        val progress = currentAnimation.progressFraction
+        val interpolatedProgress = currentAnimation.interpolatedProgress
+        goingToEnd =
+            if (fling) {
+                goingUp == currentAnimationIsGoingUp
+            } else {
+                interpolatedProgress > SUCCESS_TRANSITION_PROGRESS
+            }
+        var animationDuration =
+            BaseSwipeDetector.calculateDuration(
+                endVelocity,
+                if (goingToEnd) (1 - progress) else progress,
+            )
+        if (blockedFling && !goingToEnd) {
+            animationDuration *= blockedFlingDurationFactor(endVelocity).toLong()
+        }
+        // Due to very high or low velocity dismissals, animation durations can be inconsistently
+        // long or short. Bound the duration for animation of task translations for a more
+        // standardized feel.
+        animationDuration =
+            Utilities.boundToRange(
+                animationDuration,
+                MIN_TASK_DISMISS_ANIMATION_DURATION,
+                MAX_TASK_DISMISS_ANIMATION_DURATION,
+            )
+
+        currentAnimation.setEndAction { this.clearState() }
+        currentAnimation.startWithVelocity(
+            container,
+            goingToEnd,
+            abs(endVelocity.toDouble()).toFloat(),
+            endDisplacement,
+            animationDuration,
+        )
+        if (goingUp && goingToEnd && !isDismissHapticRunning) {
+            VibratorWrapper.INSTANCE.get(container)
+                .vibrate(
+                    TASK_DISMISS_VIBRATION_PRIMITIVE,
+                    TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE,
+                    TASK_DISMISS_VIBRATION_FALLBACK,
+                )
+            isDismissHapticRunning = true
+        }
+
+        draggingEnabled = true
+    }
+
+    private fun clearState() {
+        detector.finishedScrolling()
+        detector.setDetectableScrollConditions(0, false)
+        draggingEnabled = true
+        taskBeingDragged = null
+        currentAnimation = null
+        isDismissHapticRunning = false
+    }
+
+    companion object {
+        private const val ANIMATION_PROGRESS_FRACTION_MIDPOINT = 0.5f
+        private const val MIN_TASK_DISMISS_ANIMATION_DURATION: Long = 300
+        private const val MAX_TASK_DISMISS_ANIMATION_DURATION: Long = 600
+
+        private const val TASK_DISMISS_VIBRATION_PRIMITIVE: Int =
+            VibrationEffect.Composition.PRIMITIVE_TICK
+        private const val TASK_DISMISS_VIBRATION_PRIMITIVE_SCALE: Float = 1f
+        private val TASK_DISMISS_VIBRATION_FALLBACK: VibrationEffect =
+            VibrationConstants.EFFECT_TEXTURE_TICK
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
similarity index 94%
rename from quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
index d622987..b1a36c7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
@@ -49,10 +49,13 @@
 
 /**
  * Touch controller for handling task view card swipes
+ *
+ * @deprecated This class will be replaced by the new {@link TaskViewTouchController}.
  */
-public abstract class TaskViewTouchController<CONTAINER extends Context & RecentsViewContainer>
-        extends AnimatorListenerAdapter implements TouchController,
-        SingleAxisSwipeDetector.Listener {
+@Deprecated
+public class TaskViewTouchControllerDeprecated<
+        CONTAINER extends Context & RecentsViewContainer> extends AnimatorListenerAdapter
+        implements TouchController, SingleAxisSwipeDetector.Listener {
 
     private static final float ANIMATION_PROGRESS_FRACTION_MIDPOINT = 0.5f;
     private static final long MIN_TASK_DISMISS_ANIMATION_DURATION = 300;
@@ -65,6 +68,7 @@
             VibrationConstants.EFFECT_TEXTURE_TICK;
 
     protected final CONTAINER mContainer;
+    private final TaskViewRecentsTouchContext mTaskViewRecentsTouchContext;
     private final SingleAxisSwipeDetector mDetector;
     private final RecentsView<?, ?> mRecentsView;
     private final Rect mTempRect = new Rect();
@@ -88,8 +92,10 @@
 
     private boolean mIsDismissHapticRunning = false;
 
-    public TaskViewTouchController(CONTAINER container) {
+    public TaskViewTouchControllerDeprecated(CONTAINER container,
+            TaskViewRecentsTouchContext taskViewRecentsTouchContext) {
         mContainer = container;
+        mTaskViewRecentsTouchContext = taskViewRecentsTouchContext;
         mRecentsView = container.getOverviewPanel();
         mIsRtl = Utilities.isRtl(container.getResources());
         SingleAxisSwipeDetector.Direction dir =
@@ -117,15 +123,7 @@
                 mContainer, TYPE_TOUCH_CONTROLLER_NO_INTERCEPT) != null) {
             return false;
         }
-        return isRecentsInteractive();
-    }
-
-    protected abstract boolean isRecentsInteractive();
-
-    /** Is recents view showing a single task in a modal way. */
-    protected abstract boolean isRecentsModal();
-
-    protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
+        return mTaskViewRecentsTouchContext.isRecentsInteractive();
     }
 
     @Override
@@ -161,7 +159,7 @@
                     if (mRecentsView.isTaskViewVisible(taskView) && mContainer.getDragLayer()
                             .isEventOverView(taskView, ev)) {
                         // Disable swiping up and down if the task overlay is modal.
-                        if (isRecentsModal()) {
+                        if (mTaskViewRecentsTouchContext.isRecentsModal()) {
                             mTaskBeingDragged = null;
                             break;
                         }
@@ -259,7 +257,7 @@
         // Setting this interpolator doesn't affect the visual motion, but is used to determine
         // whether we successfully reached the target state in onDragEnd().
         mCurrentAnimation.getTarget().setInterpolator(currentInterpolator);
-        onUserControlledAnimationCreated(mCurrentAnimation);
+        mTaskViewRecentsTouchContext.onUserControlledAnimationCreated(mCurrentAnimation);
         mCurrentAnimation.getTarget().addListener(this);
         mCurrentAnimation.dispatchOnStart();
         mProgressMultiplier = 1 / mEndDisplacement;
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 3dd1473..8b76ce9 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1826,9 +1826,7 @@
 
         final Rect hotseatKeepClearArea = getKeepClearAreaForHotseat();
         final Rect destinationBounds = SystemUiProxy.INSTANCE.get(mContext)
-                .startSwipePipToHome(taskInfo.topActivity,
-                        taskInfo.topActivityInfo,
-                        runningTaskTarget.taskInfo.pictureInPictureParams,
+                .startSwipePipToHome(taskInfo,
                         homeRotation,
                         hotseatKeepClearArea);
         if (destinationBounds == null) {
diff --git a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
index d60dab6..914855b 100644
--- a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
+++ b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
@@ -26,6 +26,7 @@
 import com.android.quickstep.views.RecentsViewContainer
 import com.android.quickstep.views.TaskContainer
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper
+import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
 
@@ -70,16 +71,31 @@
                     container: RecentsViewContainer,
                     taskContainer: TaskContainer,
                 ): List<DesktopSystemShortcut>? {
-                    return if (!DesktopModeStatus.canEnterDesktopMode(container.asContext())) null
-                    else if (!taskContainer.task.isDockable) null
-                    else
-                        listOf(
-                            DesktopSystemShortcut(
-                                container,
-                                taskContainer,
-                                abstractFloatingViewHelper,
+                    val context = container.asContext()
+                    val taskKey = taskContainer.task.key
+                    val desktopModeCompatPolicy = DesktopModeCompatPolicy(context)
+                    return when {
+                        !DesktopModeStatus.canEnterDesktopMode(context) -> null
+
+                        desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+                            taskKey.baseActivity?.packageName,
+                            taskKey.numActivities,
+                            taskKey.isTopActivityNoDisplay,
+                            taskKey.isActivityStackTransparent,
+                        ) -> null
+
+                        !taskContainer.task.isDockable -> null
+
+                        else -> {
+                            listOf(
+                                DesktopSystemShortcut(
+                                    container,
+                                    taskContainer,
+                                    abstractFloatingViewHelper,
+                                )
                             )
-                        )
+                        }
+                    }
                 }
 
                 override fun showForGroupedTask() = true
diff --git a/quickstep/src/com/android/quickstep/ExternalDisplaySystemShortcut.kt b/quickstep/src/com/android/quickstep/ExternalDisplaySystemShortcut.kt
index 46c4f36..f97cf9c 100644
--- a/quickstep/src/com/android/quickstep/ExternalDisplaySystemShortcut.kt
+++ b/quickstep/src/com/android/quickstep/ExternalDisplaySystemShortcut.kt
@@ -25,6 +25,7 @@
 import com.android.quickstep.views.RecentsViewContainer
 import com.android.quickstep.views.TaskContainer
 import com.android.window.flags.Flags
+import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 
 /** A menu item that allows the user to move the current app into external display. */
@@ -66,18 +67,31 @@
                     container: RecentsViewContainer,
                     taskContainer: TaskContainer,
                 ): List<ExternalDisplaySystemShortcut>? {
-                    return if (
-                        DesktopModeStatus.canEnterDesktopMode(container.asContext()) &&
-                            Flags.moveToExternalDisplayShortcut()
-                    )
-                        listOf(
-                            ExternalDisplaySystemShortcut(
-                                container,
-                                abstractFloatingViewHelper,
-                                taskContainer,
+                    val context = container.asContext()
+                    val taskKey = taskContainer.task.key
+                    val desktopModeCompatPolicy = DesktopModeCompatPolicy(context)
+                    return when {
+                        !DesktopModeStatus.canEnterDesktopMode(context) -> null
+
+                        !Flags.moveToExternalDisplayShortcut() -> null
+
+                        desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+                            taskKey.baseActivity?.packageName,
+                            taskKey.numActivities,
+                            taskKey.isTopActivityNoDisplay,
+                            taskKey.isActivityStackTransparent,
+                        ) -> null
+
+                        else -> {
+                            listOf(
+                                ExternalDisplaySystemShortcut(
+                                    container,
+                                    abstractFloatingViewHelper,
+                                    taskContainer,
+                                )
                             )
-                        )
-                    else null
+                        }
+                    }
                 }
 
                 override fun showForGroupedTask() = true
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index e0fa77a..e1d4536 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -28,7 +28,6 @@
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_HOME;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_NEW_TASK;
 
-import android.app.TaskInfo;
 import android.content.Intent;
 import android.os.SystemClock;
 import android.view.MotionEvent;
@@ -47,7 +46,6 @@
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
 import com.android.quickstep.views.RecentsViewContainer;
 import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.wm.shell.shared.GroupedTaskInfo;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -334,13 +332,7 @@
             return new int[]{INVALID_TASK_ID, INVALID_TASK_ID};
         } else {
             if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-                if (mRunningTask.getVisibleTasks().isEmpty()) {
-                    return new int[0];
-                }
-                GroupedTaskInfo topRunningTask = mRunningTask.getVisibleTasks().getFirst();
-                List<TaskInfo> groupedTasks = topRunningTask.getTaskInfoList();
-                return groupedTasks.stream().mapToInt(
-                        groupedTask -> groupedTask.taskId).toArray();
+                return mRunningTask.topGroupedTaskIds();
             } else {
                 int cachedTasksSize = mRunningTask.mAllCachedTasks.size();
                 int count = Math.min(cachedTasksSize, getMultipleTasks ? 2 : 1);
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index 66f307c..6ad9a2c 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -28,7 +28,9 @@
 import androidx.annotation.UiThread
 import androidx.annotation.VisibleForTesting
 import com.android.internal.jank.Cuj
+import com.android.launcher3.Flags.enableFallbackOverviewInWindow
 import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
+import com.android.launcher3.Flags.enableLauncherOverviewInWindow
 import com.android.launcher3.Flags.enableOverviewCommandHelperTimeout
 import com.android.launcher3.PagedView
 import com.android.launcher3.logger.LauncherAtom
@@ -344,9 +346,12 @@
             return false
         }
 
-        val activity = containerInterface.getCreatedContainer()
-        if (activity != null) {
-            InteractionJankMonitorWrapper.begin(activity.rootView, Cuj.CUJ_LAUNCHER_QUICK_SWITCH)
+        val recentsInWindowFlagSet =
+            enableFallbackOverviewInWindow() || enableLauncherOverviewInWindow()
+        if (!recentsInWindowFlagSet) {
+            containerInterface.getCreatedContainer()?.rootView?.let { view ->
+                InteractionJankMonitorWrapper.begin(view, Cuj.CUJ_LAUNCHER_QUICK_SWITCH)
+            }
         }
 
         val gestureState =
@@ -373,6 +378,12 @@
                     transitionInfo: TransitionInfo,
                 ) {
                     Log.d(TAG, "recents animation started: $command")
+                    if (recentsInWindowFlagSet) {
+                        containerInterface.getCreatedContainer()?.rootView?.let { view ->
+                            InteractionJankMonitorWrapper.begin(view, Cuj.CUJ_LAUNCHER_QUICK_SWITCH)
+                        }
+                    }
+
                     updateRecentsViewFocus(command)
                     logShowOverviewFrom(command.type)
                     containerInterface.runOnInitBackgroundStateUI {
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 32ccd72..01ced75 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
 import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM;
+import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.KeyguardManager;
@@ -38,7 +39,10 @@
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
+import com.android.quickstep.util.SplitTask;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.recents.IRecentTasksListener;
 import com.android.wm.shell.shared.GroupedTaskInfo;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -48,6 +52,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -116,7 +121,8 @@
             @Override
             public void onTaskMovedToFront(GroupedTaskInfo taskToFront) {
                 mMainThreadExecutor.execute(() -> {
-                    topTaskTracker.handleTaskMovedToFront(taskToFront.getTaskInfo1());
+                    topTaskTracker.handleTaskMovedToFront(
+                            taskToFront.getBaseGroupedTask().getTaskInfo1());
                 });
             }
 
@@ -340,50 +346,76 @@
 
         int numVisibleTasks = 0;
         for (GroupedTaskInfo rawTask : rawTasks) {
-            if (rawTask.getType() == TYPE_FREEFORM) {
+            if (rawTask.isBaseType(TYPE_FREEFORM)) {
                 // TYPE_FREEFORM tasks is only created when desktop mode can be entered,
                 // leftover TYPE_FREEFORM tasks created when flag was on should be ignored.
                 if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
-                    GroupTask desktopTask = createDesktopTask(rawTask);
+                    GroupTask desktopTask = createDesktopTask(rawTask.getBaseGroupedTask());
                     if (desktopTask != null) {
                         allTasks.add(desktopTask);
                     }
                 }
                 continue;
             }
-            TaskInfo taskInfo1 = rawTask.getTaskInfo1();
-            TaskInfo taskInfo2 = rawTask.getTaskInfo2();
-            Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
-            Task task1 = loadKeysOnly
-                    ? new Task(task1Key)
-                    : Task.from(task1Key, taskInfo1,
-                            tmpLockedUsers.get(task1Key.userId) /* isLocked */);
-            Task task2 = null;
-            if (taskInfo2 != null) {
-                // Is split task
-                Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
-                task2 = loadKeysOnly
-                        ? new Task(task2Key)
-                        : Task.from(task2Key, taskInfo2,
-                                tmpLockedUsers.get(task2Key.userId) /* isLocked */);
+
+            if (Flags.enableShellTopTaskTracking()) {
+                final TaskInfo taskInfo1 = rawTask.getBaseGroupedTask().getTaskInfo1();
+                final Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
+                final Task task1 = Task.from(task1Key, taskInfo1,
+                        tmpLockedUsers.get(task1Key.userId) /* isLocked */);
+
+                if (rawTask.isBaseType(TYPE_SPLIT)) {
+                    final TaskInfo taskInfo2 = rawTask.getBaseGroupedTask().getTaskInfo2();
+                    final Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
+                    final Task task2 = Task.from(task2Key, taskInfo2,
+                            tmpLockedUsers.get(task2Key.userId) /* isLocked */);
+                    final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
+                            convertShellSplitBoundsToLauncher(
+                                    rawTask.getBaseGroupedTask().getSplitBounds());
+                    allTasks.add(new SplitTask(task1, task2, launcherSplitBounds));
+                } else {
+                    allTasks.add(new SingleTask(task1));
+                }
             } else {
-                // Is fullscreen task
-                if (numVisibleTasks > 0) {
-                    boolean isExcluded = (taskInfo1.baseIntent.getFlags()
-                            & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
-                    if (taskInfo1.isTopActivityTransparent && isExcluded) {
-                        // If there are already visible tasks, then ignore the excluded tasks and
-                        // don't add them to the returned list
-                        continue;
+                TaskInfo taskInfo1 = rawTask.getTaskInfo1();
+                TaskInfo taskInfo2 = rawTask.getTaskInfo2();
+                Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
+                Task task1 = loadKeysOnly
+                        ? new Task(task1Key)
+                        : Task.from(task1Key, taskInfo1,
+                                tmpLockedUsers.get(task1Key.userId) /* isLocked */);
+                Task task2 = null;
+                if (taskInfo2 != null) {
+                    // Is split task
+                    Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
+                    task2 = loadKeysOnly
+                            ? new Task(task2Key)
+                            : Task.from(task2Key, taskInfo2,
+                                    tmpLockedUsers.get(task2Key.userId) /* isLocked */);
+                } else {
+                    // Is fullscreen task
+                    if (numVisibleTasks > 0) {
+                        boolean isExcluded = (taskInfo1.baseIntent.getFlags()
+                                & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
+                        if (taskInfo1.isTopActivityTransparent && isExcluded) {
+                            // If there are already visible tasks, then ignore the excluded tasks
+                            // and don't add them to the returned list
+                            continue;
+                        }
                     }
                 }
+                if (taskInfo1.isVisible) {
+                    numVisibleTasks++;
+                }
+                if (task2 != null) {
+                    Objects.requireNonNull(rawTask.getSplitBounds());
+                    final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
+                            convertShellSplitBoundsToLauncher(rawTask.getSplitBounds());
+                    allTasks.add(new SplitTask(task1, task2, launcherSplitBounds));
+                } else {
+                    allTasks.add(new SingleTask(task1));
+                }
             }
-            if (taskInfo1.isVisible) {
-                numVisibleTasks++;
-            }
-            final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
-                    convertShellSplitBoundsToLauncher(rawTask.getSplitBounds());
-            allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
         }
 
         return allTasks;
@@ -392,10 +424,6 @@
     private @Nullable DesktopTask createDesktopTask(GroupedTaskInfo recentTaskInfo) {
         ArrayList<Task> tasks = new ArrayList<>(recentTaskInfo.getTaskInfoList().size());
         int[] minimizedTaskIds = recentTaskInfo.getMinimizedTaskIds();
-        if (minimizedTaskIds.length == recentTaskInfo.getTaskInfoList().size()) {
-            // All Tasks are minimized -> don't create a DesktopTask
-            return null;
-        }
         for (TaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) {
             Task.TaskKey key = new Task.TaskKey(taskInfo);
             Task task = Task.from(key, taskInfo, false);
@@ -409,14 +437,6 @@
         return new DesktopTask(tasks);
     }
 
-    private ArrayList<GroupTask> copyOf(ArrayList<GroupTask> tasks) {
-        ArrayList<GroupTask> newTasks = new ArrayList<>();
-        for (int i = 0; i < tasks.size(); i++) {
-            newTasks.add(tasks.get(i).copy());
-        }
-        return newTasks;
-    }
-
     public void dump(String prefix, PrintWriter writer) {
         writer.println(prefix + "RecentTasksList:");
         writer.println(prefix + "  mChangeId=" + mChangeId);
@@ -439,14 +459,7 @@
         }
         writer.println(prefix + "  rawTasks=[");
         for (GroupedTaskInfo task : rawTasks) {
-            TaskInfo taskInfo1 = task.getTaskInfo1();
-            TaskInfo taskInfo2 = task.getTaskInfo2();
-            ComponentName cn1 = taskInfo1.topActivity;
-            ComponentName cn2 = taskInfo2 != null ? taskInfo2.topActivity : null;
-            writer.println(prefix + "    t1: (id=" + taskInfo1.taskId
-                    + "; package=" + (cn1 != null ? cn1.getPackageName() + ")" : "no package)")
-                    + " t2: (id=" + (taskInfo2 != null ? taskInfo2.taskId : "-1")
-                    + "; package=" + (cn2 != null ? cn2.getPackageName() + ")" : "no package)"));
+            writer.println(prefix + task);
         }
         writer.println(prefix + "  ]");
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index d4305a5..e1e962a 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -37,7 +37,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
@@ -64,12 +64,15 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.NavigationMode;
-import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SettingsCache;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
 import com.android.quickstep.util.ActiveGestureLog;
@@ -85,13 +88,14 @@
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
+
+import javax.inject.Inject;
 
 /**
  * Manages the state of the system during a swipe up gesture.
  */
-public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, ExclusionListener,
-        SafeCloseable {
+@LauncherAppSingleton
+public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, ExclusionListener {
 
     static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
 
@@ -99,8 +103,8 @@
     private static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 3f;
     private static final float QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL = 1.414f;
 
-    public static MainThreadInitializedObject<RecentsAnimationDeviceState> INSTANCE =
-            new MainThreadInitializedObject<>(RecentsAnimationDeviceState::new);
+    public static DaggerSingletonObject<RecentsAnimationDeviceState> INSTANCE =
+            new DaggerSingletonObject<>(LauncherAppComponent::getRecentsAnimationDeviceState);
 
     private final Context mContext;
     private final DisplayController mDisplayController;
@@ -110,12 +114,11 @@
 
     private final RotationTouchHelper mRotationTouchHelper;
     private final TaskStackChangeListener mPipListener;
+    private final DaggerSingletonTracker mLifeCycle;
     // Cache for better performance since it doesn't change at runtime.
     private final boolean mCanImeRenderGesturalNavButtons =
             InputMethodService.canImeRenderGesturalNavButtons();
 
-    private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
-
     private @SystemUiStateFlags long mSystemUiStateFlags = QuickStepContract.SYSUI_STATE_AWAKE;
     private NavigationMode mMode = THREE_BUTTONS;
     private NavBarPosition mNavBarPosition;
@@ -134,35 +137,39 @@
     private @NonNull Region mExclusionRegion = GestureExclusionManager.EMPTY_REGION;
     private boolean mExclusionListenerRegistered;
 
-    private RecentsAnimationDeviceState(Context context) {
-        this(context, GestureExclusionManager.INSTANCE);
-    }
-
     @VisibleForTesting
-    RecentsAnimationDeviceState(Context context, GestureExclusionManager exclusionManager) {
+    @Inject
+    RecentsAnimationDeviceState(
+            @ApplicationContext Context context,
+            GestureExclusionManager exclusionManager,
+            DisplayController displayController,
+            ContextualSearchStateManager contextualSearchStateManager,
+            RotationTouchHelper rotationTouchHelper,
+            SettingsCache settingsCache,
+            DaggerSingletonTracker lifeCycle) {
         mContext = context;
-        mDisplayController = DisplayController.INSTANCE.get(context);
+        mDisplayController = displayController;
         mExclusionManager = exclusionManager;
-        mContextualSearchStateManager = ContextualSearchStateManager.INSTANCE.get(context);
+        mContextualSearchStateManager = contextualSearchStateManager;
+        mRotationTouchHelper = rotationTouchHelper;
+        mLifeCycle = lifeCycle;
         mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
-        mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
 
         // Register for exclusion updates
-        runOnDestroy(this::unregisterExclusionListener);
+        mLifeCycle.addCloseable(this::unregisterExclusionListener);
 
         // Register for display changes changes
         mDisplayController.addChangeListener(this);
         onDisplayInfoChanged(context, mDisplayController.getInfo(), CHANGE_ALL);
-        runOnDestroy(() -> mDisplayController.removeChangeListener(this));
+        mLifeCycle.addCloseable(() -> mDisplayController.removeChangeListener(this));
 
-        SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
         if (mIsOneHandedModeSupported) {
             Uri oneHandedUri = Settings.Secure.getUriFor(ONE_HANDED_ENABLED);
             SettingsCache.OnChangeListener onChangeListener =
                     enabled -> mIsOneHandedModeEnabled = enabled;
             settingsCache.register(oneHandedUri, onChangeListener);
             mIsOneHandedModeEnabled = settingsCache.getValue(oneHandedUri);
-            runOnDestroy(() -> settingsCache.unregister(oneHandedUri, onChangeListener));
+            mLifeCycle.addCloseable(() -> settingsCache.unregister(oneHandedUri, onChangeListener));
         } else {
             mIsOneHandedModeEnabled = false;
         }
@@ -173,14 +180,16 @@
                 enabled -> mIsSwipeToNotificationEnabled = enabled;
         settingsCache.register(swipeBottomNotificationUri, onChangeListener);
         mIsSwipeToNotificationEnabled = settingsCache.getValue(swipeBottomNotificationUri);
-        runOnDestroy(() -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener));
+        mLifeCycle.addCloseable(
+                () -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener));
 
         Uri setupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE);
         mIsUserSetupComplete = settingsCache.getValue(setupCompleteUri, 0);
         if (!mIsUserSetupComplete) {
             SettingsCache.OnChangeListener userSetupChangeListener = e -> mIsUserSetupComplete = e;
             settingsCache.register(setupCompleteUri, userSetupChangeListener);
-            runOnDestroy(() -> settingsCache.unregister(setupCompleteUri, userSetupChangeListener));
+            mLifeCycle.addCloseable(
+                    () -> settingsCache.unregister(setupCompleteUri, userSetupChangeListener));
         }
 
         try {
@@ -201,21 +210,10 @@
             }
         };
         TaskStackChangeListeners.getInstance().registerTaskStackListener(mPipListener);
-        runOnDestroy(() ->
+        mLifeCycle.addCloseable(() ->
                 TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mPipListener));
     }
 
-    private void runOnDestroy(Runnable action) {
-        mOnDestroyActions.add(action);
-    }
-
-    @Override
-    public void close() {
-        for (Runnable r : mOnDestroyActions) {
-            r.run();
-        }
-    }
-
     /**
      * Adds a listener for the nav mode change, guaranteed to be called after the device state's
      * mode has changed.
@@ -228,7 +226,7 @@
         };
         mDisplayController.addChangeListener(listener);
         callback.run();
-        runOnDestroy(() -> mDisplayController.removeChangeListener(listener));
+        mLifeCycle.addCloseable(() -> mDisplayController.removeChangeListener(listener));
     }
 
     @Override
@@ -588,7 +586,7 @@
     /** Returns whether IME is rendering nav buttons, and IME is currently showing. */
     public boolean isImeRenderingNavButtons() {
         return mCanImeRenderGesturalNavButtons && mMode == NO_BUTTON
-                && ((mSystemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0);
+                && ((mSystemUiStateFlags & SYSUI_STATE_IME_VISIBLE) != 0);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/RecentsFilterState.java b/quickstep/src/com/android/quickstep/RecentsFilterState.java
index 70d5696..c4b0f25 100644
--- a/quickstep/src/com/android/quickstep/RecentsFilterState.java
+++ b/quickstep/src/com/android/quickstep/RecentsFilterState.java
@@ -19,6 +19,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.views.TaskViewType;
 import com.android.systemui.shared.recents.model.Task;
 
 import java.util.HashMap;
@@ -38,7 +39,7 @@
     public static final int MIN_FILTERING_TASK_COUNT = 2;
 
     // default filter that returns true for any input
-    public static final Predicate<GroupTask> DEFAULT_FILTER = (groupTask -> true);
+    public static final Predicate<GroupTask> EMPTY_FILTER = (groupTask -> true);
 
     // the package name to filter recent tasks by
     @Nullable
@@ -116,14 +117,37 @@
      * Returns a predicate for filtering out GroupTasks by package name.
      *
      * @param packageName package name to filter GroupTasks by
-     *                    if null, Predicate always returns true.
+     *                    if null, Predicate filters out desktop tasks with no non-minimized tasks.
      */
     public static Predicate<GroupTask> getFilter(@Nullable String packageName) {
         if (packageName == null) {
-            return DEFAULT_FILTER;
+            return getEmptyDesktopTaskFilter();
         }
 
-        return (groupTask) -> (groupTask.containsPackage(packageName));
+        return (groupTask) -> (groupTask.containsPackage(packageName)
+                && !isDestopTaskWithMinimizedTasksOnly(groupTask));
+    }
+
+    /**
+     * Returns a predicate that filters out desk tasks that contain no non-minimized desktop tasks.
+     */
+    public static Predicate<GroupTask> getEmptyDesktopTaskFilter() {
+        return (groupTask -> !isDestopTaskWithMinimizedTasksOnly(groupTask));
+    }
+
+    /**
+     * Whether the provided task is a desktop task with no non-minimized tasks - returns true if the
+     * desktop task has no tasks at all.
+     *
+     * @param groupTask The group task to check.
+     */
+    static boolean isDestopTaskWithMinimizedTasksOnly(GroupTask groupTask) {
+        if (groupTask.taskViewType != TaskViewType.DESKTOP) {
+            return false;
+        }
+        return groupTask.getTasks().stream()
+                .filter(task -> !task.isMinimized)
+                .toList().isEmpty();
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 6498b7a..dc5d59f 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -154,7 +154,7 @@
 
     /**
      * Fetches the list of recent tasks. Tasks are ordered by recency, with the latest active tasks
-     * at the end of the list.
+     * at the end of the list. Filters out desktop tasks that contain no non-minimized tasks.
      *
      * @param callback The callback to receive the task plan once its complete or null. This is
      *                always called on the UI thread.
@@ -163,7 +163,7 @@
     @Override
     public int getTasks(@Nullable Consumer<List<GroupTask>> callback) {
         return mTaskList.getTasks(false /* loadKeysOnly */, callback,
-                RecentsFilterState.DEFAULT_FILTER);
+                RecentsFilterState.getEmptyDesktopTaskFilter());
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 8edbacb..ef63b9b 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -142,7 +142,9 @@
         if (mSplitBounds == null) {
             SplitBounds shellSplitBounds = targets.extras.getParcelable(KEY_EXTRA_SPLIT_BOUNDS,
                     SplitBounds.class);
-            mSplitBounds = convertShellSplitBoundsToLauncher(shellSplitBounds);
+            if (shellSplitBounds != null) {
+                mSplitBounds = convertShellSplitBoundsToLauncher(shellSplitBounds);
+            }
         }
 
         boolean containsSplitTargets = mSplitBounds != null;
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index f54b655..a614327 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -33,13 +33,16 @@
 import android.view.MotionEvent;
 import android.view.OrientationEventListener;
 
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.NavigationMode;
-import com.android.launcher3.util.SafeCloseable;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.systemui.shared.Flags;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -48,16 +51,20 @@
 
 import java.io.PrintWriter;
 
+import javax.inject.Inject;
+
 /**
  * Helper class for transforming touch events
  */
-public class RotationTouchHelper implements DisplayInfoChangeListener, SafeCloseable {
+@LauncherAppSingleton
+public class RotationTouchHelper implements DisplayInfoChangeListener {
 
-    public static final MainThreadInitializedObject<RotationTouchHelper> INSTANCE =
-            new MainThreadInitializedObject<>(RotationTouchHelper::new);
+    public static final DaggerSingletonObject<RotationTouchHelper> INSTANCE =
+            new DaggerSingletonObject<>(LauncherAppComponent::getRotationTouchHelper);
 
     private final OrientationTouchTransformer mOrientationTouchTransformer;
     private final DisplayController mDisplayController;
+    private final SystemUiProxy mSystemUiProxy;
     private final int mDisplayId;
     private int mDisplayRotation;
 
@@ -127,12 +134,17 @@
     private boolean mTaskListFrozen;
     private final Context mContext;
 
-    private RotationTouchHelper(Context context) {
+    @Inject
+    RotationTouchHelper(@ApplicationContext Context context,
+            DisplayController displayController,
+            SystemUiProxy systemUiProxy,
+            DaggerSingletonTracker lifeCycle) {
         mContext = context;
-        mDisplayController = DisplayController.INSTANCE.get(mContext);
-        Resources resources = mContext.getResources();
+        mDisplayController = displayController;
+        mSystemUiProxy = systemUiProxy;
         mDisplayId = DEFAULT_DISPLAY;
 
+        Resources resources = mContext.getResources();
         mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
                 () -> QuickStepContract.getWindowCornerRadius(mContext));
 
@@ -160,14 +172,13 @@
                 }
             }
         };
-    }
 
-    @Override
-    public void close() {
-        mDisplayController.removeChangeListener(this);
-        mOrientationListener.disable();
-        TaskStackChangeListeners.getInstance()
-                .unregisterTaskStackListener(mFrozenTaskListener);
+        lifeCycle.addCloseable(() -> {
+            mDisplayController.removeChangeListener(this);
+            mOrientationListener.disable();
+            TaskStackChangeListeners.getInstance()
+                    .unregisterTaskStackListener(mFrozenTaskListener);
+        });
     }
 
     public boolean isTaskListFrozen() {
@@ -340,8 +351,7 @@
     }
 
     private void notifySysuiOfCurrentRotation(int rotation) {
-        UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext)
-                .notifyPrioritizedRotation(rotation));
+        UI_HELPER_EXECUTOR.execute(() -> mSystemUiProxy.notifyPrioritizedRotation(rotation));
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
index f2cedba..87953c7 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.kt
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
@@ -19,11 +19,9 @@
 import android.app.ActivityManager.RunningTaskInfo
 import android.app.ActivityOptions
 import android.app.PendingIntent
-import android.app.PictureInPictureParams
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
-import android.content.pm.ActivityInfo
 import android.content.pm.ShortcutInfo
 import android.graphics.Point
 import android.graphics.Rect
@@ -83,6 +81,7 @@
 import com.android.wm.shell.common.pip.IPipAnimationListener
 import com.android.wm.shell.desktopmode.IDesktopMode
 import com.android.wm.shell.desktopmode.IDesktopTaskListener
+import com.android.wm.shell.desktopmode.IMoveToDesktopCallback
 import com.android.wm.shell.draganddrop.IDragAndDrop
 import com.android.wm.shell.onehanded.IOneHanded
 import com.android.wm.shell.recents.IRecentTasks
@@ -95,6 +94,7 @@
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation.UpdateSource
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
+import com.android.wm.shell.shared.desktopmode.DesktopTaskToFrontReason
 import com.android.wm.shell.shared.split.SplitBounds
 import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition
 import com.android.wm.shell.splitscreen.ISplitScreen
@@ -170,7 +170,7 @@
      * different process). It is bare-bones, so it's expected that the component and options will be
      * provided via fill-in intent.
      */
-    private val recentsPendingIntent =
+    private val recentsPendingIntent by lazy {
         PendingIntent.getActivity(
             context,
             0,
@@ -184,6 +184,7 @@
                 )
                 .toBundle(),
         )
+    }
 
     val unfoldTransitionProvider: ProxyUnfoldTransitionProvider? =
         if ((Flags.enableUnfoldStateAnimation() && ResourceUnfoldTransitionConfig().isEnabled))
@@ -499,20 +500,12 @@
 
     /** @return Destination bounds of auto-pip animation, `null` if the animation is not ready. */
     fun startSwipePipToHome(
-        componentName: ComponentName?,
-        activityInfo: ActivityInfo?,
-        pictureInPictureParams: PictureInPictureParams?,
+        taskInfo: RunningTaskInfo,
         launcherRotation: Int,
         hotseatKeepClearArea: Rect?,
     ): Rect? {
         executeWithErrorLog({ "Failed call startSwipePipToHome" }) {
-            return pip?.startSwipePipToHome(
-                componentName,
-                activityInfo,
-                pictureInPictureParams,
-                launcherRotation,
-                hotseatKeepClearArea,
-            )
+            return pip?.startSwipePipToHome(taskInfo, launcherRotation, hotseatKeepClearArea)
         }
         return null
     }
@@ -673,8 +666,10 @@
      *
      * @param intent the intent used to create the bubble.
      */
-    fun showAppBubble(intent: Intent?) =
-        executeWithErrorLog({ "Failed call showAppBubble" }) { bubbles?.showAppBubble(intent) }
+    fun showAppBubble(intent: Intent?, user: UserHandle) =
+        executeWithErrorLog({ "Failed call showAppBubble" }) {
+            bubbles?.showAppBubble(intent, user)
+        }
 
     /** Tells SysUI to show the expanded view. */
     fun showExpandedView() =
@@ -838,6 +833,15 @@
             splitScreen?.startIntent(intent, userId, fillInIntent, position, options, instanceId)
         }
 
+    /**
+     * Call the desktop mode interface to start a TRANSIT_OPEN transition when launching an intent
+     * from the taskbar so that it can be handled in desktop mode.
+     */
+    fun startLaunchIntentTransition(intent: Intent, options: Bundle, displayId: Int) =
+        executeWithErrorLog({ "Failed call startLaunchIntentTransition" }) {
+            desktopMode?.startLaunchIntentTransition(intent, options, displayId)
+        }
+
     //
     // One handed
     //
@@ -1069,6 +1073,19 @@
     //
     // Desktop Mode
     //
+    /** Calls shell to create a new desk (if possible) on the display whose ID is `displayId`. */
+    fun createDesktop(displayId: Int) =
+        executeWithErrorLog({ "Failed call createDesk" }) { desktopMode?.createDesk(displayId) }
+
+    /**
+     * Calls shell to activate the desk whose ID is `deskId` on whatever display it exists on. This
+     * will bring all tasks on this desk to the front.
+     */
+    fun activateDesktop(deskId: Int, transition: RemoteTransition?) =
+        executeWithErrorLog({ "Failed call activateDesk" }) {
+            desktopMode?.activateDesk(deskId, transition)
+        }
+
     /** Call shell to show all apps active on the desktop */
     fun showDesktopApps(displayId: Int, transition: RemoteTransition?) =
         executeWithErrorLog({ "Failed call showDesktopApps" }) {
@@ -1076,19 +1093,15 @@
         }
 
     /** If task with the given id is on the desktop, bring it to front */
-    fun showDesktopApp(taskId: Int, transition: RemoteTransition?) =
+    fun showDesktopApp(
+        taskId: Int,
+        transition: RemoteTransition?,
+        toFrontReason: DesktopTaskToFrontReason,
+    ) =
         executeWithErrorLog({ "Failed call showDesktopApp" }) {
-            desktopMode?.showDesktopApp(taskId, transition)
+            desktopMode?.showDesktopApp(taskId, transition, toFrontReason)
         }
 
-    /** Call shell to get number of visible freeform tasks */
-    fun getVisibleDesktopTaskCount(displayId: Int): Int {
-        executeWithErrorLog({ "Failed call getVisibleDesktopTaskCount" }) {
-            return desktopMode?.getVisibleTaskCount(displayId) ?: 0
-        }
-        return 0
-    }
-
     /** Set a listener on shell to get updates about desktop task state */
     fun setDesktopTaskListener(listener: IDesktopTaskListener?) {
         desktopTaskListener = listener
@@ -1108,9 +1121,19 @@
         taskId: Int,
         transitionSource: DesktopModeTransitionSource?,
         transition: RemoteTransition?,
+        successCallback: Runnable,
     ) =
         executeWithErrorLog({ "Failed call moveToDesktop" }) {
-            desktopMode?.moveToDesktop(taskId, transitionSource, transition)
+            desktopMode?.moveToDesktop(
+                taskId,
+                transitionSource,
+                transition,
+                object : IMoveToDesktopCallback.Stub() {
+                    override fun onTaskMovedToDesktop() {
+                        successCallback.run()
+                    }
+                },
+            )
         }
 
     /** Call shell to remove the desktop that is on given `displayId` */
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index bfd6107..b3d9da3 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -24,12 +24,12 @@
 
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_A;
+import static com.android.wm.shell.Flags.enableShellTopTaskTracking;
 import static com.android.wm.shell.Flags.enableFlexibleSplit;
 import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
-import android.content.Context;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -37,7 +37,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
-import com.android.launcher3.dagger.ApplicationContext;
 import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.util.DaggerSingletonObject;
 import com.android.launcher3.util.DaggerSingletonTracker;
@@ -77,8 +76,6 @@
 
     private static final int HISTORY_SIZE = 5;
 
-    private final Context mContext;
-
     // Only used when Flags.enableShellTopTaskTracking() is disabled
     // Ordered list with first item being the most recent task.
     private final LinkedList<TaskInfo> mOrderedTaskList = new LinkedList<>();
@@ -87,20 +84,13 @@
     private int mPinnedTaskId = INVALID_TASK_ID;
 
     // Only used when Flags.enableShellTopTaskTracking() is enabled
-    // Mapping of display id to running tasks.  Running tasks are ordered from top most to
-    // bottom most.
-    private ArrayMap<Integer, ArrayList<GroupedTaskInfo>> mVisibleTasks = new ArrayMap<>();
+    // Mapping of display id to visible tasks.  Visible tasks are ordered from top most to bottom
+    // most.
+    private ArrayMap<Integer, GroupedTaskInfo> mVisibleTasks = new ArrayMap<>();
 
     @Inject
-    public TopTaskTracker(@ApplicationContext Context context, DaggerSingletonTracker tracker,
-            SystemUiProxy systemUiProxy) {
-        mContext = context;
-
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-            // Just prepopulate a list for the default display tasks so we don't need to add null
-            // checks everywhere
-            mVisibleTasks.put(DEFAULT_DISPLAY, new ArrayList<>());
-        } else {
+    public TopTaskTracker(DaggerSingletonTracker tracker, SystemUiProxy systemUiProxy) {
+        if (!enableShellTopTaskTracking()) {
             mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN;
             mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE;
 
@@ -109,7 +99,7 @@
         }
 
         tracker.addCloseable(() -> {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+            if (enableShellTopTaskTracking()) {
                 return;
             }
 
@@ -120,7 +110,7 @@
 
     @Override
     public void onTaskRemoved(int taskId) {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (enableShellTopTaskTracking()) {
             return;
         }
 
@@ -133,7 +123,7 @@
     }
 
     void handleTaskMovedToFront(TaskInfo taskInfo) {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (enableShellTopTaskTracking()) {
             return;
         }
 
@@ -187,32 +177,25 @@
      * Called when the set of visible tasks have changed.
      */
     public void onVisibleTasksChanged(GroupedTaskInfo[] visibleTasks) {
-        if (!com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (!enableShellTopTaskTracking()) {
             return;
         }
 
-        // TODO(346588978): Per-display info, just have everything in order by display
-
         // Clear existing tasks for each display
-        mVisibleTasks.forEach((displayId, visibleTasksOnDisplay) -> visibleTasksOnDisplay.clear());
+        mVisibleTasks.clear();
 
         // Update the visible tasks on each display
-        for (int i = 0; i < visibleTasks.length; i++) {
-            final int displayId = visibleTasks[i].getTaskInfo1().getDisplayId();
-            final ArrayList<GroupedTaskInfo> displayTasks;
-            if (mVisibleTasks.containsKey(displayId)) {
-                displayTasks = mVisibleTasks.get(displayId);
-            } else {
-                displayTasks = new ArrayList<>();
-                mVisibleTasks.put(displayId, displayTasks);
-            }
-            displayTasks.add(visibleTasks[i]);
+        Log.d(TAG, "onVisibleTasksChanged:");
+        for (GroupedTaskInfo groupedTask : visibleTasks) {
+            Log.d(TAG, "\t" + groupedTask);
+            final int displayId = groupedTask.getBaseGroupedTask().getTaskInfo1().getDisplayId();
+            mVisibleTasks.put(displayId, groupedTask);
         }
     }
 
     @Override
     public void onStagePositionChanged(@StageType int stage, @StagePosition int position) {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (enableShellTopTaskTracking()) {
             return;
         }
 
@@ -224,7 +207,7 @@
     }
 
     public void onTaskChanged(RunningTaskInfo taskInfo) {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (enableShellTopTaskTracking()) {
             return;
         }
 
@@ -238,7 +221,7 @@
 
     @Override
     public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (enableShellTopTaskTracking()) {
             return;
         }
 
@@ -262,7 +245,7 @@
 
     @Override
     public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (enableShellTopTaskTracking()) {
             return;
         }
 
@@ -271,7 +254,7 @@
 
     @Override
     public void onActivityUnpinned() {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (enableShellTopTaskTracking()) {
             return;
         }
 
@@ -279,16 +262,17 @@
     }
 
     /**
-     * @return index 0 will be task in left/top position, index 1 in right/bottom position.
-     * Will return empty array if device is not in staged split
+     * Return the running split task ids.  Index 0 will be task in left/top position, index 1 in
+     * right/bottom position, or and empty array if device is not in splitscreen.
      */
     public int[] getRunningSplitTaskIds() {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-            // TODO(346588978): This assumes default display for now
-            final ArrayList<GroupedTaskInfo> visibleTasks = mVisibleTasks.get(DEFAULT_DISPLAY);
-            final GroupedTaskInfo splitTaskInfo = visibleTasks.stream()
-                    .filter(taskInfo -> taskInfo.getType() == TYPE_SPLIT)
-                    .findFirst().orElse(null);
+        if (enableShellTopTaskTracking()) {
+            // TODO(346588978): This assumes default display as splitscreen is only currently there
+            final GroupedTaskInfo visibleTasks = mVisibleTasks.get(DEFAULT_DISPLAY);
+            final GroupedTaskInfo splitTaskInfo =
+                    visibleTasks != null && visibleTasks.isBaseType(TYPE_SPLIT)
+                            ? visibleTasks.getBaseGroupedTask()
+                            : null;
             if (splitTaskInfo != null && splitTaskInfo.getSplitBounds() != null) {
                 return new int[] {
                         splitTaskInfo.getSplitBounds().leftTopTaskId,
@@ -317,24 +301,13 @@
      * Dumps the list of tasks in top task tracker.
      */
     public void dump(PrintWriter pw) {
-        if (!com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (!enableShellTopTaskTracking()) {
             return;
         }
 
-        // TODO(346588978): This assumes default display for now
-        final ArrayList<GroupedTaskInfo> displayTasks = mVisibleTasks.get(DEFAULT_DISPLAY);
         pw.println("TopTaskTracker:");
-        pw.println("  tasks: [");
-        for (GroupedTaskInfo taskInfo : displayTasks) {
-            final TaskInfo info = taskInfo.getTaskInfo1();
-            final boolean isExcluded = (info.baseIntent.getFlags()
-                    & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
-            pw.println("    " + info.taskId + ": excluded=" + isExcluded
-                    + " visibleRequested=" + info.isVisibleRequested
-                    + " visible=" + info.isVisible
-                    + " " + info.baseIntent.getComponent());
-        }
-        pw.println("  ]");
+        mVisibleTasks.forEach((displayId, tasks) ->
+                pw.println("  visibleTasks(" + displayId + "): " + tasks));
     }
 
     /**
@@ -343,13 +316,12 @@
     @NonNull
     @UiThread
     public CachedTaskInfo getCachedTopTask(boolean filterOnlyVisibleRecents) {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (enableShellTopTaskTracking()) {
             // TODO(346588978): Currently ignore filterOnlyVisibleRecents, but perhaps make this an
             //  explicit filter For things to ignore (ie. PIP/Bubbles/Assistant/etc/so that this is
             //  explicit)
-            // TODO(346588978): This assumes default display for now (as does all of Launcher)
-            final ArrayList<GroupedTaskInfo> displayTasks = mVisibleTasks.get(DEFAULT_DISPLAY);
-            return new CachedTaskInfo(new ArrayList<>(displayTasks));
+            // TODO(346588978): This assumes default display as gesture nav is only supported there
+            return new CachedTaskInfo(mVisibleTasks.get(DEFAULT_DISPLAY));
         } else {
             if (filterOnlyVisibleRecents) {
                 // Since we only know about the top most task, any filtering may not be applied on
@@ -374,6 +346,11 @@
         }
     }
 
+    private static boolean isHomeTask(TaskInfo task) {
+        return task != null && task.configuration.windowConfiguration
+                .getActivityType() == ACTIVITY_TYPE_HOME;
+    }
+
     private static boolean isRecentsTask(TaskInfo task) {
         return task != null && task.configuration.windowConfiguration
                 .getActivityType() == ACTIVITY_TYPE_RECENTS;
@@ -384,7 +361,6 @@
      * during the lifecycle of the task.
      */
     public static class CachedTaskInfo {
-
         // Only used when enableShellTopTaskTracking() is disabled
         @Nullable
         private final TaskInfo mTopTask;
@@ -393,40 +369,48 @@
 
         // Only used when enableShellTopTaskTracking() is enabled
         @Nullable
-        private final GroupedTaskInfo mTopGroupedTask;
-        @Nullable
-        private final ArrayList<GroupedTaskInfo> mVisibleTasks;
+        private final GroupedTaskInfo mVisibleTasks;
 
 
         // Only used when enableShellTopTaskTracking() is enabled
-        CachedTaskInfo(@NonNull ArrayList<GroupedTaskInfo> visibleTasks) {
+        CachedTaskInfo(@Nullable GroupedTaskInfo visibleTasks) {
             mAllCachedTasks = null;
             mTopTask = null;
             mVisibleTasks = visibleTasks;
-            mTopGroupedTask = !mVisibleTasks.isEmpty() ? mVisibleTasks.getFirst() : null;
 
         }
 
         // Only used when enableShellTopTaskTracking() is disabled
         CachedTaskInfo(@NonNull List<TaskInfo> allCachedTasks) {
             mVisibleTasks = null;
-            mTopGroupedTask = null;
             mAllCachedTasks = allCachedTasks;
             mTopTask = allCachedTasks.isEmpty() ? null : allCachedTasks.get(0);
         }
 
         /**
-         * @return The list of visible tasks
+         * Returns the "base" task that is used the as the representative running task of the set
+         * of tasks initially provided.
+         *
+         * Not for general use, as in other windowing modes (ie. split/desktop) the caller should
+         * not make assumptions about there being a single base task.
+         * TODO(346588978): Try to remove all usage of this if possible
          */
-        public ArrayList<GroupedTaskInfo> getVisibleTasks() {
-            return mVisibleTasks;
+        @Nullable
+        private TaskInfo getLegacyBaseTask() {
+            if (enableShellTopTaskTracking()) {
+                return mVisibleTasks != null
+                        ? mVisibleTasks.getBaseGroupedTask().getTaskInfo1()
+                        : null;
+            } else {
+                return mTopTask;
+            }
         }
 
         /**
-         * @return The top task id
+         * Returns the top task id.
          */
         public int getTaskId() {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+            if (enableShellTopTaskTracking()) {
                 // Callers should use topGroupedTaskContainsTask() instead
                 return INVALID_TASK_ID;
             } else {
@@ -435,29 +419,58 @@
         }
 
         /**
-         * @return Whether the top grouped task contains the given {@param taskId} if
-         *         Flags.enableShellTopTaskTracking() is true, otherwise it checks the top
-         *         task as reported from TaskStackListener.
+         * Returns the top grouped task ids if Flags.enableShellTopTaskTracking() is true, otherwise
+         * an empty array.
+         */
+        public int[] topGroupedTaskIds() {
+            if (enableShellTopTaskTracking()) {
+                if (mVisibleTasks == null) {
+                    return new int[0];
+                }
+                List<TaskInfo> groupedTasks = mVisibleTasks.getTaskInfoList();
+                return groupedTasks.stream().mapToInt(
+                        groupedTask -> groupedTask.taskId).toArray();
+            } else {
+                // Not used
+                return new int[0];
+            }
+        }
+
+        /**
+         * Returns whether the top grouped task contains the given {@param taskId} if
+         * Flags.enableShellTopTaskTracking() is true, otherwise it checks the top task as reported
+         * from TaskStackListener.
          */
         public boolean topGroupedTaskContainsTask(int taskId) {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-                return mTopGroupedTask != null && mTopGroupedTask.containsTask(taskId);
+            if (enableShellTopTaskTracking()) {
+                return mVisibleTasks != null && mVisibleTasks.containsTask(taskId);
             } else {
                 return mTopTask != null && mTopTask.taskId == taskId;
             }
         }
 
         /**
-         * Returns true if the root of the task chooser activity
+         * Returns true if this represents the task chooser activity
          */
         public boolean isRootChooseActivity() {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-                // TODO(346588978): Update this to not make an assumption on a specific task info
-                return mTopGroupedTask != null && ACTION_CHOOSER.equals(
-                        mTopGroupedTask.getTaskInfo1().baseIntent.getAction());
-            } else {
-                return mTopTask != null && ACTION_CHOOSER.equals(mTopTask.baseIntent.getAction());
-            }
+            final TaskInfo baseTask = getLegacyBaseTask();
+            return baseTask != null && ACTION_CHOOSER.equals(baseTask.baseIntent.getAction());
+        }
+
+        /**
+         * Returns true if this represents the HOME activity type task
+         */
+        public boolean isHomeTask() {
+            final TaskInfo baseTask = getLegacyBaseTask();
+            return baseTask != null && TopTaskTracker.isHomeTask(baseTask);
+        }
+
+        /**
+         * Returns true if this represents the RECENTS activity type task
+         */
+        public boolean isRecentsTask() {
+            final TaskInfo baseTask = getLegacyBaseTask();
+            return baseTask != null && TopTaskTracker.isRecentsTask(baseTask);
         }
 
         /**
@@ -465,7 +478,7 @@
          * is another running task that is not excluded from recents, returns that underlying task.
          */
         public @Nullable CachedTaskInfo getVisibleNonExcludedTask() {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+            if (enableShellTopTaskTracking()) {
                 // Callers should not need this when the full set of visible tasks are provided
                 return null;
             }
@@ -485,49 +498,16 @@
         }
 
         /**
-         * Returns true if this represents the HOME activity type task
-         */
-        public boolean isHomeTask() {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-                // TODO(346588978): Update this to not make an assumption on a specific task info
-                return mTopGroupedTask != null
-                        && mTopGroupedTask.getTaskInfo1().getActivityType() == ACTIVITY_TYPE_HOME;
-            } else {
-                return mTopTask != null && mTopTask.configuration.windowConfiguration
-                        .getActivityType() == ACTIVITY_TYPE_HOME;
-            }
-        }
-
-        /**
-         * Returns true if this represents the RECENTS activity type task
-         */
-        public boolean isRecentsTask() {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-                // TODO(346588978): Update this to not make an assumption on a specific task info
-                return mTopGroupedTask != null
-                        && TopTaskTracker.isRecentsTask(mTopGroupedTask.getTaskInfo1());
-            } else {
-                return TopTaskTracker.isRecentsTask(mTopTask);
-            }
-        }
-
-        /**
          * Returns {@link Task} array which can be used as a placeholder until the true object
          * is loaded by the model
          */
         public Task[] getPlaceholderTasks() {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-                // TODO(346588978): Update this to return more than a single task once the callers
-                //  are refactored
-                if (mVisibleTasks.isEmpty()) {
-                    return new Task[0];
-                }
-                final TaskInfo info = mVisibleTasks.getFirst().getTaskInfo1();
-                return new Task[]{Task.from(new TaskKey(info), info, false)};
-            } else {
-                return mTopTask == null ? new Task[0]
-                        : new Task[]{Task.from(new TaskKey(mTopTask), mTopTask, false)};
-            }
+            final TaskInfo baseTask = getLegacyBaseTask();
+            // TODO(346588978): Update this to return more than a single task once the callers
+            //  are refactored
+            return baseTask == null
+                    ? new Task[0]
+                    : new Task[]{Task.from(new TaskKey(baseTask), baseTask, false)};
         }
 
         /**
@@ -535,13 +515,12 @@
          * placeholder until the true object is loaded by the model
          */
         public Task[] getSplitPlaceholderTasks(int[] taskIds) {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-                if (mVisibleTasks.isEmpty()
-                        || mVisibleTasks.getFirst().getType() != TYPE_SPLIT) {
+            if (enableShellTopTaskTracking()) {
+                if (mVisibleTasks == null || !mVisibleTasks.isBaseType(TYPE_SPLIT)) {
                     return new Task[0];
                 }
 
-                GroupedTaskInfo splitTask = mVisibleTasks.getFirst();
+                GroupedTaskInfo splitTask = mVisibleTasks.getBaseGroupedTask();
                 Task[] result = new Task[taskIds.length];
                 for (int i = 0; i < taskIds.length; i++) {
                     TaskInfo info = splitTask.getTaskById(taskIds[i]);
@@ -572,22 +551,11 @@
 
         @Nullable
         public String getPackageName() {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-                // TODO(346588978): Update this to not make an assumption on a specific task info
-                if (mTopGroupedTask == null) {
-                    return null;
-                }
-                final TaskInfo info = mTopGroupedTask.getTaskInfo1();
-                if (info.baseActivity == null) {
-                    return null;
-                }
-                return info.baseActivity.getPackageName();
-            } else {
-                if (mTopTask == null || mTopTask.baseActivity == null) {
-                    return null;
-                }
-                return mTopTask.baseActivity.getPackageName();
+            final TaskInfo baseTask = getLegacyBaseTask();
+            if (baseTask == null || baseTask.baseActivity == null) {
+                return null;
             }
+            return baseTask.baseActivity.getPackageName();
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index f7fb18b..33ba780 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -301,6 +301,33 @@
 
         @BinderThread
         @Override
+        public void onDisplayReady(int displayId) {
+            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
+                    tis -> executeForTaskbarManager(
+                            taskbarManager -> taskbarManager.onDisplayReady(displayId))));
+        }
+
+        @BinderThread
+        @Override
+        public void onDisplayRemoved(int displayId) {
+            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
+                    tis -> executeForTaskbarManager(
+                            taskbarManager -> taskbarManager.onDisplayRemoved(displayId))));
+        }
+
+        @BinderThread
+        @Override
+        public void onDisplayRemoveSystemDecorations(int displayId) {
+            // TODO(b/391786915): Replace all
+            // `executeForTouchInteractionService(executeForTaskbarManager())` with just
+            // `executeForTaskbarManager` directly (since `tis` is unused).
+            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
+                    tis -> executeForTaskbarManager(taskbarManager -> taskbarManager
+                            .onDisplayRemoveSystemDecorations(displayId))));
+        }
+
+        @BinderThread
+        @Override
         public void updateWallpaperVisibility(int displayId, boolean visible) {
             MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
                     tis -> executeForTaskbarManager(
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
index 1d40d76..fe25f32 100644
--- a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
+++ b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
@@ -21,10 +21,13 @@
 import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RotationTouchHelper;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TopTaskTracker;
 import com.android.quickstep.fallback.window.RecentsDisplayModel;
 import com.android.quickstep.util.AsyncClockEventDelegate;
+import com.android.quickstep.util.ContextualSearchStateManager;
 
 /**
  * Launcher Quickstep base component for Dagger injection.
@@ -49,4 +52,10 @@
     DesktopVisibilityController getDesktopVisibilityController();
 
     TopTaskTracker getTopTaskTracker();
+
+    RotationTouchHelper getRotationTouchHelper();
+
+    ContextualSearchStateManager getContextualSearchStateManager();
+
+    RecentsAnimationDeviceState getRecentsAnimationDeviceState();
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index d9209bf..fff7e9b 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -33,7 +33,6 @@
 import com.android.launcher3.Flags;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.desktop.DesktopRecentsTransitionController;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.statehandlers.DesktopVisibilityController;
@@ -47,6 +46,7 @@
 import com.android.quickstep.GestureState;
 import com.android.quickstep.fallback.window.RecentsDisplayModel;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.views.OverviewActionsView;
@@ -210,7 +210,7 @@
             if (!found) {
                 ArrayList<GroupTask> newList = new ArrayList<>(taskGroups.size() + 1);
                 newList.addAll(taskGroups);
-                newList.add(new GroupTask(mHomeTask, null, null));
+                newList.add(new SingleTask(mHomeTask));
                 taskGroups = newList;
             }
         }
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java b/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
index a2884b6..7e5afc3 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
@@ -15,9 +15,15 @@
  */
 package com.android.quickstep.fallback;
 
+import static com.android.launcher3.Flags.enableExpressiveDismissTaskMotion;
+
 import android.content.Context;
 import android.util.AttributeSet;
 
+import com.android.launcher3.statemanager.StatefulContainer;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewRecentsTouchContext;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchControllerDeprecated;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.views.RecentsViewContainer;
@@ -25,16 +31,33 @@
 /**
  * Drag layer for fallback recents activity
  */
-public class RecentsDragLayer<T extends Context & RecentsViewContainer> extends BaseDragLayer<T> {
+public class RecentsDragLayer<T extends Context & RecentsViewContainer
+        & StatefulContainer<RecentsState>> extends BaseDragLayer<T> {
+
+    private final TaskViewRecentsTouchContext mTaskViewRecentsTouchContext =
+            new TaskViewRecentsTouchContext() {
+                @Override
+                public boolean isRecentsInteractive() {
+                    return mContainer.getRootView().hasWindowFocus()
+                            || mContainer.getStateManager().getState().hasLiveTile();
+                }
+
+                @Override
+                public boolean isRecentsModal() {
+                    return false;
+                }
+            };
+
     public RecentsDragLayer(Context context, AttributeSet attrs) {
         super(context, attrs, 1 /* alphaChannelCount */);
     }
 
     @Override
     public void recreateControllers() {
-        mControllers = new TouchController[] {
-                new RecentsTaskController(mContainer),
-                new FallbackNavBarTouchController(mContainer),
-        };
+        mControllers = new TouchController[]{
+                enableExpressiveDismissTaskMotion() ? new TaskViewTouchController<>(mContainer,
+                        mTaskViewRecentsTouchContext) : new TaskViewTouchControllerDeprecated<>(
+                        mContainer, mTaskViewRecentsTouchContext),
+                new FallbackNavBarTouchController(mContainer)};
     }
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java b/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
deleted file mode 100644
index 07da379..0000000
--- a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.fallback;
-
-import android.content.Context;
-
-import com.android.launcher3.statemanager.StatefulContainer;
-import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
-import com.android.quickstep.views.RecentsViewContainer;
-
-public class RecentsTaskController<T extends Context & RecentsViewContainer &
-        StatefulContainer<RecentsState>> extends TaskViewTouchController<T> {
-    public RecentsTaskController(T container) {
-        super(container);
-    }
-
-    @Override
-    protected boolean isRecentsInteractive() {
-        return mContainer.getRootView().hasWindowFocus()
-                || mContainer.getStateManager().getState().hasLiveTile();
-    }
-
-    @Override
-    protected boolean isRecentsModal() {
-        return false;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
index e7e9f51..31a1be8 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
@@ -25,6 +25,7 @@
 import com.android.launcher3.util.DaggerSingletonObject
 import com.android.launcher3.util.DaggerSingletonTracker
 import com.android.launcher3.util.Executors
+import com.android.launcher3.util.WallpaperColorHints
 import com.android.quickstep.DisplayModel
 import com.android.quickstep.FallbackWindowInterface
 import com.android.quickstep.dagger.QuickstepBaseAppComponent
@@ -34,8 +35,11 @@
 @LauncherAppSingleton
 class RecentsDisplayModel
 @Inject
-constructor(@ApplicationContext context: Context, tracker: DaggerSingletonTracker) :
-    DisplayModel<RecentsDisplayResource>(context) {
+constructor(
+    @ApplicationContext context: Context,
+    private val wallpaperColorHints: WallpaperColorHints,
+    tracker: DaggerSingletonTracker,
+) : DisplayModel<RecentsDisplayResource>(context) {
 
     companion object {
         private const val TAG = "RecentsDisplayModel"
@@ -81,7 +85,11 @@
 
     private fun storeRecentsDisplayResource(displayId: Int, display: Display) {
         displayResourceArray[displayId] =
-            RecentsDisplayResource(displayId, context.createDisplayContext(display))
+            RecentsDisplayResource(
+                displayId,
+                context.createDisplayContext(display),
+                wallpaperColorHints.hints,
+            )
     }
 
     fun getRecentsWindowManager(displayId: Int): RecentsWindowManager? {
@@ -92,9 +100,12 @@
         return getDisplayResource(displayId)?.fallbackWindowInterface
     }
 
-    data class RecentsDisplayResource(var displayId: Int, var displayContext: Context) :
-        DisplayResource() {
-        val recentsWindowManager = RecentsWindowManager(displayContext)
+    data class RecentsDisplayResource(
+        var displayId: Int,
+        var displayContext: Context,
+        val wallpaperColorHints: Int,
+    ) : DisplayResource() {
+        val recentsWindowManager = RecentsWindowManager(displayContext, wallpaperColorHints)
         val fallbackWindowInterface: FallbackWindowInterface =
             FallbackWindowInterface(recentsWindowManager)
 
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
index 52a7682..047658c 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
@@ -32,11 +32,16 @@
 
 /**
  * Window context for the Overview overlays.
+ *
  * <p>
  * Overlays have their own window and need a window context.
  */
-open class RecentsWindowContext(windowContext: Context) :
-    ContextThemeWrapper(windowContext, Themes.getActivityThemeRes(windowContext)), ActivityContext {
+open class RecentsWindowContext(windowContext: Context, wallpaperColorHints: Int) :
+    ContextThemeWrapper(
+        windowContext,
+        Themes.getActivityThemeRes(windowContext, wallpaperColorHints),
+    ),
+    ActivityContext {
 
     private var deviceProfile: DeviceProfile? = null
     private var dragLayer: RecentsDragLayer<RecentsWindowManager> = RecentsDragLayer(this, null)
@@ -48,7 +53,9 @@
 
     protected var windowLayoutParams: WindowManager.LayoutParams? =
         createDefaultWindowLayoutParams(
-            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, windowTitle)
+            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+            windowTitle,
+        )
 
     override fun getDragLayer(): BaseDragLayer<RecentsWindowManager> {
         return dragLayer
@@ -56,8 +63,7 @@
 
     override fun getDeviceProfile(): DeviceProfile {
         if (deviceProfile == null) {
-            deviceProfile = InvariantDeviceProfile.INSTANCE[this].getDeviceProfile(this)
-                .copy(this)
+            deviceProfile = InvariantDeviceProfile.INSTANCE[this].getDeviceProfile(this).copy(this)
         }
         return deviceProfile!!
     }
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
index 5d99aec..cda6c1b 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
@@ -88,8 +88,10 @@
  * To add new protologs, see [RecentsWindowProtoLogProxy]. To enable logging to logcat, see
  * [QuickstepProtoLogGroup.Constants.DEBUG_RECENTS_WINDOW]
  */
-class RecentsWindowManager(context: Context) :
-    RecentsWindowContext(context), RecentsViewContainer, StatefulContainer<RecentsState> {
+class RecentsWindowManager(context: Context, wallpaperColorHints: Int) :
+    RecentsWindowContext(context, wallpaperColorHints),
+    RecentsViewContainer,
+    StatefulContainer<RecentsState> {
 
     companion object {
         private const val HOME_APPEAR_DURATION: Long = 250
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 9bfe71f..b2a30ca 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -31,15 +31,15 @@
 import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_0;
-import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_90;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_180;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_270;
+import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_90;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__ALLAPPS;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__BACKGROUND;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW;
-import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__PORTRAIT;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__LANDSCAPE;
+import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__PORTRAIT;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__SEASCAPE;
 
 import android.content.Context;
@@ -69,6 +69,7 @@
 import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Executors;
@@ -386,7 +387,8 @@
                 // and then write to StatsLog.
                 app.getModel().enqueueModelUpdateTask((taskController, dataModel, apps) ->
                         write(event, applyOverwrites(mItemInfo.buildProto(
-                                dataModel.collections.get(mItemInfo.container), mContext))));
+                                (CollectionInfo) dataModel.itemsIdMap.get(mItemInfo.container),
+                                mContext))));
             })) {
                 // Write log on the model thread so that logs do not go out of order
                 // (for eg: drop comes after drag)
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index 703d631..002a4e8 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -17,7 +17,6 @@
 package com.android.quickstep.recents.data
 
 import android.graphics.drawable.Drawable
-import android.graphics.drawable.ShapeDrawable
 import android.util.Log
 import com.android.launcher3.util.coroutines.DispatcherProvider
 import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskIconChangedCallback
@@ -52,33 +51,29 @@
     override fun getAllTaskData(forceRefresh: Boolean): Flow<List<Task>> {
         if (forceRefresh) {
             recentsModel.getTasks { newTaskList ->
-                val oldTaskMap = tasks.value
                 val recentTasks =
                     newTaskList
                         .flatMap { groupTask -> groupTask.tasks }
                         .associateBy { it.key.id }
                         .also { newTaskMap ->
                             // Clean tasks that are not in the latest group tasks list.
-                            val tasksNoLongerVisible = oldTaskMap.keys.subtract(newTaskMap.keys)
+                            val tasksNoLongerVisible = tasks.value.keys.subtract(newTaskMap.keys)
                             removeTasks(tasksNoLongerVisible)
-
-                            // Use pre-loaded thumbnail data and icon from the previous list.
-                            // This reduces the Thumbnail loading time in the Overview and prevent
-                            // empty thumbnail and icon.
-                            val cache =
-                                taskRequests.keys
-                                    .mapNotNull { key ->
-                                        val task = oldTaskMap[key] ?: return@mapNotNull null
-                                        key to Pair(task.thumbnail, task.icon)
-                                    }
-                                    .toMap()
-
-                            newTaskMap.values.forEach { task ->
-                                task.thumbnail = task.thumbnail ?: cache[task.key.id]?.first
-                                task.icon = task.icon ?: cache[task.key.id]?.second
-                            }
                         }
+                Log.d(
+                    TAG,
+                    "getAllTaskData: oldTasks ${tasks.value.keys}, newTasks: ${recentTasks.keys}",
+                )
                 tasks.value = MapForStateFlow(recentTasks)
+
+                // Request data for completed tasks to prevent stale data.
+                // This will prevent thumbnail and icon from being replaced and
+                // null due to race condition.
+                taskRequests.values.forEach { (taskKey, job) ->
+                    if (job.isCompleted) {
+                        requestTaskData(taskKey.id)
+                    }
+                }
             }
         }
         return tasks.map { it.values.toList() }
@@ -198,13 +193,11 @@
     private suspend fun getIconFromDataSource(task: Task) =
         withContext(dispatcherProvider.background) {
             val iconCacheEntry = taskIconDataSource.getIcon(task)
-            val icon = iconCacheEntry.icon.constantState?.newDrawable()?.mutate() ?: EMPTY_DRAWABLE
-            IconData(icon, iconCacheEntry.contentDescription, iconCacheEntry.title)
+            IconData(iconCacheEntry.icon, iconCacheEntry.contentDescription, iconCacheEntry.title)
         }
 
     companion object {
         private const val TAG = "TasksRepository"
-        private val EMPTY_DRAWABLE = ShapeDrawable()
     }
 
     /** Helper class to support StateFlow emissions when using a Map with a MutableStateFlow. */
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index 2b364f9..0a47338 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -26,6 +26,7 @@
 import com.android.quickstep.recents.data.TaskVisualsChangedDelegate
 import com.android.quickstep.recents.data.TaskVisualsChangedDelegateImpl
 import com.android.quickstep.recents.data.TasksRepository
+import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
 import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
 import com.android.quickstep.recents.usecase.GetThumbnailUseCase
 import com.android.quickstep.recents.usecase.SysUiStatusNavFlagsUseCase
@@ -181,8 +182,6 @@
                         taskContainerData = inject(scopeId),
                         dispatcherProvider = inject(),
                         getThumbnailPositionUseCase = inject(),
-                        tasksRepository = inject(),
-                        deviceProfileRepository = inject(),
                         splashAlphaUseCase = inject(scopeId),
                     )
                 TaskOverlayViewModel::class.java -> {
@@ -195,6 +194,7 @@
                         dispatcherProvider = inject(),
                     )
                 }
+                GetTaskUseCase::class.java -> GetTaskUseCase(repository = inject())
                 GetThumbnailUseCase::class.java -> GetThumbnailUseCase(taskRepository = inject())
                 SysUiStatusNavFlagsUseCase::class.java ->
                     SysUiStatusNavFlagsUseCase(taskRepository = inject())
@@ -252,6 +252,7 @@
         fun initialize(view: View): RecentsDependencies = initialize(view.context)
 
         fun initialize(context: Context): RecentsDependencies {
+            Log.d(TAG, "initializing")
             synchronized(this) {
                 activeRecentsCount++
                 instance = RecentsDependencies(context.applicationContext)
@@ -286,10 +287,12 @@
             activeRecentsCount--
             if (activeRecentsCount == 0) {
                 instance.scopes.clear()
+                Log.d(TAG, "destroyed", Exception("Printing stack trace"))
             } else {
-                instance.log(
+                Log.d(
+                    TAG,
                     "RecentsDependencies was not destroyed. " +
-                        "There is still an active RecentsView instance."
+                        "There is still an active RecentsView instance.",
                 )
             }
         }
diff --git a/quickstep/src/com/android/quickstep/recents/domain/model/TaskModel.kt b/quickstep/src/com/android/quickstep/recents/domain/model/TaskModel.kt
new file mode 100644
index 0000000..bf29b1d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/domain/model/TaskModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2025 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.recents.domain.model
+
+import android.graphics.drawable.Drawable
+import com.android.systemui.shared.recents.model.ThumbnailData
+
+/**
+ * Data class representing a task in the application.
+ *
+ * This class holds the essential information about a task, including its unique identifier, display
+ * title, associated icon, optional thumbnail data, and background color.
+ *
+ * @property id The unique identifier for this task. Must be an integer.
+ * @property title The display title of the task.
+ * @property titleDescription A content description of the task.
+ * @property icon An optional drawable resource representing an icon for the task. Can be null if no
+ *   icon is required.
+ * @property thumbnail An optional [ThumbnailData] object containing thumbnail information. Can be
+ *   null if no thumbnail is needed.
+ * @property backgroundColor The background color of the task, represented as an integer color
+ *   value.
+ * @property isLocked Indicates whether the [Task] is locked.
+ */
+data class TaskModel(
+    val id: TaskId,
+    val title: String?,
+    val titleDescription: String?,
+    val icon: Drawable?,
+    val thumbnail: ThumbnailData?,
+    val backgroundColor: Int,
+    val isLocked: Boolean,
+)
+
+typealias TaskId = Int
diff --git a/quickstep/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCase.kt b/quickstep/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCase.kt
new file mode 100644
index 0000000..a60144b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCase.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2025 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.recents.domain.usecase
+
+import com.android.quickstep.recents.data.RecentTasksRepository
+import com.android.quickstep.recents.domain.model.TaskModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class GetTaskUseCase(private val repository: RecentTasksRepository) {
+    operator fun invoke(taskId: Int): Flow<TaskModel?> =
+        repository.getTaskDataById(taskId).map { task ->
+            if (task != null) {
+                TaskModel(
+                    id = task.key.id,
+                    title = task.title,
+                    titleDescription = task.titleDescription,
+                    icon = task.icon,
+                    thumbnail = task.thumbnail,
+                    backgroundColor = task.colorBackground,
+                    isLocked = task.isLocked,
+                )
+            } else {
+                null
+            }
+        }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
new file mode 100644
index 0000000..fb62268
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2025 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.recents.ui.mapper
+
+import com.android.quickstep.recents.ui.viewmodel.TaskData
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
+
+object TaskUiStateMapper {
+
+    /**
+     * Converts a [TaskData] object into a [TaskThumbnailUiState] for display in the UI.
+     *
+     * This function handles different types of [TaskData] and determines the appropriate UI state
+     * based on the data and provided flags.
+     *
+     * @param taskData The [TaskData] to convert. Can be null or a specific subclass.
+     * @param isLiveTile A flag indicating whether the task data represents live tile.
+     * @param hasHeader A flag indicating whether the UI should display a header.
+     * @return A [TaskThumbnailUiState] representing the UI state for the given task data.
+     */
+    fun toTaskThumbnailUiState(
+        taskData: TaskData?,
+        isLiveTile: Boolean,
+        hasHeader: Boolean,
+    ): TaskThumbnailUiState =
+        when {
+            taskData !is TaskData.Data -> Uninitialized
+            isLiveTile -> createLiveTileState(taskData, hasHeader)
+            isBackgroundOnly(taskData) -> BackgroundOnly(taskData.backgroundColor)
+            isSnapshotSplash(taskData) ->
+                SnapshotSplash(createSnapshotState(taskData, hasHeader), taskData.icon)
+            else -> Uninitialized
+        }
+
+    private fun createSnapshotState(taskData: TaskData.Data, hasHeader: Boolean): Snapshot =
+        if (canHeaderBeCreated(taskData, hasHeader)) {
+            Snapshot.WithHeader(
+                taskData.thumbnailData?.thumbnail!!,
+                taskData.thumbnailData.rotation,
+                taskData.backgroundColor,
+                ThumbnailHeader(taskData.icon!!, taskData.titleDescription!!),
+            )
+        } else {
+            Snapshot.WithoutHeader(
+                taskData.thumbnailData?.thumbnail!!,
+                taskData.thumbnailData.rotation,
+                taskData.backgroundColor,
+            )
+        }
+
+    private fun isBackgroundOnly(taskData: TaskData.Data) =
+        taskData.isLocked || taskData.thumbnailData == null
+
+    private fun isSnapshotSplash(taskData: TaskData.Data) =
+        taskData.thumbnailData?.thumbnail != null && !taskData.isLocked
+
+    private fun canHeaderBeCreated(taskData: TaskData.Data, hasHeader: Boolean) =
+        hasHeader && taskData.icon != null && taskData.titleDescription != null
+
+    private fun createLiveTileState(taskData: TaskData.Data, hasHeader: Boolean) =
+        if (canHeaderBeCreated(taskData, hasHeader)) {
+            // TODO(http://b/353965691): figure out what to do when `icon` or `titleDescription` is
+            //  null.
+            LiveTile.WithHeader(ThumbnailHeader(taskData.icon!!, taskData.titleDescription!!))
+        } else LiveTile.WithoutHeader
+}
diff --git a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt
new file mode 100644
index 0000000..54b2389
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2025 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.recents.ui.viewmodel
+
+import android.graphics.drawable.Drawable
+import com.android.systemui.shared.recents.model.ThumbnailData
+
+/**
+ * This class represents the UI state to be consumed by TaskView, GroupTaskView and DesktopTaskView.
+ * Data class representing the state of a list of tasks.
+ *
+ * This class encapsulates a list of [TaskTileUiState] objects, along with a flag indicating whether
+ * the data is being used for a live tile display.
+ *
+ * @property tasks The list of [TaskTileUiState] objects representing the individual tasks.
+ * @property isLiveTile Indicates whether this data is intended for a live tile. If `true`, the
+ *   running app will be displayed instead of the thumbnail.
+ */
+data class TaskTileUiState(
+    val tasks: List<TaskData>,
+    val isLiveTile: Boolean,
+    val hasHeader: Boolean,
+)
+
+sealed class TaskData {
+    abstract val taskId: Int
+
+    /** When no data was found for the TaskId provided */
+    data class NoData(override val taskId: Int) : TaskData()
+
+    /**
+     * This class provides UI information related to a Task (App) to be displayed within a TaskView.
+     *
+     * @property taskId Identifier of the task
+     * @property title App title
+     * @property titleDescription App content description
+     * @property icon App icon
+     * @property thumbnailData Information related to the last snapshot retrieved from the app
+     * @property backgroundColor The background color of the task.
+     * @property isLocked Indicates whether the task is locked or not.
+     */
+    data class Data(
+        override val taskId: Int,
+        val title: String?,
+        val titleDescription: String?,
+        val icon: Drawable?,
+        val thumbnailData: ThumbnailData?,
+        val backgroundColor: Int,
+        val isLocked: Boolean,
+    ) : TaskData()
+}
diff --git a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
new file mode 100644
index 0000000..b2806f0
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2025 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.recents.ui.viewmodel
+
+import android.annotation.ColorInt
+import android.util.Log
+import androidx.core.graphics.ColorUtils
+import com.android.launcher3.util.coroutines.DispatcherProvider
+import com.android.quickstep.recents.domain.model.TaskId
+import com.android.quickstep.recents.domain.model.TaskModel
+import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
+import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.views.TaskViewType
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/**
+ * ViewModel used for [com.android.quickstep.views.TaskView],
+ * [com.android.quickstep.views.DesktopTaskView] and [com.android.quickstep.views.GroupedTaskView].
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class TaskViewModel(
+    private val taskViewType: TaskViewType,
+    recentsViewData: RecentsViewData,
+    private val getTaskUseCase: GetTaskUseCase,
+    dispatcherProvider: DispatcherProvider,
+) {
+    private var taskIds = MutableStateFlow(emptySet<Int>())
+
+    private val isLiveTile =
+        combine(
+                taskIds,
+                recentsViewData.runningTaskIds,
+                recentsViewData.runningTaskShowScreenshot,
+            ) { taskIds, runningTaskIds, runningTaskShowScreenshot ->
+                runningTaskIds == taskIds && !runningTaskShowScreenshot
+            }
+            .distinctUntilChanged()
+
+    val state: Flow<TaskTileUiState> =
+        taskIds
+            .flatMapLatest { ids ->
+                // Combine Tasks requests
+                combine(
+                    ids.map { id -> getTaskUseCase(id).map { taskModel -> id to taskModel } },
+                    ::mapToUiState,
+                )
+            }
+            .combine(isLiveTile) { tasks, isLiveTile ->
+                TaskTileUiState(
+                    tasks = tasks,
+                    isLiveTile = isLiveTile,
+                    hasHeader = taskViewType == TaskViewType.DESKTOP,
+                )
+            }
+            .distinctUntilChanged()
+            .flowOn(dispatcherProvider.background)
+
+    fun bind(vararg taskId: TaskId) {
+        Log.d(TAG, "bind: $taskId")
+        taskIds.value = taskId.toSet()
+    }
+
+    private fun mapToUiState(result: Array<Pair<TaskId, TaskModel?>>): List<TaskData> =
+        result.map { mapToUiState(it.first, it.second) }
+
+    private fun mapToUiState(taskId: TaskId, result: TaskModel?): TaskData =
+        result?.let {
+            TaskData.Data(
+                taskId = taskId,
+                title = result.title,
+                titleDescription = result.titleDescription,
+                icon = result.icon,
+                thumbnailData = result.thumbnail,
+                backgroundColor = result.backgroundColor.removeAlpha(),
+                isLocked = result.isLocked,
+            )
+        } ?: TaskData.NoData(taskId)
+
+    @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff)
+
+    private companion object {
+        const val TAG = "TaskViewModel"
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 639d3a7..4a990b3 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -20,6 +20,7 @@
 import android.graphics.Color
 import android.graphics.Outline
 import android.graphics.Rect
+import android.graphics.drawable.ShapeDrawable
 import android.util.AttributeSet
 import android.util.Log
 import android.view.LayoutInflater
@@ -108,21 +109,6 @@
         viewData = RecentsDependencies.get(this)
         updateViewDataValues()
         viewModel = RecentsDependencies.get(this)
-        viewModel.uiState
-            .dropWhile { it == Uninitialized }
-            .flowOn(dispatcherProvider.background)
-            .onEach { viewModelUiState ->
-                Log.d(TAG, "viewModelUiState changed from: $uiState to: $viewModelUiState")
-                uiState = viewModelUiState
-                resetViews()
-                when (viewModelUiState) {
-                    is Uninitialized -> {}
-                    is LiveTile -> drawLiveWindow(viewModelUiState)
-                    is SnapshotSplash -> drawSnapshotSplash(viewModelUiState)
-                    is BackgroundOnly -> drawBackground(viewModelUiState.backgroundColor)
-                }
-            }
-            .launchIn(viewAttachedScope)
         viewModel.dimProgress
             .dropWhile { it == 0f }
             .flowOn(dispatcherProvider.background)
@@ -146,8 +132,8 @@
             }
     }
 
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
+    // TODO(b/391842220): Cancel scope in onDetach instead of having a specific method for this.
+    fun destroyScopes() {
         val scopeToCancel = viewAttachedScope
         recentsCoroutineScope.launch(dispatcherProvider.background) {
             scopeToCancel.cancel("TaskThumbnailView detaching from window")
@@ -166,6 +152,19 @@
         }
     }
 
+    fun setState(state: TaskThumbnailUiState) {
+        Log.d(TAG, "viewModelUiState changed from: $uiState to: $state")
+        if (uiState == state) return
+        uiState = state
+        resetViews()
+        when (state) {
+            is Uninitialized -> {}
+            is LiveTile -> drawLiveWindow(state)
+            is SnapshotSplash -> drawSnapshotSplash(state)
+            is BackgroundOnly -> drawBackground(state.backgroundColor)
+        }
+    }
+
     private fun updateViewDataValues() {
         viewData.width.value = width
         viewData.height.value = height
@@ -219,7 +218,8 @@
         drawSnapshot(snapshotSplash.snapshot)
 
         splashBackground.setBackgroundColor(snapshotSplash.snapshot.backgroundColor)
-        splashIcon.setImageDrawable(snapshotSplash.splash)
+        val icon = snapshotSplash.splash?.constantState?.newDrawable()?.mutate() ?: ShapeDrawable()
+        splashIcon.setImageDrawable(icon)
     }
 
     private fun drawSnapshot(snapshot: Snapshot) {
@@ -238,10 +238,6 @@
         thumbnailView.imageMatrix = viewModel.getThumbnailPositionState(width, height, isLayoutRtl)
     }
 
-    private companion object {
-        const val TAG = "TaskThumbnailView"
-    }
-
     private fun maybeCreateHeader() {
         if (enableDesktopExplodedView() && taskThumbnailViewHeader == null) {
             taskThumbnailViewHeader =
@@ -251,4 +247,8 @@
             addView(taskThumbnailViewHeader)
         }
     }
+
+    private companion object {
+        const val TAG = "TaskThumbnailView"
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
index a048a1d..a9fdaa5 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
@@ -17,7 +17,6 @@
 package com.android.quickstep.task.viewmodel
 
 import android.graphics.Matrix
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
 import kotlinx.coroutines.flow.Flow
 
 /** ViewModel for representing TaskThumbnails */
@@ -28,9 +27,6 @@
     /** Provides the alpha of the splash icon */
     val splashAlpha: Flow<Float>
 
-    /** Provides the UiState by which the task thumbnail can be represented */
-    val uiState: Flow<TaskThumbnailUiState>
-
     /** Attaches this ViewModel to a specific task id for it to provide data from. */
     fun bind(taskId: Int)
 
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
index a154c3c..4e4e225 100644
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
@@ -16,50 +16,31 @@
 
 package com.android.quickstep.task.viewmodel
 
-import android.annotation.ColorInt
 import android.app.ActivityTaskManager.INVALID_TASK_ID
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.graphics.Matrix
 import android.util.Log
-import androidx.core.graphics.ColorUtils
-import com.android.launcher3.Flags.enableDesktopExplodedView
 import com.android.launcher3.util.coroutines.DispatcherProvider
-import com.android.quickstep.recents.data.RecentTasksRepository
-import com.android.quickstep.recents.data.RecentsDeviceProfileRepository
 import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
 import com.android.quickstep.recents.usecase.ThumbnailPositionState
 import com.android.quickstep.recents.viewmodel.RecentsViewData
 import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
-import com.android.systemui.shared.recents.model.Task
 import kotlin.math.max
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
 
 @OptIn(ExperimentalCoroutinesApi::class)
 class TaskThumbnailViewModelImpl(
     recentsViewData: RecentsViewData,
     taskContainerData: TaskContainerData,
     dispatcherProvider: DispatcherProvider,
-    private val tasksRepository: RecentTasksRepository,
-    private val deviceProfileRepository: RecentsDeviceProfileRepository,
     private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
     private val splashAlphaUseCase: SplashAlphaUseCase,
 ) : TaskThumbnailViewModel {
-    private val task = MutableStateFlow<Flow<Task?>>(flowOf(null))
     private val splashProgress = MutableStateFlow(flowOf(0f))
     private var taskId: Int = INVALID_TASK_ID
 
@@ -74,42 +55,9 @@
     override val splashAlpha =
         splashProgress.flatMapLatest { it }.flowOn(dispatcherProvider.background)
 
-    private val isLiveTile =
-        combine(
-                task.flatMapLatest { it }.map { it?.key?.id }.distinctUntilChanged(),
-                recentsViewData.runningTaskIds,
-                recentsViewData.runningTaskShowScreenshot,
-            ) { taskId, runningTaskIds, runningTaskShowScreenshot ->
-                runningTaskIds.contains(taskId) && !runningTaskShowScreenshot
-            }
-            .distinctUntilChanged()
-
-    override val uiState: Flow<TaskThumbnailUiState> =
-        combine(task.flatMapLatest { it }, isLiveTile) { taskVal, isRunning ->
-                // TODO(b/369339561) This log is firing a lot. Reduce emissions from TasksRepository
-                //  then re-enable this log.
-                //                Log.d(
-                //                    TAG,
-                //                    "Received task and / or live tile update. taskVal: $taskVal"
-                //                    + " isRunning: $isRunning.",
-                //                )
-                when {
-                    taskVal == null -> Uninitialized
-                    isRunning -> createLiveTileState(taskVal)
-                    isBackgroundOnly(taskVal) ->
-                        BackgroundOnly(taskVal.colorBackground.removeAlpha())
-                    isSnapshotSplashState(taskVal) ->
-                        SnapshotSplash(createSnapshotState(taskVal), taskVal.icon)
-                    else -> Uninitialized
-                }
-            }
-            .distinctUntilChanged()
-            .flowOn(dispatcherProvider.background)
-
     override fun bind(taskId: Int) {
         Log.d(TAG, "bind taskId: $taskId")
         this.taskId = taskId
-        task.value = tasksRepository.getTaskDataById(taskId)
         splashProgress.value = splashAlphaUseCase.execute(taskId)
     }
 
@@ -122,62 +70,6 @@
             is ThumbnailPositionState.MissingThumbnail -> Matrix.IDENTITY_MATRIX
         }
 
-    private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null
-
-    private fun isSnapshotSplashState(task: Task): Boolean {
-        val thumbnailPresent = task.thumbnail?.thumbnail != null
-        val taskLocked = task.isLocked
-
-        return thumbnailPresent && !taskLocked
-    }
-
-    private fun createSnapshotState(task: Task): Snapshot {
-        val thumbnailData = task.thumbnail
-        val bitmap = thumbnailData?.thumbnail!!
-        var thumbnailHeader = maybeCreateHeader(task)
-        return if (thumbnailHeader != null)
-            Snapshot.WithHeader(
-                bitmap,
-                thumbnailData.rotation,
-                task.colorBackground.removeAlpha(),
-                thumbnailHeader,
-            )
-        else
-            Snapshot.WithoutHeader(
-                bitmap,
-                thumbnailData.rotation,
-                task.colorBackground.removeAlpha(),
-            )
-    }
-
-    private fun shouldHaveThumbnailHeader(task: Task): Boolean {
-        return deviceProfileRepository.getRecentsDeviceProfile().canEnterDesktopMode &&
-            enableDesktopExplodedView() &&
-            task.key.windowingMode == WINDOWING_MODE_FREEFORM
-    }
-
-    private fun maybeCreateHeader(task: Task): ThumbnailHeader? {
-        // Header is only needed when this task is a desktop task and Overivew exploded view is
-        // enabled.
-        if (!shouldHaveThumbnailHeader(task)) {
-            return null
-        }
-
-        // TODO(http://b/353965691): figure out what to do when `icon` or `titleDescription` is
-        // null.
-        val icon = task.icon ?: return null
-        val titleDescription = task.titleDescription ?: return null
-        return ThumbnailHeader(icon, titleDescription)
-    }
-
-    private fun createLiveTileState(task: Task): LiveTile {
-        val thumbnailHeader = maybeCreateHeader(task)
-        return if (thumbnailHeader != null) LiveTile.WithHeader(thumbnailHeader)
-        else LiveTile.WithoutHeader
-    }
-
-    @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff)
-
     private companion object {
         const val MAX_SCRIM_ALPHA = 0.4f
         const val TAG = "TaskThumbnailViewModel"
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 8399792..6b8650f 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -46,7 +46,6 @@
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.apppairs.AppPairIcon;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
@@ -127,8 +126,7 @@
                 .anyMatch(att -> att != null && att.getItemInfo() != null
                         && ((att.getItemInfo().runtimeStatusFlags
                             & ItemInfoWithIcon.FLAG_NOT_PINNABLE) != 0));
-        if (!FeatureFlags.enableAppPairs()
-                || !taskView.containsMultipleTasks()
+        if (!taskView.containsMultipleTasks()
                 || hasUnpinnableApp
                 || !(taskView instanceof GroupedTaskView)) {
             return false;
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
index f75d3b3..ed96399 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
@@ -21,7 +21,6 @@
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_SEARCH_SCREEN;
 
 import android.app.PendingIntent;
@@ -44,11 +43,13 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.R;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.EventLogArray;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.quickstep.DeviceConfigWrapper;
@@ -58,12 +59,14 @@
 import java.io.PrintWriter;
 import java.util.Optional;
 
-/** Long-lived class to manage Contextual Search states like the user setting and availability. */
-public class ContextualSearchStateManager implements ResourceBasedOverride, SafeCloseable {
+import javax.inject.Inject;
 
-    public static final MainThreadInitializedObject<ContextualSearchStateManager> INSTANCE =
-            forOverride(ContextualSearchStateManager.class,
-                    R.string.contextual_search_state_manager_class);
+/** Long-lived class to manage Contextual Search states like the user setting and availability. */
+@LauncherAppSingleton
+public class ContextualSearchStateManager  {
+
+    public static final DaggerSingletonObject<ContextualSearchStateManager> INSTANCE =
+            new DaggerSingletonObject<>(LauncherAppComponent::getContextualSearchStateManager);
 
     private static final String TAG = "ContextualSearchStMgr";
     private static final int MAX_DEBUG_EVENT_SIZE = 20;
@@ -73,23 +76,29 @@
     private final Runnable mSysUiStateChangeListener = this::updateOverridesToSysUi;
     private final SimpleBroadcastReceiver mContextualSearchPackageReceiver =
             new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, (unused) -> requestUpdateProperties());
-    private final SettingsCache.OnChangeListener mContextualSearchSettingChangedListener =
-            this::onContextualSearchSettingChanged;
     protected final EventLogArray mEventLogArray = new EventLogArray(TAG, MAX_DEBUG_EVENT_SIZE);
 
     // Cached value whether the ContextualSearch intent filter matched any enabled components.
     private boolean mIsContextualSearchIntentAvailable;
     private boolean mIsContextualSearchSettingEnabled;
 
-    protected Context mContext;
-    protected String mContextualSearchPackage;
+    protected final Context mContext;
+    protected final String mContextualSearchPackage;
+    protected final SystemUiProxy mSystemUiProxy;
+    protected final TopTaskTracker mTopTaskTracker;
 
-    public ContextualSearchStateManager() {}
-
-    public ContextualSearchStateManager(Context context) {
+    @Inject
+    public ContextualSearchStateManager(
+            @ApplicationContext Context context,
+            SettingsCache settingsCache,
+            SystemUiProxy systemUiProxy,
+            TopTaskTracker topTaskTracker,
+            DaggerSingletonTracker lifeCycle) {
         mContext = context;
         mContextualSearchPackage = mContext.getResources().getString(
                 com.android.internal.R.string.config_defaultContextualSearchPackageName);
+        mSystemUiProxy = systemUiProxy;
+        mTopTaskTracker = topTaskTracker;
 
         if (areAllContextualSearchFlagsDisabled()
                 || !context.getPackageManager().hasSystemFeature(FEATURE_CONTEXTUAL_SEARCH)) {
@@ -106,11 +115,20 @@
                 context, mContextualSearchPackage, Intent.ACTION_PACKAGE_ADDED,
                 Intent.ACTION_PACKAGE_CHANGED, Intent.ACTION_PACKAGE_REMOVED);
 
-        SettingsCache.INSTANCE.get(context).register(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI,
-                mContextualSearchSettingChangedListener);
-        onContextualSearchSettingChanged(
-                SettingsCache.INSTANCE.get(context).getValue(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI));
-        SystemUiProxy.INSTANCE.get(mContext).addOnStateChangeListener(mSysUiStateChangeListener);
+        SettingsCache.OnChangeListener settingChangedListener =
+                isEnabled -> mIsContextualSearchSettingEnabled = isEnabled;
+        settingsCache.register(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI, settingChangedListener);
+        mIsContextualSearchSettingEnabled =
+                settingsCache.getValue(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI);
+
+        systemUiProxy.addOnStateChangeListener(mSysUiStateChangeListener);
+
+        lifeCycle.addCloseable(() -> {
+            mContextualSearchPackageReceiver.unregisterReceiverSafely(mContext);
+            unregisterSearchScreenSystemAction();
+            settingsCache.unregister(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI, settingChangedListener);
+            systemUiProxy.removeOnStateChangeListener(mSysUiStateChangeListener);
+        });
     }
 
     /** Return {@code true} if the Settings toggle is enabled. */
@@ -118,10 +136,6 @@
         return mIsContextualSearchSettingEnabled;
     }
 
-    private void onContextualSearchSettingChanged(boolean isEnabled) {
-        mIsContextualSearchSettingEnabled = isEnabled;
-    }
-
     /** Whether search supports showing on the lockscreen. */
     protected boolean supportsShowWhenLocked() {
         return false;
@@ -208,7 +222,7 @@
 
     protected final void updateOverridesToSysUi() {
         // LPH commit haptic is always enabled
-        SystemUiProxy.INSTANCE.get(mContext).setOverrideHomeButtonLongPress(
+        mSystemUiProxy.setOverrideHomeButtonLongPress(
                 getLPHDurationMillis().orElse(0L), getLPHCustomSlopMultiplier().orElse(0f), true);
         Log.i(TAG, "Sent LPH override to sysui: " + getLPHDurationMillis().orElse(0L) + ";"
                 + getLPHCustomSlopMultiplier().orElse(0f));
@@ -227,10 +241,8 @@
                                     new ContextualSearchInvoker(mContext).show(
                                             ENTRYPOINT_SYSTEM_ACTION);
                             if (contextualSearchInvoked) {
-                                String runningPackage =
-                                        TopTaskTracker.INSTANCE.get(mContext).getCachedTopTask(
-                                                /* filterOnlyVisibleRecents */
-                                                true).getPackageName();
+                                String runningPackage = mTopTaskTracker.getCachedTopTask(
+                                        /* filterOnlyVisibleRecents */ true).getPackageName();
                                 StatsLogManager.newInstance(mContext).logger()
                                         .withPackageName(runningPackage)
                                         .log(LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION);
@@ -259,15 +271,6 @@
         }
     }
 
-    @Override
-    public void close() {
-        mContextualSearchPackageReceiver.unregisterReceiverSafely(mContext);
-        unregisterSearchScreenSystemAction();
-        SettingsCache.INSTANCE.get(mContext).unregister(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI,
-                mContextualSearchSettingChangedListener);
-        SystemUiProxy.INSTANCE.get(mContext).removeOnStateChangeListener(mSysUiStateChangeListener);
-    }
-
     protected final void addEventLog(String event) {
         synchronized (mEventLogArray) {
             mEventLogArray.addLog(event);
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.kt b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
index 1cee2d2..5463cf7 100644
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.kt
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
@@ -17,7 +17,6 @@
 
 import com.android.quickstep.views.TaskViewType
 import com.android.systemui.shared.recents.model.Task
-import java.util.Objects
 
 /**
  * A [Task] container that can contain N number of tasks that are part of the desktop in recent
@@ -39,9 +38,6 @@
     override fun equals(o: Any?): Boolean {
         if (this === o) return true
         if (o !is DesktopTask) return false
-        if (!super.equals(o)) return false
-        return tasks == o.tasks
+        return super.equals(o)
     }
-
-    override fun hashCode() = Objects.hash(super.hashCode(), tasks)
 }
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.kt b/quickstep/src/com/android/quickstep/util/GroupTask.kt
index 0bee5f6..d5bbcd3 100644
--- a/quickstep/src/com/android/quickstep/util/GroupTask.kt
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.kt
@@ -22,10 +22,10 @@
 import java.util.Objects
 
 /**
- * A [Task] container that can contain one or two tasks, depending on if the two tasks are
- * represented as an app-pair in the recents task list.
+ * An abstract class for creating [Task] containers that can be [SingleTask]s, [SplitTask]s, or
+ * [DesktopTask]s in the recent tasks list.
  */
-open class GroupTask
+abstract class GroupTask
 @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
 constructor(
     @Deprecated("Prefer using `getTasks()` instead") @JvmField val task1: Task,
@@ -33,13 +33,16 @@
     @JvmField val mSplitBounds: SplitConfigurationOptions.SplitBounds?,
     @JvmField val taskViewType: TaskViewType,
 ) {
-    constructor(task: Task) : this(task, null, null)
-
-    constructor(
-        t1: Task,
-        t2: Task?,
+    protected constructor(
+        task1: Task,
+        task2: Task?,
         splitBounds: SplitConfigurationOptions.SplitBounds?,
-    ) : this(t1, t2, splitBounds, if (t2 != null) TaskViewType.GROUPED else TaskViewType.SINGLE)
+    ) : this(
+        task1,
+        task2,
+        splitBounds,
+        if (task2 != null) TaskViewType.GROUPED else TaskViewType.SINGLE,
+    )
 
     open fun containsTask(taskId: Int) =
         task1.key.id == taskId || (task2 != null && task2.key.id == taskId)
@@ -59,18 +62,50 @@
         get() = listOfNotNull(task1, task2)
 
     /** Creates a copy of this instance */
-    open fun copy() = GroupTask(Task(task1), if (task2 != null) Task(task2) else null, mSplitBounds)
+    abstract fun copy(): GroupTask
 
     override fun toString() = "type=$taskViewType task1=$task1 task2=$task2"
 
     override fun equals(o: Any?): Boolean {
         if (this === o) return true
         if (o !is GroupTask) return false
-        return taskViewType == o.taskViewType &&
-            task1 == o.task1 &&
-            task2 == o.task2 &&
-            mSplitBounds == o.mSplitBounds
+        return taskViewType == o.taskViewType && tasks == o.tasks
     }
 
-    override fun hashCode() = Objects.hash(task1, task2, mSplitBounds, taskViewType)
+    override fun hashCode() = Objects.hash(tasks, taskViewType)
+}
+
+/** A [Task] container that must contain exactly one task in the recent tasks list. */
+class SingleTask(task: Task) :
+    GroupTask(task, task2 = null, mSplitBounds = null, TaskViewType.SINGLE) {
+    override fun copy() = SingleTask(task1)
+
+    override fun toString() = "type=$taskViewType task=$task1"
+
+    override fun equals(o: Any?): Boolean {
+        if (this === o) return true
+        if (o !is SingleTask) return false
+        return super.equals(o)
+    }
+}
+
+/**
+ * A [Task] container that must contain exactly two tasks and split bounds to represent an app-pair
+ * in the recent tasks list.
+ */
+class SplitTask(task1: Task, task2: Task, splitBounds: SplitConfigurationOptions.SplitBounds) :
+    GroupTask(task1, task2, splitBounds, TaskViewType.GROUPED) {
+
+    override fun copy() = SplitTask(task1, task2!!, mSplitBounds!!)
+
+    override fun toString() = "type=$taskViewType task1=$task1 task2=$task2"
+
+    override fun equals(o: Any?): Boolean {
+        if (this === o) return true
+        if (o !is SplitTask) return false
+        if (mSplitBounds!! != o.mSplitBounds!!) return false
+        return super.equals(o)
+    }
+
+    override fun hashCode() = Objects.hash(super.hashCode(), mSplitBounds)
 }
diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
index d982e81..4005c5a 100644
--- a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
@@ -33,38 +33,34 @@
         // TODO(b/254378592): Remove these methods when the two classes are reunited
         /** Converts the shell version of SplitBounds to the launcher version */
         @JvmStatic
-        fun convertShellSplitBoundsToLauncher(
-            shellSplitBounds: SplitBounds?
-        ): SplitConfigurationOptions.SplitBounds? {
-            return if (shellSplitBounds == null) {
-                null
-            } else {
-                SplitConfigurationOptions.SplitBounds(
-                    shellSplitBounds.leftTopBounds,
-                    shellSplitBounds.rightBottomBounds,
-                    shellSplitBounds.leftTopTaskId,
-                    shellSplitBounds.rightBottomTaskId,
-                    shellSplitBounds.snapPosition
-                )
-            }
-        }
+        fun convertShellSplitBoundsToLauncher(shellSplitBounds: SplitBounds) =
+            SplitConfigurationOptions.SplitBounds(
+                shellSplitBounds.leftTopBounds,
+                shellSplitBounds.rightBottomBounds,
+                shellSplitBounds.leftTopTaskId,
+                shellSplitBounds.rightBottomTaskId,
+                shellSplitBounds.snapPosition,
+            )
 
         /**
          * Given a TransitionInfo, generates the tree structure for those changes and extracts out
-         * the top most root and it's two immediate children.
-         * Changes can be provided in any order.
+         * the top most root and it's two immediate children. Changes can be provided in any order.
          *
-         * @return a [Pair] where first -> top most split root,
-         *         second -> [List] of 2, leftTop/bottomRight stage roots
+         * @return a [Pair] where first -> top most split root, second -> [List] of 2,
+         *   leftTop/bottomRight stage roots
          */
-        fun extractTopParentAndChildren(transitionInfo: TransitionInfo):
-                Pair<Change, List<Change>>? {
+        fun extractTopParentAndChildren(
+            transitionInfo: TransitionInfo
+        ): Pair<Change, List<Change>>? {
             val parentToChildren = mutableMapOf<Change, MutableList<Change>>()
             val hasParent = mutableSetOf<Change>()
             // filter out anything that isn't opening and the divider
-            val taskChanges: List<Change> = transitionInfo.changes
-                    .filter { change -> (change.mode == TRANSIT_OPEN ||
-                            change.mode == TRANSIT_TO_FRONT) && change.flags < FLAG_FIRST_CUSTOM}
+            val taskChanges: List<Change> =
+                transitionInfo.changes
+                    .filter { change ->
+                        (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT) &&
+                            change.flags < FLAG_FIRST_CUSTOM
+                    }
                     .toList()
 
             // 1. Build Parent-Child Relationships
@@ -73,8 +69,8 @@
                 //  startAnimation() and we can know the precise taskIds of launching tasks.
                 change.parent?.let { parent ->
                     parentToChildren
-                            .getOrPut(transitionInfo.getChange(parent)!!) { mutableListOf() }
-                            .add(change)
+                        .getOrPut(transitionInfo.getChange(parent)!!) { mutableListOf() }
+                        .add(change)
                     hasParent.add(change)
                 }
             }
diff --git a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
index f973dd0..e353160 100644
--- a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
+++ b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
@@ -17,8 +17,13 @@
 package com.android.quickstep.views
 
 import android.content.Context
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RoundRectShape
 import android.util.AttributeSet
 import android.widget.ImageButton
+import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X
+import com.android.launcher3.R
+import com.android.launcher3.util.MultiPropertyFactory
 
 /**
  * Button for supporting multiple desktop sessions. The button will be next to the first TaskView
@@ -26,5 +31,44 @@
  */
 class AddDesktopButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
     ImageButton(context, attrs) {
-    // TODO(b/382057498): add this button the overview.
+
+    private enum class TranslationX {
+        GRID,
+        OFFSET,
+    }
+
+    private val multiTranslationX =
+        MultiPropertyFactory(this, VIEW_TRANSLATE_X, TranslationX.entries.size) { a: Float, b: Float
+            ->
+            a + b
+        }
+
+    var gridTranslationX
+        get() = multiTranslationX[TranslationX.GRID.ordinal].value
+        set(value) {
+            multiTranslationX[TranslationX.GRID.ordinal].value = value
+        }
+
+    var offsetTranslationX
+        get() = multiTranslationX[TranslationX.OFFSET.ordinal].value
+        set(value) {
+            multiTranslationX[TranslationX.OFFSET.ordinal].value = value
+        }
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+
+        background =
+            ShapeDrawable().apply {
+                shape =
+                    RoundRectShape(
+                        FloatArray(8) { R.dimen.add_desktop_button_size.toFloat() },
+                        null,
+                        null,
+                    )
+                setTint(
+                    resources.getColor(android.R.color.system_surface_bright_light, context.theme)
+                )
+            }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 3f0b520..38ffe50 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -27,7 +27,6 @@
 import com.android.launcher3.Flags.enableRefactorTaskThumbnail
 import com.android.launcher3.R
 import com.android.launcher3.Utilities
-import com.android.launcher3.config.FeatureFlags
 import com.android.launcher3.util.RunnableList
 import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
@@ -340,14 +339,6 @@
         return Utilities.pointInView(this, localPos[0], localPos[1], 0f /* slop */)
     }
 
-    override fun setOverlayEnabled(overlayEnabled: Boolean) {
-        if (FeatureFlags.enableAppPairs()) {
-            super.setOverlayEnabled(overlayEnabled)
-        } else {
-            // Intentional no-op to prevent setting smart actions overlay on thumbnails
-        }
-    }
-
     companion object {
         private const val TAG = "GroupedTaskView"
     }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index cfad303..b93a2f0 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -74,7 +74,6 @@
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
-import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SCREEN;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SELECT_ACTIVE;
 
 import android.animation.Animator;
@@ -547,6 +546,8 @@
     private final int mSplitPlaceholderSize;
     private final int mSplitPlaceholderInset;
     private final ClearAllButton mClearAllButton;
+    @Nullable
+    private AddDesktopButton mAddDesktopButton = null;
     private final Rect mClearAllButtonDeadZoneRect = new Rect();
     private final Rect mTaskViewDeadZoneRect = new Rect();
     private final Rect mTopRowDeadZoneRect = new Rect();
@@ -902,6 +903,12 @@
         mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
                 .inflate(R.layout.overview_clear_all_button, this, false);
         mClearAllButton.setOnClickListener(this::dismissAllTasks);
+
+        if (DesktopModeStatus.enableMultipleDesktops(mContext)) {
+            mAddDesktopButton = (AddDesktopButton) LayoutInflater.from(context).inflate(
+                    R.layout.overview_add_desktop_button, this, false);
+        }
+
         mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
                 10 /* initial size */);
         mGroupedTaskViewPool = new ViewPool<>(context, this,
@@ -1247,6 +1254,13 @@
     public void destroy() {
         Log.d(TAG, "destroy");
         if (enableRefactorTaskThumbnail()) {
+            try {
+                mTaskViewPool.killOngoingInitializations();
+                mGroupedTaskViewPool.killOngoingInitializations();
+                mDesktopTaskViewPool.killOngoingInitializations();
+            } catch (InterruptedException e) {
+                Log.e(TAG, "Ongoing initializations could not be killed", e);
+            }
             mHelper.onDestroy();
             RecentsDependencies.destroy();
         }
@@ -1880,7 +1894,7 @@
         }
         mLoadPlanEverApplied = true;
         if (taskGroups == null || taskGroups.isEmpty()) {
-            removeTasksViewsAndClearAllButton();
+            removeAllTaskViews();
             onTaskStackUpdated();
             // With all tasks removed, touch handling in PagedView is disabled and we need to reset
             // touch state or otherwise values will be obsolete.
@@ -1943,6 +1957,11 @@
             taskGroups = mUtils.sortDesktopTasksToFront(taskGroups);
         }
 
+        if (mAddDesktopButton != null) {
+            // Add `mAddDesktopButton` as the first child.
+            addView(mAddDesktopButton);
+        }
+
         // Add views as children based on whether it's grouped or single task. Looping through
         // taskGroups backwards populates the thumbnail grid from least recent to most recent.
         for (int i = taskGroups.size() - 1; i >= 0; i--) {
@@ -2088,13 +2107,14 @@
         return mModel.isLoadingTasksInBackground();
     }
 
-    private void removeTasksViewsAndClearAllButton() {
+    private void removeAllTaskViews() {
         // This handles an edge case where applyLoadPlan happens during a gesture when the only
         // Task is one with excludeFromRecents, in which case we should not remove it.
         CollectionsKt
                 .filter(getTaskViews(), taskView -> !isGestureActive() || !taskView.isRunningTask())
                 .forEach(this::removeView);
         if (!hasTaskViews()) {
+            removeView(mAddDesktopButton);
             removeView(mClearAllButton);
         }
     }
@@ -2324,6 +2344,9 @@
 
         float taskAlignmentTranslationY = getTaskAlignmentTranslationY();
         mClearAllButton.setTaskAlignmentTranslationY(taskAlignmentTranslationY);
+        if (mAddDesktopButton != null) {
+            mAddDesktopButton.setTranslationY(taskAlignmentTranslationY);
+        }
 
         updateGridProperties();
     }
@@ -2670,16 +2693,17 @@
     }
 
     private void onReset() {
-        if (enableRefactorTaskThumbnail()) {
-            mRecentsViewModel.onReset();
-            removeAllViews();
-        }
         unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
         setCurrentPage(0);
         LayoutUtils.setViewEnabled(mActionsView, true);
         if (mOrientationState.setGestureActive(false)) {
             updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false);
         }
+        if (enableRefactorTaskThumbnail()) {
+            mRecentsViewModel.onReset();
+            // TODO(b/391842220) Remove TaskViews rather than calling specific logic to cancel scope
+            getTaskViews().forEach(TaskView::destroyScopes);
+        }
     }
 
     public int getRunningTaskViewId() {
@@ -3006,6 +3030,9 @@
                 taskView = getTaskViewFromPool(TaskViewType.SINGLE);
                 taskView.bind(runningTasks[0], mOrientationState, mTaskOverlayFactory);
             }
+            if (mAddDesktopButton != null && wasEmpty) {
+                addView(mAddDesktopButton);
+            }
             addView(taskView, getRunningTaskExpectedIndex(taskView));
             runningTaskViewId = taskView.getTaskViewId();
             if (wasEmpty) {
@@ -3204,7 +3231,7 @@
         Map<TaskView, Float> gridTranslations = new HashMap<>();
 
         TaskView lastLargeTaskView = mUtils.getLastLargeTaskView();
-        int focusedTaskShift = 0;
+        int focusedTaskViewShift = 0;
         int largeTaskWidthAndSpacing = 0;
         int snappedTaskRowWidth = 0;
         int snappedPage = isKeyboardTaskFocusPending() ? mKeyboardTaskFocusIndex : getNextPage();
@@ -3249,7 +3276,7 @@
                     topRowWidth += taskWidthAndSpacing;
                     bottomRowWidth += taskWidthAndSpacing;
                 }
-                gridTranslation += focusedTaskShift;
+                gridTranslation += focusedTaskViewShift;
                 gridTranslation += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
 
                 // Center view vertically in case it's from different orientation.
@@ -3268,9 +3295,12 @@
                     gridTranslation +=
                             mIsRtl ? largeTaskWidthAndSpacing : -largeTaskWidthAndSpacing;
                 } else {
-                    // For task before the focused task, accumulate the width and spacing to
-                    // calculate the distance focused task need to shift.
-                    focusedTaskShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
+                    // For TaskViews before the new focused TaskView, accumulate the width and
+                    // spacing to calculate the distance the new focused TaskView needs to shift.
+                    // This could happen for example after multiple times of dismissing the
+                    // focused TaskView, the triggered rebalance might set a non-first TaskView
+                    // inside `mChildren` as the new focused TaskView.
+                    focusedTaskViewShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
                 }
                 int taskViewId = taskView.getTaskViewId();
 
@@ -3341,7 +3371,7 @@
         if (snappedTaskView != null) {
             snappedTaskNonGridScrollAdjustment = snappedTaskView.getScrollAdjustment(
                     /*gridEnabled=*/false);
-            snappedTaskGridTranslationX = gridTranslations.get(snappedTaskView);
+            snappedTaskGridTranslationX = gridTranslations.getOrDefault(snappedTaskView, 0f);
         }
 
         // Use the accumulated translation of the row containing the last task.
@@ -3422,10 +3452,24 @@
 
         for (TaskView taskView : getTaskViews()) {
             taskView.setGridTranslationX(
-                    gridTranslations.get(taskView) - snappedTaskGridTranslationX
+                    gridTranslations.getOrDefault(taskView, 0f) - snappedTaskGridTranslationX
                             + snappedTaskNonGridScrollAdjustment);
         }
 
+        if (mAddDesktopButton != null) {
+            TaskView firstTaskView = getFirstTaskView();
+            float translationX = 0f;
+            if (firstTaskView != null) {
+                translationX += firstTaskView.getGridTranslationX();
+            }
+            if (focusedTaskViewShift != 0) {
+                // If the focused task is inserted between `firstTaskView` and
+                // `mAddDesktopButton`, shift `mAddDesktopButton` to accommodate.
+                translationX += largeTaskWidthAndSpacing;
+            }
+            mAddDesktopButton.setGridTranslationX(translationX);
+        }
+
         final TaskView runningTask = getRunningTaskView();
         if (showAsGrid() && enableGridOnlyOverview() && runningTask != null) {
             runActionOnRemoteHandles(
@@ -4137,6 +4181,7 @@
 
                     if (taskCount == 1) {
                         removeViewInLayout(mClearAllButton);
+                        removeViewInLayout(mAddDesktopButton);
                         if (isHomeTaskDismissed) {
                             updateEmptyMessage();
                         } else if (!mSplitSelectStateController.isSplitSelectActive()) {
@@ -4344,9 +4389,6 @@
         boolean isCurrentSplit = taskView instanceof GroupedTaskView;
         GroupedTaskView groupedTaskView = isCurrentSplit ? (GroupedTaskView) taskView : null;
         // Update flags to see if entire actions bar should be hidden.
-        if (!FeatureFlags.enableAppPairs()) {
-            mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SCREEN, isCurrentSplit);
-        }
         mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive());
         // Update flags to see if actions bar should show buttons for a single task or a pair of
         // tasks.
@@ -4468,7 +4510,7 @@
                 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, () -> {
                     UI_HELPER_EXECUTOR.getHandler().post(
                             ActivityManagerWrapper.getInstance()::removeAllRecentTasks);
-                    removeTasksViewsAndClearAllButton();
+                    removeAllTaskViews();
                     startHome();
                 });
             }
@@ -4627,6 +4669,11 @@
             taskView.setStableAlpha(alpha);
         }
         mClearAllButton.setContentAlpha(mContentAlpha);
+
+        // TODO(b/389209338): Handle the visibility of the `mAddDesktopButton`.
+        if (mAddDesktopButton != null) {
+            mAddDesktopButton.setAlpha(mContentAlpha);
+        }
         int alphaInt = Math.round(alpha * 255);
         mEmptyMessagePaint.setAlpha(alphaInt);
         mEmptyIcon.setAlpha(alphaInt);
@@ -4923,9 +4970,11 @@
                     + carouselHiddenOffsetSize;
             if (child instanceof TaskView taskView) {
                 taskView.getPrimaryTaskOffsetTranslationProperty().set(taskView, totalTranslationX);
-            } else {
+            } else if (child instanceof ClearAllButton) {
                 getPagedOrientationHandler().getPrimaryViewTranslate().set(child,
                         totalTranslationX);
+            } else if (child instanceof AddDesktopButton addDesktopButton) {
+                addDesktopButton.setOffsetTranslationX(totalTranslationX);
             }
             if (mEnableDrawingLiveTile && i == getRunningTaskIndex()) {
                 runActionOnRemoteHandles(
@@ -6104,6 +6153,13 @@
             outPageScrolls[clearAllIndex] = clearAllScroll;
         }
 
+        int addDesktopButtonIndex = indexOfChild(mAddDesktopButton);
+        if (addDesktopButtonIndex != -1 && addDesktopButtonIndex < outPageScrolls.length) {
+            outPageScrolls[addDesktopButtonIndex] =
+                    newPageScrolls[addDesktopButtonIndex] + Math.round(
+                            mAddDesktopButton.getGridTranslationX());
+        }
+
         int lastTaskScroll = getLastTaskScroll(clearAllScroll, clearAllWidth);
         getTaskViews().forEachWithIndexInParent((index, taskView) -> {
             float scrollDiff = taskView.getScrollAdjustment(showAsGrid);
@@ -6784,10 +6840,8 @@
             return;
         }
 
-        mDesktopRecentsTransitionController.moveToDesktop(taskContainer, transitionSource);
-        // TODO(b/387471509): Invoke successCallback after actual transition completion of
-        //  overview menu to desktop
-        successCallback.run();
+        mDesktopRecentsTransitionController.moveToDesktop(taskContainer, transitionSource,
+                successCallback);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 5de8d1c..6b5d8dd 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -28,6 +28,8 @@
 import com.android.quickstep.recents.di.get
 import com.android.quickstep.recents.di.getScope
 import com.android.quickstep.recents.di.inject
+import com.android.quickstep.recents.ui.mapper.TaskUiStateMapper
+import com.android.quickstep.recents.ui.viewmodel.TaskData
 import com.android.quickstep.recents.viewmodel.TaskContainerViewModel
 import com.android.quickstep.task.thumbnail.TaskThumbnailView
 import com.android.quickstep.task.viewmodel.TaskContainerData
@@ -141,6 +143,11 @@
         }
     }
 
+    // TODO(b/391842220): Cancel scope in onDetach instead of having a specific method for this.
+    fun destroyScopes() {
+        thumbnailView.destroyScopes()
+    }
+
     fun bindThumbnailView() {
         taskThumbnailViewModel.bind(task.key.id)
     }
@@ -158,4 +165,8 @@
         digitalWellBeingToast?.let { addAccessibleChildToList(it, outChildren) }
         overlay.addChildForAccessibility(outChildren)
     }
+
+    fun setState(state: TaskData?, liveTile: Boolean, hasHeader: Boolean) {
+        thumbnailView.setState(TaskUiStateMapper.toTaskThumbnailUiState(state, liveTile, hasHeader))
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
index 9f2bb9a..5ee5e10 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
@@ -46,7 +46,6 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.SystemUiController.SystemUiControllerFlags;
 import com.android.launcher3.util.ViewPool;
@@ -66,8 +65,6 @@
  */
 @Deprecated
 public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusable {
-    private static final MainThreadInitializedObject<FullscreenDrawParams> TEMP_PARAMS =
-            new MainThreadInitializedObject<>(FullscreenDrawParams::new);
 
     public static final Property<TaskThumbnailViewDeprecated, Float> DIM_ALPHA =
             new FloatProperty<TaskThumbnailViewDeprecated>("dimAlpha") {
@@ -145,8 +142,7 @@
         mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
         mContainer = RecentsViewContainer.containerFromContext(context);
         // Initialize with placeholder value. It is overridden later by TaskView
-        mFullscreenParams = TEMP_PARAMS.get(context);
-
+        mFullscreenParams = new FullscreenDrawParams(context, __ -> 0f, __ -> 0f);
         mDimColor = RecentsView.getForegroundScrimDimColor(context);
         mDimmingPaintAfterClearing.setColor(mDimColor);
     }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index dc849f3..741297d 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -70,6 +70,7 @@
 import com.android.launcher3.util.TraceHelper
 import com.android.launcher3.util.TransformingTouchDelegate
 import com.android.launcher3.util.ViewPool
+import com.android.launcher3.util.coroutines.DispatcherProvider
 import com.android.launcher3.util.rects.set
 import com.android.quickstep.FullscreenDrawParams
 import com.android.quickstep.RecentsModel
@@ -77,6 +78,11 @@
 import com.android.quickstep.TaskOverlayFactory
 import com.android.quickstep.TaskViewUtils
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler
+import com.android.quickstep.recents.di.RecentsDependencies
+import com.android.quickstep.recents.di.get
+import com.android.quickstep.recents.di.inject
+import com.android.quickstep.recents.ui.viewmodel.TaskTileUiState
+import com.android.quickstep.recents.ui.viewmodel.TaskViewModel
 import com.android.quickstep.util.ActiveGestureErrorDetector
 import com.android.quickstep.util.ActiveGestureLog
 import com.android.quickstep.util.BorderAnimator
@@ -88,6 +94,12 @@
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
 import com.android.systemui.shared.system.ActivityManagerWrapper
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
 
 /** A task in the Recents view. */
 open class TaskView
@@ -448,6 +460,11 @@
     private val settledProgressDismiss =
         settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_DISMISS)
 
+    private var viewModel: TaskViewModel? = null
+    private val dispatcherProvider: DispatcherProvider by RecentsDependencies.inject()
+    private val coroutineScope by lazy { CoroutineScope(SupervisorJob() + dispatcherProvider.main) }
+    private val coroutineJobs = mutableListOf<Job>()
+
     /**
      * Returns an animator of [settledProgressDismiss] that transition in with a built-in
      * interpolator.
@@ -601,6 +618,8 @@
 
     override fun onRecycle() {
         resetPersistentViewTransforms()
+
+        viewModel = null
         attachAlpha = 1f
         splitAlpha = 1f
         // Clear any references to the thumbnail (it will be re-read either from the cache or the
@@ -613,9 +632,15 @@
         borderEnabled = false
         hoverBorderVisible = false
         taskViewId = UNBOUND_TASK_VIEW_ID
+        // TODO(b/390583187): Clean the components UI State when TaskView is recycled.
         taskContainers.forEach { it.destroy() }
     }
 
+    fun destroyScopes() {
+        // TODO(b/391842220): Cancel scope in onDetach instead of having a specific method for this.
+        taskContainers.forEach { it.destroyScopes() }
+    }
+
     // TODO: Clip-out the icon region from the thumbnail, since they are overlapping.
     override fun hasOverlappingRendering() = false
 
@@ -709,6 +734,44 @@
             ?.inflate()
     }
 
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        if (enableRefactorTaskThumbnail()) {
+            // The TaskView lifecycle is starts the ViewModel during onBind, and cleans it in
+            // onRecycle. So it should be initialized at this point. TaskView Lifecycle:
+            // `bind` -> `onBind` ->  onAttachedToWindow() -> onDetachFromWindow -> onRecycle
+            coroutineJobs +=
+                coroutineScope.launch {
+                    viewModel!!.state.collectLatest(::updateTaskContainerState)
+                }
+        }
+    }
+
+    private fun updateTaskContainerState(state: TaskTileUiState) {
+        val mapOfTasks = state.tasks.associateBy { it.taskId }
+        taskContainers.forEach { container ->
+            container.setState(
+                state = mapOfTasks[container.task.key.id],
+                liveTile = state.isLiveTile,
+                hasHeader = type == TaskViewType.DESKTOP,
+            )
+        }
+    }
+
+    override fun onDetachedFromWindow() {
+        super.onDetachedFromWindow()
+        if (enableRefactorTaskThumbnail()) {
+            // The jobs are being cancelled in the background thread. So we make a copy of the list
+            // to prevent cleaning a new job that might be added to this list during onAttach
+            // or another moment in the lifecycle.
+            val coroutineJobsToCancel = coroutineJobs.toList()
+            coroutineJobs.clear()
+            coroutineScope.launch(dispatcherProvider.background) {
+                coroutineJobsToCancel.forEach { it.cancel("TaskView detaching from window") }
+            }
+        }
+    }
+
     /** Updates this task view to the given {@param task}. */
     open fun bind(
         task: Task,
@@ -732,6 +795,17 @@
     }
 
     open fun onBind(orientedState: RecentsOrientedState) {
+        if (enableRefactorTaskThumbnail()) {
+            viewModel =
+                TaskViewModel(
+                        taskViewType = type,
+                        recentsViewData = RecentsDependencies.get(),
+                        getTaskUseCase = RecentsDependencies.get(),
+                        dispatcherProvider = RecentsDependencies.get(),
+                    )
+                    .apply { bind(*taskIds) }
+        }
+
         taskContainers.forEach {
             it.bind()
             if (enableRefactorTaskThumbnail()) {
diff --git a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
index be1a4e8..37c64cf 100644
--- a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
@@ -395,11 +395,11 @@
 
     public static void logOnRecentsAnimationStart(int appCount) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
-                "RecentsAnimationCallbacks.onAnimationStart (canceled): %d", appCount),
+                "RecentsAnimationCallbacks.onAnimationStart: %d", appCount),
                 /* gestureEvent= */ ON_START_RECENTS_ANIMATION);
         if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
-                "RecentsAnimationCallbacks.onAnimationStart (canceled): %d", appCount);
+                "RecentsAnimationCallbacks.onAnimationStart: %d", appCount);
     }
 
     public static void logStartRecentsAnimationCallback(@NonNull String callback) {
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
index 47d2bfc..e033e7b 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
@@ -17,14 +17,12 @@
 package com.android.quickstep.task.thumbnail
 
 import android.graphics.Matrix
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
 import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
 import kotlinx.coroutines.flow.MutableStateFlow
 
 class FakeTaskThumbnailViewModel : TaskThumbnailViewModel {
     override val dimProgress = MutableStateFlow(0f)
     override val splashAlpha = MutableStateFlow(0f)
-    override val uiState = MutableStateFlow<TaskThumbnailUiState>(Uninitialized)
 
     override fun bind(taskId: Int) {
         // no-op
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
index a76f83c..3b28afd 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
@@ -60,36 +60,27 @@
         screenshotRule.screenshotTest("taskThumbnailView_uninitialized") { activity ->
             activity.actionBar?.hide()
             val taskThumbnailView = createTaskThumbnailView(activity)
-            taskThumbnailViewModel.uiState.value = BackgroundOnly(Color.YELLOW)
-            taskThumbnailViewModel.uiState.value = Uninitialized
+            taskThumbnailView.setState(Uninitialized)
             taskThumbnailView
         }
     }
 
     @Test
     fun taskThumbnailView_recyclesToUninitialized() {
-        screenshotRule.screenshotTest(
-            "taskThumbnailView_uninitialized",
-            viewProvider = { activity ->
-                activity.actionBar?.hide()
-                val taskThumbnailView = createTaskThumbnailView(activity)
-                taskThumbnailViewModel.uiState.value = BackgroundOnly(Color.YELLOW)
-                taskThumbnailView
-            },
-            checkView = { _, taskThumbnailView ->
-                // Call onRecycle() after View is attached (end of block above)
-                (taskThumbnailView as TaskThumbnailView).onRecycle()
-                false
-            },
-        )
+        screenshotRule.screenshotTest("taskThumbnailView_uninitialized") { activity ->
+            activity.actionBar?.hide()
+            val taskThumbnailView = createTaskThumbnailView(activity)
+            taskThumbnailView.setState(BackgroundOnly(Color.YELLOW))
+            taskThumbnailView.onRecycle()
+            taskThumbnailView
+        }
     }
 
     @Test
     fun taskThumbnailView_backgroundOnly() {
         screenshotRule.screenshotTest("taskThumbnailView_backgroundOnly") { activity ->
             activity.actionBar?.hide()
-            taskThumbnailViewModel.uiState.value = BackgroundOnly(Color.YELLOW)
-            createTaskThumbnailView(activity)
+            createTaskThumbnailView(activity).apply { setState(BackgroundOnly(Color.YELLOW)) }
         }
     }
 
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
similarity index 98%
rename from quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
index df98606..b39c3f1 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
@@ -17,7 +17,7 @@
 
 package com.android.launcher3.taskbar
 
-import androidx.test.runner.AndroidJUnit4
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.launcher3.statemanager.StateManager
 import com.android.quickstep.RecentsActivity
 import com.android.quickstep.fallback.RecentsState
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
similarity index 98%
rename from quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
index d064f4a..26f1197 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
@@ -99,7 +99,7 @@
                 keyboardQuickSwitchController,
                 taskbarPinningController,
                 optionalBubbleControllers,
-                taskbarDesktopModeController
+                taskbarDesktopModeController,
             )
     }
 }
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
similarity index 97%
rename from quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
index e619e7c..2431020 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
@@ -16,6 +16,7 @@
 package com.android.launcher3.taskbar
 
 import android.app.KeyguardManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING
@@ -23,6 +24,7 @@
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.kotlin.any
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
@@ -30,6 +32,7 @@
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
+@RunWith(AndroidJUnit4::class)
 class TaskbarKeyguardControllerTest : TaskbarBaseTestCase() {
 
     private val baseDragLayer: TaskbarDragLayer = mock()
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
index 13880f1..9ca8a1b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
@@ -19,9 +19,11 @@
 import android.animation.AnimatorTestRule
 import android.content.ComponentName
 import android.content.Intent
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR
 import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW
 import com.android.launcher3.R
 import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
@@ -64,6 +66,7 @@
     FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
     FLAG_ENABLE_BUBBLE_BAR,
 )
+@DisableFlags(FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR)
 class TaskbarOverflowTest {
     @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
 
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
similarity index 90%
rename from quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
index ed0c928..c792783 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -21,29 +21,38 @@
 import android.content.Context
 import android.content.Intent
 import android.content.res.Resources
+import android.graphics.Rect
 import android.os.Process
 import android.os.UserHandle
-import android.platform.test.rule.TestWatcher
-import android.testing.AndroidTestingRunner
+import android.platform.test.annotations.EnableFlags
+import androidx.test.annotation.UiThreadTest
 import com.android.internal.R
 import com.android.launcher3.BubbleTextView.RunningAppState
+import com.android.launcher3.Flags
 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION
 import com.android.launcher3.model.data.AppInfo
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.TaskItemInfo
 import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.taskbar.TaskbarRecentAppsController.TaskState
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.quickstep.RecentsModel
 import com.android.quickstep.RecentsModel.RecentTasksChangedListener
 import com.android.quickstep.TaskIconCache
 import com.android.quickstep.util.DesktopTask
 import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.SingleTask
+import com.android.quickstep.util.SplitTask
 import com.android.systemui.shared.recents.model.Task
+import com.android.wm.shell.shared.split.SplitScreenConstants
 import com.google.common.truth.Truth.assertThat
 import java.util.function.Consumer
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+import org.junit.rules.TestWatcher
 import org.junit.runner.Description
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
@@ -56,7 +65,9 @@
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
-@RunWith(AndroidTestingRunner::class)
+@UiThreadTest
+@RunWith(LauncherMultivalentJUnit::class)
+@EnableFlags(Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR)
 class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() {
 
     @get:Rule val mockitoRule = MockitoJUnit.rule()
@@ -86,6 +97,9 @@
     private var canShowRunningAndRecentAppsAtInit = true
     private var recentTasksChangedListener: RecentTasksChangedListener? = null
 
+    val recentShownTasks: List<Task>
+        get() = recentAppsController.shownTasks.flatMap { it.tasks }
+
     @Before
     fun setUp() {
         super.setup()
@@ -139,7 +153,7 @@
     @Test
     fun canShowRunningAndRecentAppsIsFalseAfterInit_getTasksOnlyCalledInInit() {
         // getTasks() should have been called once from init().
-        verify(mockRecentsModel, times(1)).getTasks(any<Consumer<List<GroupTask>>>())
+        verify(mockRecentsModel, times(1)).getTasks(any<Consumer<List<GroupTask>>>(), any())
         recentAppsController.canShowRunningApps = false
         recentAppsController.canShowRecentApps = false
         prepareHotseatAndRunningAndRecentApps(
@@ -148,29 +162,28 @@
             recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
         )
         // Verify that getTasks() was not called again after the init().
-        verify(mockRecentsModel, times(1)).getTasks(any<Consumer<List<GroupTask>>>())
+        verify(mockRecentsModel, times(1)).getTasks(any<Consumer<List<GroupTask>>>(), any())
     }
 
     @Test
     fun getDesktopItemState_nullItemInfo_returnsNotRunning() {
         setInDesktopMode(true)
-        assertThat(recentAppsController.getDesktopItemState(/* itemInfo= */ null))
-            .isEqualTo(RunningAppState.NOT_RUNNING)
+        val taskState = recentAppsController.getDesktopItemState(/* itemInfo= */ null)
+        assertThat(taskState).isEqualTo(TaskState(RunningAppState.NOT_RUNNING))
     }
 
     @Test
     fun getDesktopItemState_noItemPackage_returnsNotRunning() {
         setInDesktopMode(true)
-        assertThat(recentAppsController.getDesktopItemState(ItemInfo()))
-            .isEqualTo(RunningAppState.NOT_RUNNING)
+        val taskState = recentAppsController.getDesktopItemState(ItemInfo())
+        assertThat(taskState).isEqualTo(TaskState(RunningAppState.NOT_RUNNING))
     }
 
     @Test
     fun getDesktopItemState_noMatchingTasks_returnsNotRunning() {
         setInDesktopMode(true)
-        val itemInfo = createItemInfo("package")
-        assertThat(recentAppsController.getDesktopItemState(itemInfo))
-            .isEqualTo(RunningAppState.NOT_RUNNING)
+        val taskState = recentAppsController.getDesktopItemState(createItemInfo("package"))
+        assertThat(taskState).isEqualTo(TaskState(RunningAppState.NOT_RUNNING))
     }
 
     @Test
@@ -178,10 +191,10 @@
         setInDesktopMode(true)
         val visibleTask = createTask(id = 1, "visiblePackage", isVisible = true)
         updateRecentTasks(runningTasks = listOf(visibleTask), recentTaskPackages = emptyList())
-        val itemInfo = createItemInfo("visiblePackage")
 
-        assertThat(recentAppsController.getDesktopItemState(itemInfo))
-            .isEqualTo(RunningAppState.RUNNING)
+        val taskState = recentAppsController.getDesktopItemState(createItemInfo("visiblePackage"))
+
+        assertThat(taskState).isEqualTo(TaskState(RunningAppState.RUNNING, taskId = 1))
     }
 
     @Test
@@ -189,10 +202,10 @@
         setInDesktopMode(true)
         val minimizedTask = createTask(id = 1, "minimizedPackage", isVisible = false)
         updateRecentTasks(runningTasks = listOf(minimizedTask), recentTaskPackages = emptyList())
-        val itemInfo = createItemInfo("minimizedPackage")
 
-        assertThat(recentAppsController.getDesktopItemState(itemInfo))
-            .isEqualTo(RunningAppState.MINIMIZED)
+        val taskState = recentAppsController.getDesktopItemState(createItemInfo("minimizedPackage"))
+
+        assertThat(taskState).isEqualTo(TaskState(RunningAppState.MINIMIZED, taskId = 1))
     }
 
     @Test
@@ -206,10 +219,10 @@
                 ),
             recentTaskPackages = emptyList(),
         )
-        val itemInfo = createItemInfo("package")
 
-        assertThat(recentAppsController.getDesktopItemState(itemInfo))
-            .isEqualTo(RunningAppState.RUNNING)
+        val taskState = recentAppsController.getDesktopItemState(createItemInfo("package"))
+
+        assertThat(taskState).isEqualTo(TaskState(RunningAppState.RUNNING, taskId = 2))
     }
 
     @Test
@@ -223,10 +236,11 @@
                 ),
             recentTaskPackages = emptyList(),
         )
-        val itemInfo = createItemInfo("package", USER_HANDLE_2)
 
-        assertThat(recentAppsController.getDesktopItemState(itemInfo))
-            .isEqualTo(RunningAppState.NOT_RUNNING)
+        val taskState =
+            recentAppsController.getDesktopItemState(createItemInfo("package", USER_HANDLE_2))
+
+        assertThat(taskState).isEqualTo(TaskState(RunningAppState.NOT_RUNNING))
     }
 
     @Test
@@ -323,8 +337,12 @@
         assertThat(hotseatItem1.taskId).isEqualTo(1)
     }
 
+    /**
+     * Tests that in desktop mode, when two tasks have the same package name and one is in the
+     * hotseat, only the hotseat item represents the app, and no duplicate is shown in recent apps.
+     */
     @Test
-    fun updateHotseatItemInfos_inDesktopMode_twoRunningTasksSamePackage_hotseatCoversFirstTask() {
+    fun updateHotseatItemInfos_inDesktopMode_twoRunningTasksSamePackage_onlyHotseatCoversTask() {
         setInDesktopMode(true)
 
         val newHotseatItems =
@@ -338,16 +356,15 @@
                 recentTaskPackages = emptyList(),
             )
 
-        // First task is in Hotseat Items
+        // The task is in Hotseat Items
         assertThat(newHotseatItems).hasLength(2)
         assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java)
         assertThat(newHotseatItems[1]).isNotInstanceOf(TaskItemInfo::class.java)
         val hotseatItem1 = newHotseatItems[0] as TaskItemInfo
-        assertThat(hotseatItem1.taskId).isEqualTo(1)
-        // Second task is in shownTasks
-        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
-        assertThat(shownTasks)
-            .containsExactlyElementsIn(listOf(createTask(id = 2, HOTSEAT_PACKAGE_1)))
+        assertThat(hotseatItem1.targetPackage).isEqualTo(HOTSEAT_PACKAGE_1)
+
+        // The other task of the same package is not in recentShownTasks
+        assertThat(recentShownTasks).isEmpty()
     }
 
     @Test
@@ -430,8 +447,7 @@
             runningTasks = listOf(task1, task2),
             recentTaskPackages = emptyList(),
         )
-        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
-        assertThat(shownTasks).containsExactlyElementsIn(listOf(task1, task2))
+        assertThat(recentShownTasks).containsExactlyElementsIn(listOf(task1, task2))
     }
 
     @Test
@@ -526,12 +542,15 @@
             recentTaskPackages = emptyList(),
         )
 
-        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
-        assertThat(shownTasks).isEqualTo(listOf(task1, task2))
+        assertThat(recentShownTasks).isEqualTo(listOf(task1, task2))
     }
 
+    /**
+     * Tests that when multiple instances of the same app are running in desktop mode and the app is
+     * not in the hotseat, only one instance is shown in the recent apps section.
+     */
     @Test
-    fun onRecentTasksChanged_inDesktopMode_multiInstance_shownTasks_maintainsOrder() {
+    fun onRecentTasksChanged_inDesktopMode_multiInstance_noHotseat_shownTasksHasOneInstance() {
         setInDesktopMode(true)
         val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
         val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_1)
@@ -541,43 +560,9 @@
             recentTaskPackages = emptyList(),
         )
 
-        prepareHotseatAndRunningAndRecentApps(
-            hotseatPackages = emptyList(),
-            runningTasks = listOf(task2, task1),
-            recentTaskPackages = emptyList(),
-        )
-
-        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
-        assertThat(shownTasks).isEqualTo(listOf(task1, task2))
-    }
-
-    @Test
-    fun updateHotseatItems_inDesktopMode_multiInstanceHotseatPackage_shownItems_maintainsOrder() {
-        setInDesktopMode(true)
-        val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
-        val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_1)
-        prepareHotseatAndRunningAndRecentApps(
-            hotseatPackages = listOf(RUNNING_APP_PACKAGE_1),
-            runningTasks = listOf(task1, task2),
-            recentTaskPackages = emptyList(),
-        )
-        updateRecentTasks( // Trigger a recent-tasks change before calling updateHotseatItems()
-            runningTasks = listOf(task2, task1),
-            recentTaskPackages = emptyList(),
-        )
-
-        prepareHotseatAndRunningAndRecentApps(
-            hotseatPackages = listOf(RUNNING_APP_PACKAGE_1),
-            runningTasks = listOf(task2, task1),
-            recentTaskPackages = emptyList(),
-        )
-
-        val newHotseatItems = recentAppsController.shownHotseatItems
-        assertThat(newHotseatItems).hasSize(1)
-        assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java)
-        assertThat((newHotseatItems[0] as TaskItemInfo).taskId).isEqualTo(1)
-        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
-        assertThat(shownTasks).isEqualTo(listOf(task2))
+        // Assert that recentShownTasks contains only one instance of the app
+        assertThat(recentShownTasks).hasSize(1)
+        assertThat(recentShownTasks[0].key.packageName).isEqualTo(RUNNING_APP_PACKAGE_1)
     }
 
     @Test
@@ -859,8 +844,7 @@
             runningTasks = runningTasks,
             recentTaskPackages = emptyList(),
         )
-        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
-        assertThat(shownTasks).contains(runningTask)
+        assertThat(recentShownTasks).contains(runningTask)
         assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1))
     }
 
@@ -903,7 +887,7 @@
                 taskListChangeId
             }
             .whenever(mockRecentsModel)
-            .getTasks(any<Consumer<List<GroupTask>>>())
+            .getTasks(any<Consumer<List<GroupTask>>>(), any())
         recentTasksChangedListener?.onRecentTasksChanged()
     }
 
@@ -934,15 +918,21 @@
         return packageNames.map { packageName ->
             if (packageName.startsWith("split")) {
                 val splitPackages = packageName.split("_")
-                GroupTask(
+                SplitTask(
                     createTask(100, splitPackages[0]),
                     createTask(101, splitPackages[1]),
-                    /* splitBounds = */ null,
+                    SplitConfigurationOptions.SplitBounds(
+                        /* leftTopBounds = */ Rect(),
+                        /* rightBottomBounds = */ Rect(),
+                        /* leftTopTaskId = */ -1,
+                        /* rightBottomTaskId = */ -1,
+                        /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50,
+                    ),
                 )
             } else {
                 // Use the number at the end of the test packageName as the id.
                 val id = 1000 + packageName[packageName.length - 1].code
-                GroupTask(createTask(id, packageName))
+                SingleTask(createTask(id, packageName))
             }
         }
     }
@@ -967,7 +957,7 @@
     }
 
     private fun setInDesktopMode(inDesktopMode: Boolean) {
-        whenever(taskbarControllers.taskbarDesktopModeController.areDesktopTasksVisible)
+        whenever(taskbarControllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar())
             .thenReturn(inDesktopMode)
     }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
index 588c22c..021e1e4 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
@@ -53,7 +53,7 @@
 import com.android.launcher3.util.LauncherMultivalentJUnit
 import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED
-import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE
 import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.TruthJUnit.assume
@@ -542,7 +542,7 @@
         assume().that(activityContext.isHardwareKeyboard).isFalse()
 
         getInstrumentation().runOnMainSync {
-            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, false)
+            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, false)
             animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME)
         }
         assertThat(viewController.areIconsVisible()).isFalse()
@@ -555,7 +555,7 @@
         assume().that(activityContext.isHardwareKeyboard).isFalse()
 
         getInstrumentation().runOnMainSync {
-            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
+            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, true)
             animatorTestRule.advanceTimeBy(0)
         }
 
@@ -574,7 +574,7 @@
 
         // Start with IME shown.
         getInstrumentation().runOnMainSync {
-            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
+            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, true)
             animatorTestRule.advanceTimeBy(0)
         }
 
@@ -600,7 +600,7 @@
         assume().that(activityContext.isHardwareKeyboard).isFalse()
 
         getInstrumentation().runOnMainSync {
-            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, false)
+            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, false)
             animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME)
         }
         assertThat(viewController.areIconsVisible()).isFalse()
@@ -613,7 +613,7 @@
         assume().that(activityContext.isHardwareKeyboard).isFalse()
 
         getInstrumentation().runOnMainSync {
-            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, false)
+            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, false)
             animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME)
         }
 
@@ -633,7 +633,7 @@
         assume().that(activityContext.isHardwareKeyboard).isFalse()
 
         getInstrumentation().runOnMainSync {
-            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
+            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, true)
             animatorTestRule.advanceTimeBy(0)
         }
 
@@ -653,7 +653,7 @@
 
         getInstrumentation().runOnMainSync {
             stashController.updateStateForFlag(FLAG_IN_APP, true)
-            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
+            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, true)
         }
 
         assertThat(stashController.isStashed).isFalse()
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
index f2dcf77..df70b10 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
@@ -28,6 +28,7 @@
 import com.android.launcher3.taskbar.TaskbarIconType.OVERFLOW
 import com.android.launcher3.taskbar.TaskbarIconType.RECENT
 import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.SingleTask
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.Task.TaskKey
 import com.google.common.truth.FailureMetadata
@@ -56,7 +57,7 @@
     /** Creates a list of fake recent tasks. */
     fun createRecents(size: Int): List<GroupTask> {
         return List(size) {
-            GroupTask(
+            SingleTask(
                 Task().apply {
                     key =
                         TaskKey(
@@ -99,9 +100,9 @@
     /** Verifies that recents from [startIndex] have IDs that match [expectedIds] in order. */
     fun hasRecentsOrder(startIndex: Int, expectedIds: List<Int>) {
         val actualIds =
-            view.iconViews.slice(startIndex..<startIndex + expectedIds.size).map {
+            view.iconViews.slice(startIndex..<startIndex + expectedIds.size).flatMap {
                 assertThat(it.tag).isInstanceOf(GroupTask::class.java)
-                (it.tag as? GroupTask)?.task1?.key?.id
+                (it.tag as GroupTask).tasks.map { task -> task.key.id }
             }
         assertThat(actualIds).containsExactlyElementsIn(expectedIds).inOrder()
     }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
index 542eb64..0c74610 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
@@ -18,6 +18,8 @@
 
 import static com.android.quickstep.AbsSwipeUpHandler.STATE_HANDLER_INVALIDATED;
 import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
+import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
 
 import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertTrue;
@@ -68,6 +70,7 @@
 import com.android.quickstep.views.RecentsViewContainer;
 import com.android.systemui.shared.Flags;
 import com.android.systemui.shared.system.InputConsumerController;
+import com.android.wm.shell.shared.split.SplitBounds;
 
 import com.google.android.msdl.data.model.MSDLToken;
 
@@ -140,6 +143,12 @@
     public void setUpAnimationTargets() {
         Bundle extras = new Bundle();
         extras.putBoolean(KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION, true);
+        extras.putParcelable(KEY_EXTRA_SPLIT_BOUNDS, new SplitBounds(
+                /* leftTopBounds = */ new Rect(),
+                /* rightBottomBounds = */ new Rect(),
+                /* leftTopTaskId = */ -1,
+                /* rightBottomTaskId = */ -1,
+                /* snapPosition = */ SNAP_TO_2_50_50));
         mRecentsAnimationTargets = new RecentsAnimationTargets(
                 new RemoteAnimationTarget[] {mRemoteAnimationTarget},
                 new RemoteAnimationTarget[] {mRemoteAnimationTarget},
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
index f05b422..af741f6 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
@@ -86,17 +86,11 @@
         whenever(displayManager.displays).thenReturn(arrayOf(display))
 
         sandboxContext.initDaggerComponent(
-            DaggerTestComponent.builder().bindSystemUiProxy(systemUiProxy)
+            DaggerTestComponent.builder()
+                .bindSystemUiProxy(systemUiProxy)
+                .bindRotationHelper(mock(RotationTouchHelper::class.java))
+                .bindRecentsState(mock(RecentsAnimationDeviceState::class.java))
         )
-        sandboxContext.putObject(
-            RotationTouchHelper.INSTANCE,
-            mock(RotationTouchHelper::class.java),
-        )
-        sandboxContext.putObject(
-            RecentsAnimationDeviceState.INSTANCE,
-            mock(RecentsAnimationDeviceState::class.java),
-        )
-
         gestureState = spy(GestureState(OverviewComponentObserver.INSTANCE.get(sandboxContext), 0))
 
         underTest =
@@ -117,20 +111,14 @@
         gestureState.setTrackpadGestureType(GestureState.TrackpadGestureType.THREE_FINGER)
         underTest.onGestureEnded(flingSpeed, PointF())
         verify(systemUiProxy)
-            .updateContextualEduStats(
-                /* isTrackpadGesture= */ eq(true),
-                eq(GestureType.HOME),
-            )
+            .updateContextualEduStats(/* isTrackpadGesture= */ eq(true), eq(GestureType.HOME))
     }
 
     @Test
     fun goHomeFromAppByTouch_updateEduStats() {
         underTest.onGestureEnded(flingSpeed, PointF())
         verify(systemUiProxy)
-            .updateContextualEduStats(
-                /* isTrackpadGesture= */ eq(false),
-                eq(GestureType.HOME),
-            )
+            .updateContextualEduStats(/* isTrackpadGesture= */ eq(false), eq(GestureType.HOME))
     }
 }
 
@@ -141,6 +129,10 @@
     interface Builder : LauncherAppComponent.Builder {
         @BindsInstance fun bindSystemUiProxy(proxy: SystemUiProxy): Builder
 
+        @BindsInstance fun bindRotationHelper(helper: RotationTouchHelper): Builder
+
+        @BindsInstance fun bindRecentsState(state: RecentsAnimationDeviceState): Builder
+
         override fun build(): TestComponent
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
index e6c5a6c..66c4ab5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
@@ -33,11 +33,13 @@
 import com.android.quickstep.views.RecentsView;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
 @SmallTest
 @RunWith(LauncherMultivalentJUnit.class)
+@Ignore
 public class LauncherSwipeHandlerV2TestCase extends AbsSwipeUpHandlerTestCase<
         LauncherState,
         QuickstepLauncher,
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
similarity index 78%
rename from quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
index ccc0311..cad3b99 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
@@ -21,7 +21,9 @@
 import static junit.framework.TestCase.assertNull;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.anyInt;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -33,7 +35,9 @@
 import android.app.TaskInfo;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Rect;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
@@ -42,9 +46,12 @@
 import com.android.quickstep.views.TaskViewType;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.wm.shell.shared.GroupedTaskInfo;
+import com.android.wm.shell.shared.split.SplitBounds;
+import com.android.wm.shell.shared.split.SplitScreenConstants;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -56,6 +63,7 @@
 import java.util.stream.Collectors;
 
 @SmallTest
+@RunWith(AndroidJUnit4.class)
 public class RecentTasksListTest {
 
     @Mock
@@ -94,7 +102,12 @@
     @Test
     public void loadTasksInBackground_onlyKeys_noValidTaskDescription() throws Exception  {
         GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks(
-                new RecentTaskInfo(), new RecentTaskInfo(), null);
+                new RecentTaskInfo(), new RecentTaskInfo(), new SplitBounds(
+                        /* leftTopBounds = */ new Rect(),
+                        /* rightBottomBounds = */ new Rect(),
+                        /* leftTopTaskId = */ -1,
+                        /* rightBottomTaskId = */ -1,
+                        /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50));
         when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
                 .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
 
@@ -124,7 +137,13 @@
         task1.taskDescription = new ActivityManager.TaskDescription(taskDescription);
         RecentTaskInfo task2 = new RecentTaskInfo();
         task2.taskDescription = new ActivityManager.TaskDescription();
-        GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks(task1, task2, null);
+        GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks(task1, task2,
+                new SplitBounds(
+                        /* leftTopBounds = */ new Rect(),
+                        /* rightBottomBounds = */ new Rect(),
+                        /* leftTopTaskId = */ -1,
+                        /* rightBottomTaskId = */ -1,
+                        /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50));
         when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
                 .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
 
@@ -157,12 +176,15 @@
         List<Task> actualFreeformTasks = taskList.get(0).getTasks();
         assertEquals(3, actualFreeformTasks.size());
         assertEquals(1, actualFreeformTasks.get(0).key.id);
+        assertFalse(actualFreeformTasks.get(0).isMinimized);
         assertEquals(4, actualFreeformTasks.get(1).key.id);
+        assertFalse(actualFreeformTasks.get(1).isMinimized);
         assertEquals(5, actualFreeformTasks.get(2).key.id);
+        assertFalse(actualFreeformTasks.get(2).isMinimized);
     }
 
     @Test
-    public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_doesNotCreateDesktopTask()
+    public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_createDesktopTask()
             throws Exception {
         List<TaskInfo> tasks = Arrays.asList(
                 createRecentTaskInfo(1 /* taskId */),
@@ -177,7 +199,16 @@
         List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(
                 Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */);
 
-        assertEquals(0, taskList.size());
+        assertEquals(1, taskList.size());
+        assertEquals(TaskViewType.DESKTOP, taskList.get(0).taskViewType);
+        List<Task> actualFreeformTasks = taskList.get(0).getTasks();
+        assertEquals(3, actualFreeformTasks.size());
+        assertEquals(1, actualFreeformTasks.get(0).key.id);
+        assertTrue(actualFreeformTasks.get(0).isMinimized);
+        assertEquals(4, actualFreeformTasks.get(1).key.id);
+        assertTrue(actualFreeformTasks.get(1).isMinimized);
+        assertEquals(5, actualFreeformTasks.get(2).key.id);
+        assertTrue(actualFreeformTasks.get(2).isMinimized);
     }
 
     private TaskInfo createRecentTaskInfo(int taskId) {
diff --git a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
similarity index 66%
rename from quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
index 80fbce7..b652ee8 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
@@ -1,14 +1,17 @@
 package com.android.quickstep
 
-import android.content.Context
-import android.testing.AndroidTestingRunner
-import androidx.test.core.app.ApplicationProvider
+import androidx.test.annotation.UiThreadTest
 import androidx.test.filters.SmallTest
+import com.android.launcher3.dagger.LauncherComponentProvider
 import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
 import com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE
 import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
 import com.android.launcher3.util.DisplayController.Info
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import com.android.launcher3.util.LauncherMultivalentJUnit
 import com.android.launcher3.util.NavigationMode
+import com.android.launcher3.util.SandboxApplication
 import com.android.quickstep.util.GestureExclusionManager
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING
@@ -22,7 +25,9 @@
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED
 import com.google.common.truth.Truth.assertThat
+import org.junit.After
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
@@ -30,24 +35,43 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
 import org.mockito.kotlin.whenever
 
 /** Unit test for [RecentsAnimationDeviceState]. */
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@UiThreadTest
+@RunWith(LauncherMultivalentJUnit::class)
 class RecentsAnimationDeviceStateTest {
 
+    @get:Rule val context = SandboxApplication()
+
     @Mock private lateinit var exclusionManager: GestureExclusionManager
     @Mock private lateinit var info: Info
 
-    private val context = ApplicationProvider.getApplicationContext() as Context
     private lateinit var underTest: RecentsAnimationDeviceState
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        underTest = RecentsAnimationDeviceState(context, exclusionManager)
+
+        val component = LauncherComponentProvider.get(context)
+        underTest =
+            RecentsAnimationDeviceState(
+                context,
+                exclusionManager,
+                component.displayController,
+                component.contextualSearchStateManager,
+                component.rotationTouchHelper,
+                component.settingsCache,
+                component.daggerSingletonTracker,
+            )
+    }
+
+    @After
+    fun tearDown() {
+        UI_HELPER_EXECUTOR.submit {}.get()
+        MAIN_EXECUTOR.submit {}.get()
     }
 
     @Test
@@ -64,7 +88,7 @@
 
         underTest.registerExclusionListener()
 
-        verifyZeroInteractions(exclusionManager)
+        verifyNoMoreInteractions(exclusionManager)
     }
 
     @Test
@@ -85,7 +109,7 @@
 
         underTest.unregisterExclusionListener()
 
-        verifyZeroInteractions(exclusionManager)
+        verifyNoMoreInteractions(exclusionManager)
     }
 
     @Test
@@ -116,13 +140,13 @@
 
         underTest.onDisplayInfoChanged(context, info, CHANGE_DENSITY)
 
-        verifyZeroInteractions(exclusionManager)
+        verifyNoMoreInteractions(exclusionManager)
     }
 
     @Test
     fun trackpadGesturesNotAllowedForSelectedStates() {
-        val disablingStates = GESTURE_DISABLING_SYSUI_STATES +
-                SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED
+        val disablingStates =
+            GESTURE_DISABLING_SYSUI_STATES + SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED
 
         allSysUiStates().forEach { state ->
             val canStartGesture = !disablingStates.contains(state)
@@ -133,13 +157,13 @@
 
     @Test
     fun trackpadGesturesNotAllowedIfHomeAndOverviewIsDisabled() {
-        val stateToExpectedResult = mapOf(
-            SYSUI_STATE_HOME_DISABLED to true,
-            SYSUI_STATE_OVERVIEW_DISABLED to true,
-            DEFAULT_STATE
-                .enable(SYSUI_STATE_OVERVIEW_DISABLED)
-                .enable(SYSUI_STATE_HOME_DISABLED) to false
-        )
+        val stateToExpectedResult =
+            mapOf(
+                SYSUI_STATE_HOME_DISABLED to true,
+                SYSUI_STATE_OVERVIEW_DISABLED to true,
+                DEFAULT_STATE.enable(SYSUI_STATE_OVERVIEW_DISABLED)
+                    .enable(SYSUI_STATE_HOME_DISABLED) to false,
+            )
 
         stateToExpectedResult.forEach { (state, allowed) ->
             underTest.setSystemUiFlags(state)
@@ -160,22 +184,19 @@
 
     @Test
     fun systemGesturesNotAllowedWhenGestureStateDisabledAndNavBarVisible() {
-        val stateToExpectedResult = mapOf(
-            DEFAULT_STATE
-                .enable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
-                .disable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
-            DEFAULT_STATE
-                .enable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
-                .enable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
-            DEFAULT_STATE
-                .disable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
-                .disable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
-            DEFAULT_STATE
-                .disable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
-                .enable(SYSUI_STATE_NAV_BAR_HIDDEN) to false,
-        )
+        val stateToExpectedResult =
+            mapOf(
+                DEFAULT_STATE.enable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
+                    .disable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
+                DEFAULT_STATE.enable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
+                    .enable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
+                DEFAULT_STATE.disable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
+                    .disable(SYSUI_STATE_NAV_BAR_HIDDEN) to true,
+                DEFAULT_STATE.disable(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY)
+                    .enable(SYSUI_STATE_NAV_BAR_HIDDEN) to false,
+            )
 
-        stateToExpectedResult.forEach {(state, gestureAllowed) ->
+        stateToExpectedResult.forEach { (state, gestureAllowed) ->
             underTest.setSystemUiFlags(state)
             assertThat(underTest.canStartSystemGesture()).isEqualTo(gestureAllowed)
         }
@@ -187,14 +208,15 @@
     }
 
     companion object {
-        private val GESTURE_DISABLING_SYSUI_STATES = listOf(
-            SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
-            SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
-            SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
-            SYSUI_STATE_MAGNIFICATION_OVERLAP,
-            SYSUI_STATE_DEVICE_DREAMING,
-            SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION,
-        )
+        private val GESTURE_DISABLING_SYSUI_STATES =
+            listOf(
+                SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
+                SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
+                SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
+                SYSUI_STATE_MAGNIFICATION_OVERLAP,
+                SYSUI_STATE_DEVICE_DREAMING,
+                SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION,
+            )
         private const val SYSUI_STATES_COUNT = 33
         private const val DEFAULT_STATE = 0L
     }
diff --git a/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
similarity index 89%
rename from quickstep/tests/src/com/android/quickstep/RecentsModelTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
index 4e34e11..99a1c59 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
@@ -32,22 +32,28 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Rect;
 import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 import com.android.launcher3.graphics.ThemeManager;
 import com.android.launcher3.icons.IconProvider;
+import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SplitTask;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.wm.shell.shared.split.SplitScreenConstants;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -56,6 +62,7 @@
 import java.util.function.Consumer;
 
 @SmallTest
+@RunWith(AndroidJUnit4.class)
 public class RecentsModelTest {
     @Mock
     private Context mContext;
@@ -170,7 +177,13 @@
         Task.TaskKey taskKey2 = new Task.TaskKey(taskInfo2);
         Task task2 = Task.from(taskKey2, taskInfo2, false);
 
-        allTasks.add(new GroupTask(task1, task2, null));
+        allTasks.add(
+                new SplitTask(task1, task2, new SplitConfigurationOptions.SplitBounds(
+                        /* leftTopBounds = */ new Rect(),
+                        /* rightBottomBounds = */ new Rect(),
+                        /* leftTopTaskId = */ -1,
+                        /* rightBottomTaskId = */ -1,
+                        /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50)));
         return allTasks;
     }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
similarity index 96%
rename from quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
index a87c328..6e9885a 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
@@ -27,16 +27,19 @@
 import android.content.Context;
 import android.content.Intent;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
+@RunWith(AndroidJUnit4.class)
 public class TaskAnimationManagerTest {
 
     protected final Context mContext =
diff --git a/quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskThumbnailCacheTest.java
similarity index 96%
rename from quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/TaskThumbnailCacheTest.java
index 4e04261..3686c16 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskThumbnailCacheTest.java
@@ -28,6 +28,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.launcher3.R;
@@ -35,12 +36,14 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.concurrent.Executor;
 
 @SmallTest
+@RunWith(AndroidJUnit4.class)
 public class TaskThumbnailCacheTest {
     @Mock
     private Context mContext;
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
index 1c9ce0b..35af29f 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
@@ -58,10 +58,10 @@
             tasks.value.map {
                 it.apply {
                     thumbnail = thumbnailDataMap[it.key.id]
-                    taskIconDataMap[it.key.id].let { data ->
-                        title = data?.title
-                        titleDescription = data?.titleDescription
-                        icon = data?.icon
+                    taskIconDataMap[it.key.id]?.let { data ->
+                        title = data.title
+                        titleDescription = data.titleDescription
+                        icon = data.icon
                     }
                 }
             }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
index b6cf5bd..823f808 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
@@ -19,13 +19,17 @@
 import android.content.ComponentName
 import android.content.Intent
 import android.graphics.Bitmap
+import android.graphics.Rect
 import android.graphics.drawable.Drawable
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.launcher3.util.TestDispatcherProvider
 import com.android.quickstep.util.DesktopTask
-import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.SingleTask
+import com.android.quickstep.util.SplitTask
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.first
@@ -48,8 +52,18 @@
     private val tasks = (0..5).map(::createTaskWithId)
     private val defaultTaskList =
         listOf(
-            GroupTask(tasks[0]),
-            GroupTask(tasks[1], tasks[2], null),
+            SingleTask(tasks[0]),
+            SplitTask(
+                tasks[1],
+                tasks[2],
+                SplitConfigurationOptions.SplitBounds(
+                    /* leftTopBounds = */ Rect(),
+                    /* rightBottomBounds = */ Rect(),
+                    /* leftTopTaskId = */ -1,
+                    /* rightBottomTaskId = */ -1,
+                    /* snapPosition = */ SNAP_TO_2_50_50,
+                ),
+            ),
             DesktopTask(tasks.subList(3, 6)),
         )
     private val recentsModel = FakeRecentTasksDataSource()
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCaseTest.kt
new file mode 100644
index 0000000..b036bce
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCaseTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2025 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.recents.domain.usecase
+
+import android.content.ComponentName
+import android.content.Intent
+import android.graphics.Color
+import android.graphics.drawable.ShapeDrawable
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.quickstep.recents.data.FakeTasksRepository
+import com.android.quickstep.recents.domain.model.TaskModel
+import com.android.systemui.shared.recents.model.Task
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class GetTaskUseCaseTest {
+    private val unconfinedTestDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(unconfinedTestDispatcher)
+
+    private val tasksRepository = FakeTasksRepository()
+    private val sut = GetTaskUseCase(repository = tasksRepository)
+
+    @Before
+    fun setUp() {
+        tasksRepository.seedTasks(listOf(TASK_1))
+    }
+
+    @Test
+    fun taskNotSeeded_returnsNull() =
+        testScope.runTest {
+            val result = sut.invoke(NOT_FOUND_TASK_ID).firstOrNull()
+            assertThat(result).isNull()
+        }
+
+    @Test
+    fun taskNotVisible_returnsNull() =
+        testScope.runTest {
+            val result = sut.invoke(TASK_1_ID).firstOrNull()
+            assertThat(result).isNull()
+        }
+
+    @Test
+    fun taskVisible_returnsData() =
+        testScope.runTest {
+            tasksRepository.setVisibleTasks(setOf(TASK_1_ID))
+            val expectedResult =
+                TaskModel(
+                    id = TASK_1_ID,
+                    title = "Title $TASK_1_ID",
+                    titleDescription = "Content Description $TASK_1_ID",
+                    icon = TASK_1_ICON,
+                    thumbnail = null,
+                    backgroundColor = Color.BLACK,
+                    isLocked = false,
+                )
+            val result = sut.invoke(TASK_1_ID).firstOrNull()
+            assertThat(result).isEqualTo(expectedResult)
+        }
+
+    private companion object {
+        const val NOT_FOUND_TASK_ID = 404
+        private const val TASK_1_ID = 1
+        private val TASK_1_ICON = ShapeDrawable()
+        private val TASK_1 =
+            Task(
+                    Task.TaskKey(
+                        /* id = */ TASK_1_ID,
+                        /* windowingMode = */ 0,
+                        /* intent = */ Intent(),
+                        /* sourceComponent = */ ComponentName("", ""),
+                        /* userId = */ 0,
+                        /* lastActiveTime = */ 2000,
+                    )
+                )
+                .apply {
+                    title = "Title 1"
+                    titleDescription = "Content Description 1"
+                    colorBackground = Color.BLACK
+                    icon = TASK_1_ICON
+                    thumbnail = null
+                    isLocked = false
+                }
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt
new file mode 100644
index 0000000..124045f
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2025 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.recents.ui.mapper
+
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.drawable.ShapeDrawable
+import android.platform.test.annotations.EnableFlags
+import android.view.Surface
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.Flags
+import com.android.quickstep.recents.ui.viewmodel.TaskData
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TaskUiStateMapperTest {
+
+    @Test
+    fun taskData_isNull_returns_Uninitialized() {
+        val result =
+            TaskUiStateMapper.toTaskThumbnailUiState(
+                taskData = null,
+                isLiveTile = false,
+                hasHeader = false,
+            )
+        assertThat(result).isEqualTo(TaskThumbnailUiState.Uninitialized)
+    }
+
+    @Test
+    fun taskData_isLiveTile_returns_LiveTile() {
+        val inputs =
+            listOf(TASK_DATA, TASK_DATA.copy(thumbnailData = null), TASK_DATA.copy(isLocked = true))
+        inputs.forEach { input ->
+            val result =
+                TaskUiStateMapper.toTaskThumbnailUiState(
+                    taskData = input,
+                    isLiveTile = true,
+                    hasHeader = false,
+                )
+            assertThat(result).isEqualTo(LiveTile.WithoutHeader)
+        }
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+    @Test
+    fun taskData_isLiveTileWithHeader_returns_LiveTileWithHeader() {
+        val inputs =
+            listOf(
+                TASK_DATA,
+                TASK_DATA.copy(thumbnailData = null),
+                TASK_DATA.copy(isLocked = true),
+                TASK_DATA.copy(title = null),
+            )
+        val expected =
+            LiveTile.WithHeader(header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION))
+        inputs.forEach { taskData ->
+            val result =
+                TaskUiStateMapper.toTaskThumbnailUiState(
+                    taskData = taskData,
+                    isLiveTile = true,
+                    hasHeader = true,
+                )
+            assertThat(result).isEqualTo(expected)
+        }
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+    @Test
+    fun taskData_isLiveTileWithHeader_missingHeaderData_returns_LiveTileWithoutHeader() {
+        val inputs =
+            listOf(
+                TASK_DATA.copy(icon = null),
+                TASK_DATA.copy(titleDescription = null),
+                TASK_DATA.copy(icon = null, titleDescription = null),
+            )
+
+        inputs.forEach { taskData ->
+            val result =
+                TaskUiStateMapper.toTaskThumbnailUiState(
+                    taskData = taskData,
+                    isLiveTile = true,
+                    hasHeader = true,
+                )
+            assertThat(result).isEqualTo(LiveTile.WithoutHeader)
+        }
+    }
+
+    @Test
+    fun taskData_isStaticTile_returns_SnapshotSplash() {
+        val result =
+            TaskUiStateMapper.toTaskThumbnailUiState(
+                taskData = TASK_DATA,
+                isLiveTile = false,
+                hasHeader = false,
+            )
+
+        val expected =
+            TaskThumbnailUiState.SnapshotSplash(
+                snapshot =
+                    Snapshot.WithoutHeader(
+                        backgroundColor = TASK_BACKGROUND_COLOR,
+                        bitmap = TASK_THUMBNAIL,
+                        thumbnailRotation = Surface.ROTATION_0,
+                    ),
+                splash = TASK_ICON,
+            )
+
+        assertThat(result).isEqualTo(expected)
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+    @Test
+    fun taskData_isStaticTile_withHeader_returns_SnapshotSplashWithHeader() {
+        val inputs = listOf(TASK_DATA, TASK_DATA.copy(title = null))
+        val expected =
+            TaskThumbnailUiState.SnapshotSplash(
+                snapshot =
+                    Snapshot.WithHeader(
+                        backgroundColor = TASK_BACKGROUND_COLOR,
+                        bitmap = TASK_THUMBNAIL,
+                        thumbnailRotation = Surface.ROTATION_0,
+                        header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION),
+                    ),
+                splash = TASK_ICON,
+            )
+        inputs.forEach { taskData ->
+            val result =
+                TaskUiStateMapper.toTaskThumbnailUiState(
+                    taskData = taskData,
+                    isLiveTile = false,
+                    hasHeader = true,
+                )
+            assertThat(result).isEqualTo(expected)
+        }
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+    @Test
+    fun taskData_isStaticTile_missingHeaderData_returns_SnapshotSplashWithoutHeader() {
+        val inputs =
+            listOf(
+                TASK_DATA.copy(titleDescription = null, icon = null),
+                TASK_DATA.copy(titleDescription = null),
+                TASK_DATA.copy(icon = null),
+            )
+        val expected =
+            Snapshot.WithoutHeader(
+                backgroundColor = TASK_BACKGROUND_COLOR,
+                thumbnailRotation = Surface.ROTATION_0,
+                bitmap = TASK_THUMBNAIL,
+            )
+        inputs.forEach { taskData ->
+            val result =
+                TaskUiStateMapper.toTaskThumbnailUiState(
+                    taskData = taskData,
+                    isLiveTile = false,
+                    hasHeader = true,
+                )
+
+            assertThat(result).isInstanceOf(TaskThumbnailUiState.SnapshotSplash::class.java)
+            result as TaskThumbnailUiState.SnapshotSplash
+            assertThat(result.snapshot).isEqualTo(expected)
+        }
+    }
+
+    @Test
+    fun taskData_thumbnailIsNull_returns_BackgroundOnly() {
+        val result =
+            TaskUiStateMapper.toTaskThumbnailUiState(
+                taskData = TASK_DATA.copy(thumbnailData = null),
+                isLiveTile = false,
+                hasHeader = false,
+            )
+
+        val expected = TaskThumbnailUiState.BackgroundOnly(TASK_BACKGROUND_COLOR)
+        assertThat(result).isEqualTo(expected)
+    }
+
+    @Test
+    fun taskData_isLocked_returns_BackgroundOnly() {
+        val result =
+            TaskUiStateMapper.toTaskThumbnailUiState(
+                taskData = TASK_DATA.copy(isLocked = true),
+                isLiveTile = false,
+                hasHeader = false,
+            )
+
+        val expected = TaskThumbnailUiState.BackgroundOnly(TASK_BACKGROUND_COLOR)
+        assertThat(result).isEqualTo(expected)
+    }
+
+    private companion object {
+        const val TASK_TITLE_DESCRIPTION = "Title Description 1"
+        val TASK_ICON = ShapeDrawable()
+        val TASK_THUMBNAIL = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+        val TASK_THUMBNAIL_DATA =
+            ThumbnailData(thumbnail = TASK_THUMBNAIL, rotation = Surface.ROTATION_0)
+        val TASK_BACKGROUND_COLOR = Color.rgb(1, 2, 3)
+        val TASK_DATA =
+            TaskData.Data(
+                1,
+                title = "Task 1",
+                titleDescription = TASK_TITLE_DESCRIPTION,
+                icon = TASK_ICON,
+                thumbnailData = TASK_THUMBNAIL_DATA,
+                backgroundColor = TASK_BACKGROUND_COLOR,
+                isLocked = false,
+            )
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt
new file mode 100644
index 0000000..7a4b5f2
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt
@@ -0,0 +1,257 @@
+/*
+ * Copyright (C) 2025 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.recents.ui.viewmodel
+
+import android.graphics.Color
+import android.graphics.drawable.ShapeDrawable
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.util.TestDispatcherProvider
+import com.android.quickstep.recents.domain.model.TaskModel
+import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
+import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.views.TaskViewType
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class TaskViewModelTest {
+    private val unconfinedTestDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(unconfinedTestDispatcher)
+
+    private val recentsViewData = RecentsViewData()
+    private val getTaskUseCase = mock<GetTaskUseCase>()
+    private lateinit var sut: TaskViewModel
+
+    @Before
+    fun setUp() {
+        sut =
+            TaskViewModel(
+                taskViewType = TaskViewType.SINGLE,
+                recentsViewData = recentsViewData,
+                getTaskUseCase = getTaskUseCase,
+                dispatcherProvider = TestDispatcherProvider(unconfinedTestDispatcher),
+            )
+        whenever(getTaskUseCase.invoke(TASK_MODEL_1.id)).thenReturn(flow { emit(TASK_MODEL_1) })
+        whenever(getTaskUseCase.invoke(TASK_MODEL_2.id)).thenReturn(flow { emit(TASK_MODEL_2) })
+        whenever(getTaskUseCase.invoke(TASK_MODEL_3.id)).thenReturn(flow { emit(TASK_MODEL_3) })
+        whenever(getTaskUseCase.invoke(INVALID_TASK_ID)).thenReturn(flow { emit(null) })
+        recentsViewData.runningTaskIds.value = emptySet()
+    }
+
+    @Test
+    fun singleTaskRetrieved_when_validTaskId() =
+        testScope.runTest {
+            sut.bind(TASK_MODEL_1.id)
+            val expectedResult =
+                TaskTileUiState(
+                    tasks = listOf(TASK_MODEL_1.toUiState()),
+                    isLiveTile = false,
+                    hasHeader = false,
+                )
+            assertThat(sut.state.first()).isEqualTo(expectedResult)
+        }
+
+    @Test
+    fun hasHeader_when_taskViewTypeIsDesktop() =
+        testScope.runTest {
+            val expectedResults =
+                mapOf(
+                    TaskViewType.SINGLE to false,
+                    TaskViewType.GROUPED to false,
+                    TaskViewType.DESKTOP to true,
+                )
+
+            expectedResults.forEach { (type, expectedResult) ->
+                sut =
+                    TaskViewModel(
+                        taskViewType = type,
+                        recentsViewData = recentsViewData,
+                        getTaskUseCase = getTaskUseCase,
+                        dispatcherProvider = TestDispatcherProvider(unconfinedTestDispatcher),
+                    )
+                sut.bind(TASK_MODEL_1.id)
+                assertThat(sut.state.first().hasHeader).isEqualTo(expectedResult)
+            }
+        }
+
+    @Test
+    fun multipleTasksRetrieved_when_validTaskIds() =
+        testScope.runTest {
+            sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id, INVALID_TASK_ID)
+            val expectedResult =
+                TaskTileUiState(
+                    tasks =
+                        listOf(
+                            TASK_MODEL_1.toUiState(),
+                            TASK_MODEL_2.toUiState(),
+                            TASK_MODEL_3.toUiState(),
+                            TaskData.NoData(INVALID_TASK_ID),
+                        ),
+                    isLiveTile = false,
+                    hasHeader = false,
+                )
+            assertThat(sut.state.first()).isEqualTo(expectedResult)
+        }
+
+    @Test
+    fun isLiveTile_when_runningTasksMatchTasks() =
+        testScope.runTest {
+            recentsViewData.runningTaskShowScreenshot.value = false
+            recentsViewData.runningTaskIds.value =
+                setOf(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+            sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+            val expectedResult =
+                TaskTileUiState(
+                    tasks =
+                        listOf(
+                            TASK_MODEL_1.toUiState(),
+                            TASK_MODEL_2.toUiState(),
+                            TASK_MODEL_3.toUiState(),
+                        ),
+                    isLiveTile = true,
+                    hasHeader = false,
+                )
+            assertThat(sut.state.first()).isEqualTo(expectedResult)
+        }
+
+    @Test
+    fun isNotLiveTile_when_runningTaskShowScreenshotIsTrue() =
+        testScope.runTest {
+            recentsViewData.runningTaskShowScreenshot.value = true
+            recentsViewData.runningTaskIds.value =
+                setOf(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+            sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+            val expectedResult =
+                TaskTileUiState(
+                    tasks =
+                        listOf(
+                            TASK_MODEL_1.toUiState(),
+                            TASK_MODEL_2.toUiState(),
+                            TASK_MODEL_3.toUiState(),
+                        ),
+                    isLiveTile = false,
+                    hasHeader = false,
+                )
+            assertThat(sut.state.first()).isEqualTo(expectedResult)
+        }
+
+    @Test
+    fun isNotLiveTile_when_runningTasksMatchPartialTasks_lessRunningTasks() =
+        testScope.runTest {
+            recentsViewData.runningTaskShowScreenshot.value = false
+            recentsViewData.runningTaskIds.value = setOf(TASK_MODEL_1.id, TASK_MODEL_2.id)
+            sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+            val expectedResult =
+                TaskTileUiState(
+                    tasks =
+                        listOf(
+                            TASK_MODEL_1.toUiState(),
+                            TASK_MODEL_2.toUiState(),
+                            TASK_MODEL_3.toUiState(),
+                        ),
+                    isLiveTile = false,
+                    hasHeader = false,
+                )
+            assertThat(sut.state.first()).isEqualTo(expectedResult)
+        }
+
+    @Test
+    fun isNotLiveTile_when_runningTasksMatchPartialTasks_moreRunningTasks() =
+        testScope.runTest {
+            recentsViewData.runningTaskShowScreenshot.value = false
+            recentsViewData.runningTaskIds.value =
+                setOf(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+            sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id)
+            val expectedResult =
+                TaskTileUiState(
+                    tasks = listOf(TASK_MODEL_1.toUiState(), TASK_MODEL_2.toUiState()),
+                    isLiveTile = false,
+                    hasHeader = false,
+                )
+            assertThat(sut.state.first()).isEqualTo(expectedResult)
+        }
+
+    @Test
+    fun noDataAvailable_when_InvalidTaskId() =
+        testScope.runTest {
+            sut.bind(INVALID_TASK_ID)
+            val expectedResult =
+                TaskTileUiState(
+                    listOf(TaskData.NoData(INVALID_TASK_ID)),
+                    isLiveTile = false,
+                    hasHeader = false,
+                )
+            assertThat(sut.state.first()).isEqualTo(expectedResult)
+        }
+
+    private fun TaskModel.toUiState() =
+        TaskData.Data(
+            taskId = id,
+            title = title,
+            titleDescription = titleDescription,
+            icon = icon!!,
+            thumbnailData = thumbnail,
+            backgroundColor = backgroundColor,
+            isLocked = isLocked,
+        )
+
+    companion object {
+        const val INVALID_TASK_ID = -1
+        val TASK_MODEL_1 =
+            TaskModel(
+                1,
+                "Title 1",
+                "Content Description 1",
+                ShapeDrawable(),
+                ThumbnailData(),
+                Color.BLACK,
+                false,
+            )
+        val TASK_MODEL_2 =
+            TaskModel(
+                2,
+                "Title 2",
+                "Content Description 2",
+                ShapeDrawable(),
+                ThumbnailData(),
+                Color.RED,
+                true,
+            )
+        val TASK_MODEL_3 =
+            TaskModel(
+                3,
+                "Title 3",
+                "Content Description 3",
+                ShapeDrawable(),
+                ThumbnailData(),
+                Color.BLUE,
+                false,
+            )
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt
index 73aa460..0044631 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt
@@ -76,7 +76,7 @@
         assertThat(systemUnderTest.run(TASK_ID)).isEqualTo(thumbnailData.thumbnail)
     }
 
-    companion object {
+    private companion object {
         const val TASK_ID = 0
         const val THUMBNAIL_WIDTH = 100
         const val THUMBNAIL_HEIGHT = 200
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
index a956c9c..22636b0 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
@@ -16,37 +16,16 @@
 
 package com.android.quickstep.task.thumbnail
 
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
-import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-import android.content.ComponentName
-import android.content.Intent
-import android.graphics.Bitmap
-import android.graphics.Color
 import android.graphics.Matrix
-import android.graphics.drawable.Drawable
-import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
-import android.view.Surface
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.launcher3.Flags
 import com.android.launcher3.util.TestDispatcherProvider
-import com.android.quickstep.recents.data.FakeRecentsDeviceProfileRepository
-import com.android.quickstep.recents.data.FakeTasksRepository
-import com.android.quickstep.recents.data.RecentsDeviceProfile
 import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
 import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
 import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
 import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
 import com.android.quickstep.task.viewmodel.TaskContainerData
 import com.android.quickstep.task.viewmodel.TaskThumbnailViewModelImpl
-import com.android.systemui.shared.recents.model.Task
-import com.android.systemui.shared.recents.model.ThumbnailData
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -69,8 +48,6 @@
     private val recentsViewData = RecentsViewData()
     private val taskContainerData = TaskContainerData()
     private val dispatcherProvider = TestDispatcherProvider(dispatcher)
-    private val tasksRepository = FakeTasksRepository()
-    private val deviceProfileRepository = FakeRecentsDeviceProfileRepository()
     private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
     private val splashAlphaUseCase: SplashAlphaUseCase = mock()
 
@@ -79,208 +56,11 @@
             recentsViewData,
             taskContainerData,
             dispatcherProvider,
-            tasksRepository,
-            deviceProfileRepository,
             mGetThumbnailPositionUseCase,
             splashAlphaUseCase,
         )
     }
 
-    private val fullscreenTaskIdRange: IntRange = 0..5
-    private val freeformTaskIdRange: IntRange = 6..10
-
-    private val fullscreenTasks = fullscreenTaskIdRange.map(::createTaskWithId)
-    private val freeformTasks = freeformTaskIdRange.map(::createFreeformTaskWithId)
-    private val tasks = fullscreenTasks + freeformTasks
-
-    @Test
-    fun initialStateIsUninitialized() =
-        testScope.runTest { assertThat(systemUnderTest.uiState.first()).isEqualTo(Uninitialized) }
-
-    @Test
-    fun bindRunningTask_thenStateIs_LiveTile() =
-        testScope.runTest {
-            val taskId = 1
-            tasksRepository.seedTasks(tasks)
-            tasksRepository.setVisibleTasks(setOf(taskId))
-            recentsViewData.runningTaskIds.value = setOf(taskId)
-            systemUnderTest.bind(taskId)
-
-            assertThat(systemUnderTest.uiState.first()).isEqualTo(LiveTile.WithoutHeader)
-        }
-
-    @Test
-    fun bindRunningTaskShouldShowScreenshot_thenStateIs_SnapshotSplash() =
-        testScope.runTest {
-            val taskId = 1
-            val expectedThumbnailData = createThumbnailData()
-            tasksRepository.seedThumbnailData(mapOf(taskId to expectedThumbnailData))
-            val expectedIconData = mock<Drawable>()
-            tasksRepository.seedIconData(taskId, "Task $taskId", "", expectedIconData)
-            tasksRepository.seedTasks(tasks)
-            tasksRepository.setVisibleTasks(setOf(taskId))
-            recentsViewData.runningTaskIds.value = setOf(taskId)
-            recentsViewData.runningTaskShowScreenshot.value = true
-            systemUnderTest.bind(taskId)
-
-            assertThat(systemUnderTest.uiState.first())
-                .isEqualTo(
-                    SnapshotSplash(
-                        Snapshot.WithoutHeader(
-                            backgroundColor = Color.rgb(1, 1, 1),
-                            bitmap = expectedThumbnailData.thumbnail!!,
-                            thumbnailRotation = Surface.ROTATION_0,
-                        ),
-                        expectedIconData,
-                    )
-                )
-        }
-
-    @Test
-    fun bindRunningTaskThenStoppedTaskWithoutThumbnail_thenStateChangesToBackgroundOnly() =
-        testScope.runTest {
-            val runningTaskId = 1
-            val stoppedTaskId = 2
-            tasksRepository.seedTasks(tasks)
-            tasksRepository.setVisibleTasks(setOf(runningTaskId, stoppedTaskId))
-            recentsViewData.runningTaskIds.value = setOf(runningTaskId)
-            systemUnderTest.bind(runningTaskId)
-            assertThat(systemUnderTest.uiState.first()).isEqualTo(LiveTile.WithoutHeader)
-
-            systemUnderTest.bind(stoppedTaskId)
-            assertThat(systemUnderTest.uiState.first())
-                .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2)))
-        }
-
-    @Test
-    fun bindStoppedTaskWithoutThumbnail_thenStateIs_BackgroundOnly_withAlphaRemoved() =
-        testScope.runTest {
-            val stoppedTaskId = 2
-            tasksRepository.seedTasks(tasks)
-            tasksRepository.setVisibleTasks(setOf(stoppedTaskId))
-
-            systemUnderTest.bind(stoppedTaskId)
-            assertThat(systemUnderTest.uiState.first())
-                .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2)))
-        }
-
-    @Test
-    fun bindLockedTaskWithThumbnail_thenStateIs_BackgroundOnly() =
-        testScope.runTest {
-            val taskId = 2
-            tasksRepository.seedThumbnailData(mapOf(taskId to createThumbnailData()))
-            tasks[taskId].isLocked = true
-            tasksRepository.seedTasks(tasks)
-            tasksRepository.setVisibleTasks(setOf(taskId))
-
-            systemUnderTest.bind(taskId)
-            assertThat(systemUnderTest.uiState.first())
-                .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2)))
-        }
-
-    @Test
-    fun bindStoppedTaskWithThumbnail_thenStateIs_SnapshotSplash_withAlphaRemoved() =
-        testScope.runTest {
-            val taskId = 2
-            val expectedThumbnailData = createThumbnailData(rotation = Surface.ROTATION_270)
-            tasksRepository.seedThumbnailData(mapOf(taskId to expectedThumbnailData))
-            val expectedIconData = mock<Drawable>()
-            tasksRepository.seedIconData(taskId, "Task $taskId", "", expectedIconData)
-            tasksRepository.seedTasks(tasks)
-            tasksRepository.setVisibleTasks(setOf(taskId))
-
-            systemUnderTest.bind(taskId)
-            assertThat(systemUnderTest.uiState.first())
-                .isEqualTo(
-                    SnapshotSplash(
-                        Snapshot.WithoutHeader(
-                            backgroundColor = Color.rgb(2, 2, 2),
-                            bitmap = expectedThumbnailData.thumbnail!!,
-                            thumbnailRotation = Surface.ROTATION_270,
-                        ),
-                        expectedIconData,
-                    )
-                )
-        }
-
-    @Test
-    fun bindNonVisibleStoppedTask_whenMadeVisible_thenStateIsSnapshotSplash() =
-        testScope.runTest {
-            val taskId = 2
-            val expectedThumbnailData = createThumbnailData()
-            tasksRepository.seedThumbnailData(mapOf(taskId to expectedThumbnailData))
-            val expectedIconData = mock<Drawable>()
-            tasksRepository.seedIconData(taskId, "Task $taskId", "", expectedIconData)
-            tasksRepository.seedTasks(tasks)
-
-            systemUnderTest.bind(taskId)
-            assertThat(systemUnderTest.uiState.first()).isEqualTo(Uninitialized)
-
-            tasksRepository.setVisibleTasks(setOf(taskId))
-            assertThat(systemUnderTest.uiState.first())
-                .isEqualTo(
-                    SnapshotSplash(
-                        Snapshot.WithoutHeader(
-                            backgroundColor = Color.rgb(2, 2, 2),
-                            bitmap = expectedThumbnailData.thumbnail!!,
-                            thumbnailRotation = Surface.ROTATION_0,
-                        ),
-                        expectedIconData,
-                    )
-                )
-        }
-
-    @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
-    fun bindRunningTask_inDesktop_thenStateIs_LiveTile_withHeader() =
-        testScope.runTest {
-            deviceProfileRepository.setRecentsDeviceProfile(
-                RecentsDeviceProfile(isLargeScreen = true, canEnterDesktopMode = true)
-            )
-
-            val taskId = freeformTaskIdRange.first
-            val expectedIconData = mock<Drawable>()
-            tasksRepository.seedIconData(taskId, "Task $taskId", "Task $taskId", expectedIconData)
-            tasksRepository.seedTasks(freeformTasks)
-            tasksRepository.setVisibleTasks(setOf(taskId))
-            recentsViewData.runningTaskIds.value = setOf(taskId)
-            systemUnderTest.bind(taskId)
-
-            assertThat(systemUnderTest.uiState.first())
-                .isEqualTo(LiveTile.WithHeader(ThumbnailHeader(expectedIconData, "Task $taskId")))
-        }
-
-    @Test
-    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
-    fun bindStoppedTaskWithThumbnail_inDesktop_thenStateIs_SnapshotSplash_withHeader() =
-        testScope.runTest {
-            deviceProfileRepository.setRecentsDeviceProfile(
-                RecentsDeviceProfile(isLargeScreen = true, canEnterDesktopMode = true)
-            )
-
-            val taskId = freeformTaskIdRange.first
-            val expectedThumbnailData = createThumbnailData(rotation = Surface.ROTATION_0)
-            tasksRepository.seedThumbnailData(mapOf(taskId to expectedThumbnailData))
-            val expectedIconData = mock<Drawable>()
-            tasksRepository.seedIconData(taskId, "Task $taskId", "Task $taskId", expectedIconData)
-            tasksRepository.seedTasks(freeformTasks)
-            tasksRepository.setVisibleTasks(setOf(taskId))
-
-            systemUnderTest.bind(taskId)
-            assertThat(systemUnderTest.uiState.first())
-                .isEqualTo(
-                    SnapshotSplash(
-                        Snapshot.WithHeader(
-                            backgroundColor = Color.rgb(taskId, taskId, taskId),
-                            bitmap = expectedThumbnailData.thumbnail!!,
-                            thumbnailRotation = Surface.ROTATION_0,
-                            header = ThumbnailHeader(expectedIconData, "Task $taskId"),
-                        ),
-                        expectedIconData,
-                    )
-                )
-        }
-
     @Test
     fun getSnapshotMatrix_MissingThumbnail() =
         testScope.runTest {
@@ -337,51 +117,7 @@
             assertThat(systemUnderTest.dimProgress.first()).isEqualTo(0f)
         }
 
-    private fun createTaskWithId(taskId: Int) =
-        Task(
-                Task.TaskKey(
-                    taskId,
-                    WINDOWING_MODE_FULLSCREEN,
-                    Intent(),
-                    ComponentName("", ""),
-                    0,
-                    2000,
-                )
-            )
-            .apply {
-                colorBackground = Color.argb(taskId, taskId, taskId, taskId)
-                titleDescription = "Task $taskId"
-                icon = mock<Drawable>()
-            }
-
-    private fun createFreeformTaskWithId(taskId: Int) =
-        Task(
-                Task.TaskKey(
-                    taskId,
-                    WINDOWING_MODE_FREEFORM,
-                    Intent(),
-                    ComponentName("", ""),
-                    0,
-                    2000,
-                )
-            )
-            .apply {
-                colorBackground = Color.argb(taskId, taskId, taskId, taskId)
-                titleDescription = "Task $taskId"
-                icon = mock<Drawable>()
-            }
-
-    private fun createThumbnailData(rotation: Int = Surface.ROTATION_0): ThumbnailData {
-        val bitmap = mock<Bitmap>()
-        whenever(bitmap.width).thenReturn(THUMBNAIL_WIDTH)
-        whenever(bitmap.height).thenReturn(THUMBNAIL_HEIGHT)
-
-        return ThumbnailData(thumbnail = bitmap, rotation = rotation)
-    }
-
-    companion object {
-        const val THUMBNAIL_WIDTH = 100
-        const val THUMBNAIL_HEIGHT = 200
+    private companion object {
         const val CANVAS_WIDTH = 300
         const val CANVAS_HEIGHT = 600
         val MATRIX =
diff --git a/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
similarity index 97%
rename from quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
index 3148737..5b42d6c 100644
--- a/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
@@ -56,9 +56,9 @@
     private val taskbarSharedState = mock<TaskbarSharedState>()
     private var isInDesktopMode = false
     private val launcherPrefs =
-        mock<LauncherPrefs> {
-            on { get(TASKBAR_PINNING) } doReturn false
-            on { get(TASKBAR_PINNING_IN_DESKTOP_MODE) } doReturn false
+        mock<LauncherPrefs>().apply {
+            doReturn(false).whenever(this).get(TASKBAR_PINNING)
+            doReturn(false).whenever(this).get(TASKBAR_PINNING_IN_DESKTOP_MODE)
         }
     private val statsLogger = mock<StatsLogManager.StatsLogger>()
     private val statsLogManager = mock<StatsLogManager> { on { logger() } doReturn statsLogger }
diff --git a/quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
similarity index 90%
rename from quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
index c190cfe..555e62b 100644
--- a/quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
@@ -18,11 +18,12 @@
 
 import android.graphics.Rect
 import android.graphics.Region
-import android.testing.AndroidTestingRunner
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.IWindowManager
+import androidx.test.annotation.UiThreadTest
 import androidx.test.filters.SmallTest
 import com.android.launcher3.util.Executors
+import com.android.launcher3.util.LauncherMultivalentJUnit
 import com.android.quickstep.util.GestureExclusionManager.ExclusionListener
 import org.junit.Before
 import org.junit.Test
@@ -31,11 +32,12 @@
 import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.reset
 import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
 
 /** Unit test for [GestureExclusionManager]. */
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@UiThreadTest
+@RunWith(LauncherMultivalentJUnit::class)
 class GestureExclusionManagerTest {
 
     @Mock private lateinit var windowManager: IWindowManager
@@ -72,7 +74,7 @@
         underTest.addListener(listener2)
 
         awaitTasksCompleted()
-        verifyZeroInteractions(windowManager)
+        verifyNoMoreInteractions(windowManager)
     }
 
     @Test
@@ -98,7 +100,7 @@
         underTest.removeListener(listener1)
 
         awaitTasksCompleted()
-        verifyZeroInteractions(windowManager)
+        verifyNoMoreInteractions(windowManager)
     }
 
     @Test
@@ -119,12 +121,12 @@
         awaitTasksCompleted()
         underTest.addListener(listener1)
         awaitTasksCompleted()
-        verifyZeroInteractions(listener1)
+        verifyNoMoreInteractions(listener1)
 
         underTest.addListener(listener2)
         awaitTasksCompleted()
 
-        verifyZeroInteractions(listener1)
+        verifyNoMoreInteractions(listener1)
         verify(listener2).onGestureExclusionChanged(r1, r2)
     }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
index 108cfb5..fa043b9 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
@@ -21,7 +21,6 @@
 import android.graphics.Rect
 import com.android.launcher3.util.LauncherMultivalentJUnit
 import com.android.launcher3.util.SplitConfigurationOptions
-import com.android.quickstep.views.TaskViewType
 import com.android.systemui.shared.recents.model.Task
 import com.android.wm.shell.shared.split.SplitScreenConstants
 import com.google.common.truth.Truth.assertThat
@@ -33,28 +32,28 @@
 
     @Test
     fun testGroupTask_sameInstance_isEqual() {
-        val task = GroupTask(createTask(1))
+        val task = SingleTask(createTask(1))
         assertThat(task).isEqualTo(task)
     }
 
     @Test
     fun testGroupTask_identicalConstructor_isEqual() {
-        val task1 = GroupTask(createTask(1))
-        val task2 = GroupTask(createTask(1))
+        val task1 = SingleTask(createTask(1))
+        val task2 = SingleTask(createTask(1))
         assertThat(task1).isEqualTo(task2)
     }
 
     @Test
     fun testGroupTask_copy_isEqual() {
-        val task1 = GroupTask(createTask(1))
+        val task1 = SingleTask(createTask(1))
         val task2 = task1.copy()
         assertThat(task1).isEqualTo(task2)
     }
 
     @Test
     fun testGroupTask_differentId_isNotEqual() {
-        val task1 = GroupTask(createTask(1))
-        val task2 = GroupTask(createTask(2))
+        val task1 = SingleTask(createTask(1))
+        val task2 = SingleTask(createTask(2))
         assertThat(task1).isNotEqualTo(task2)
     }
 
@@ -66,10 +65,10 @@
                 Rect(),
                 1,
                 2,
-                SplitScreenConstants.SNAP_TO_2_50_50
+                SplitScreenConstants.SNAP_TO_2_50_50,
             )
-        val task1 = GroupTask(createTask(1), createTask(2), splitBounds, TaskViewType.GROUPED)
-        val task2 = GroupTask(createTask(1), createTask(2), splitBounds, TaskViewType.GROUPED)
+        val task1 = SplitTask(createTask(1), createTask(2), splitBounds)
+        val task2 = SplitTask(createTask(1), createTask(2), splitBounds)
         assertThat(task1).isEqualTo(task2)
     }
 
@@ -81,7 +80,7 @@
                 Rect(),
                 1,
                 2,
-                SplitScreenConstants.SNAP_TO_2_50_50
+                SplitScreenConstants.SNAP_TO_2_50_50,
             )
         val splitBounds2 =
             SplitConfigurationOptions.SplitBounds(
@@ -89,17 +88,17 @@
                 Rect(),
                 1,
                 2,
-                SplitScreenConstants.SNAP_TO_2_33_66
+                SplitScreenConstants.SNAP_TO_2_33_66,
             )
-        val task1 = GroupTask(createTask(1), createTask(2), splitBounds1, TaskViewType.GROUPED)
-        val task2 = GroupTask(createTask(1), createTask(2), splitBounds2, TaskViewType.GROUPED)
+        val task1 = SplitTask(createTask(1), createTask(2), splitBounds1)
+        val task2 = SplitTask(createTask(1), createTask(2), splitBounds2)
         assertThat(task1).isNotEqualTo(task2)
     }
 
     @Test
     fun testGroupTask_differentType_isNotEqual() {
-        val task1 = GroupTask(createTask(1), null, null, TaskViewType.SINGLE)
-        val task2 = GroupTask(createTask(1), null, null, TaskViewType.DESKTOP)
+        val task1 = SingleTask(createTask(1))
+        val task2 = DesktopTask(listOf(createTask(1)))
         assertThat(task1).isNotEqualTo(task2)
     }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index 708273e..0491c07 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -660,10 +660,16 @@
         intent.component = task2ComponentName
         taskInfo.baseIntent = intent
         task2.key = Task.TaskKey(taskInfo)
-        return GroupTask(
+        return SplitTask(
             task1,
             task2,
-            SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50),
+            SplitConfigurationOptions.SplitBounds(
+                /* leftTopBounds = */ Rect(),
+                /* rightBottomBounds = */ Rect(),
+                /* leftTopTaskId = */ -1,
+                /* rightBottomTaskId = */ -1,
+                /* snapPosition = */ SNAP_TO_2_50_50,
+            ),
         )
     }
 
@@ -692,10 +698,16 @@
         intent.component = task2ComponentName
         taskInfo.baseIntent = intent
         task2.key = Task.TaskKey(taskInfo)
-        return GroupTask(
+        return SplitTask(
             task1,
             task2,
-            SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50),
+            SplitConfigurationOptions.SplitBounds(
+                /* leftTopBounds = */ Rect(),
+                /* rightBottomBounds = */ Rect(),
+                /* leftTopTaskId = */ -1,
+                /* rightBottomTaskId = */ -1,
+                /* snapPosition = */ SNAP_TO_2_50_50,
+            ),
         )
     }
 }
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
index 67a0ee4..3f7c85c 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
@@ -127,11 +127,11 @@
         when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
         waitForIdleSync();
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 true);
     }
@@ -141,11 +141,11 @@
         when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
         waitForIdleSync();
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 false);
     }
@@ -155,11 +155,11 @@
         when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
         waitForIdleSync();
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 true);
     }
@@ -169,11 +169,11 @@
         when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
         waitForIdleSync();
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 false);
     }
@@ -184,11 +184,11 @@
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
         doReturn(true).when(mSpyFolderView).isOpen();
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
         waitForIdleSync();
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 false);
     }
@@ -199,10 +199,10 @@
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
         doReturn(true).when(mSpyFolderView).isOpen();
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
     }
 
     @Test
@@ -210,10 +210,10 @@
         when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_MOVE);
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_MOVE);
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
     }
 
     @Test
@@ -221,11 +221,11 @@
         when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mAppPairIcon, mMotionEvent);
         waitForIdleSync();
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 true);
     }
@@ -235,11 +235,11 @@
         when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mAppPairIcon, mMotionEvent);
         waitForIdleSync();
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 false);
     }
@@ -250,11 +250,11 @@
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
         when(taskbarActivityContext.isIconAlignedWithHotseat()).thenReturn(true);
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
         waitForIdleSync();
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 true);
     }
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index c152ee1..52bd2ea 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -17,28 +17,33 @@
 package com.android.quickstep
 
 import android.content.ComponentName
+import android.content.Context
 import android.content.Intent
+import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.internal.R
 import com.android.launcher3.AbstractFloatingView
 import com.android.launcher3.AbstractFloatingViewHelper
 import com.android.launcher3.Flags.enableRefactorTaskThumbnail
 import com.android.launcher3.logging.StatsLogManager
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent
 import com.android.launcher3.model.data.TaskViewItemInfo
-import com.android.launcher3.uioverrides.QuickstepLauncher
 import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.launcher3.util.TransformingTouchDelegate
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay
 import com.android.quickstep.task.thumbnail.TaskThumbnailView
 import com.android.quickstep.views.LauncherRecentsView
+import com.android.quickstep.views.RecentsViewContainer
 import com.android.quickstep.views.TaskContainer
 import com.android.quickstep.views.TaskThumbnailViewDeprecated
 import com.android.quickstep.views.TaskView
 import com.android.quickstep.views.TaskViewIcon
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.Task.TaskKey
+import com.android.window.flags.Flags
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
 import com.google.common.truth.Truth.assertThat
@@ -58,7 +63,7 @@
 /** Test for [DesktopSystemShortcut] */
 class DesktopSystemShortcutTest {
 
-    private val launcher: QuickstepLauncher = mock()
+    private val launcher: RecentsViewContainer = mock()
     private val statsLogManager: StatsLogManager = mock()
     private val statsLogger: StatsLogManager.StatsLogger = mock()
     private val recentsView: LauncherRecentsView = mock()
@@ -67,6 +72,7 @@
     private val overlayFactory: TaskOverlayFactory = mock()
     private val factory: TaskShortcutFactory =
         DesktopSystemShortcut.createFactory(abstractFloatingViewHelper)
+    private val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
 
     private lateinit var mockitoSession: StaticMockitoSession
 
@@ -79,6 +85,7 @@
                 .startMocking()
         whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
         whenever(overlayFactory.createOverlay(any())).thenReturn(mock<TaskOverlay<*>>())
+        whenever(launcher.asContext()).thenReturn(context)
     }
 
     @After
@@ -97,6 +104,53 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+    fun createDesktopTaskShortcutFactory_transparentTask() {
+        val baseComponent = ComponentName("", /* class */ "")
+        val taskKey =
+            TaskKey(
+                /* id */ 1,
+                /* windowingMode */ 0,
+                Intent(),
+                baseComponent,
+                /* userId */ 0,
+                /* lastActiveTime */ 2000,
+                DEFAULT_DISPLAY,
+                baseComponent,
+                /* numActivities */ 1,
+                /* isTopActivityNoDisplay */ false,
+                /* isActivityStackTransparent */ true,
+            )
+        val taskContainer = createTaskContainer(Task(taskKey))
+        val shortcuts = factory.getShortcuts(launcher, taskContainer)
+        assertThat(shortcuts).isNull()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+    fun createDesktopTaskShortcutFactory_systemUiTask() {
+        val sysUiPackageName: String = context.resources.getString(R.string.config_systemUi)
+        val baseComponent = ComponentName(sysUiPackageName, /* class */ "")
+        val taskKey =
+            TaskKey(
+                /* id */ 1,
+                /* windowingMode */ 0,
+                Intent(),
+                baseComponent,
+                /* userId */ 0,
+                /* lastActiveTime */ 2000,
+                DEFAULT_DISPLAY,
+                baseComponent,
+                /* numActivities */ 1,
+                /* isTopActivityNoDisplay */ false,
+                /* isActivityStackTransparent */ false,
+            )
+        val taskContainer = createTaskContainer(Task(taskKey))
+        val shortcuts = factory.getShortcuts(launcher, taskContainer)
+        assertThat(shortcuts).isNull()
+    }
+
+    @Test
     fun createDesktopTaskShortcutFactory_undockable() {
         val unDockableTask = createTask().apply { isDockable = false }
         val taskContainer = createTaskContainer(unDockableTask)
@@ -114,8 +168,7 @@
         whenever(launcher.statsLogManager).thenReturn(statsLogManager)
         whenever(statsLogManager.logger()).thenReturn(statsLogger)
         whenever(statsLogger.withItemInfo(any())).thenReturn(statsLogger)
-        whenever(taskView.context)
-            .thenReturn(InstrumentationRegistry.getInstrumentation().targetContext)
+        whenever(taskView.context).thenReturn(context)
         whenever(recentsView.moveTaskToDesktop(any(), any(), any())).thenAnswer {
             val successCallback = it.getArgument<Runnable>(2)
             successCallback.run()
@@ -145,7 +198,22 @@
     }
 
     private fun createTask() =
-        Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply { isDockable = true }
+        Task(
+                TaskKey(
+                    /* id */ 1,
+                    /* windowingMode */ 0,
+                    Intent(),
+                    ComponentName("", ""),
+                    /* userId */ 0,
+                    /* lastActiveTime */ 2000,
+                    DEFAULT_DISPLAY,
+                    ComponentName("", ""),
+                    /* numActivities */ 1,
+                    /* isTopActivityNoDisplay */ false,
+                    /* isActivityStackTransparent */ false,
+                )
+            )
+            .apply { isDockable = true }
 
     private fun createTaskContainer(task: Task) =
         TaskContainer(
diff --git a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
index 9c2c13c..4111dec 100644
--- a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
@@ -17,23 +17,27 @@
 package com.android.quickstep
 
 import android.content.ComponentName
+import android.content.Context
 import android.content.Intent
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.platform.app.InstrumentationRegistry
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.internal.R
 import com.android.launcher3.AbstractFloatingView
 import com.android.launcher3.AbstractFloatingViewHelper
 import com.android.launcher3.Flags.enableRefactorTaskThumbnail
 import com.android.launcher3.logging.StatsLogManager
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent
 import com.android.launcher3.model.data.TaskViewItemInfo
-import com.android.launcher3.uioverrides.QuickstepLauncher
 import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.launcher3.util.TransformingTouchDelegate
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay
 import com.android.quickstep.task.thumbnail.TaskThumbnailView
 import com.android.quickstep.views.LauncherRecentsView
+import com.android.quickstep.views.RecentsViewContainer
 import com.android.quickstep.views.TaskContainer
 import com.android.quickstep.views.TaskThumbnailViewDeprecated
 import com.android.quickstep.views.TaskView
@@ -62,7 +66,7 @@
 
     @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
 
-    private val launcher: QuickstepLauncher = mock()
+    private val launcher: RecentsViewContainer = mock()
     private val statsLogManager: StatsLogManager = mock()
     private val statsLogger: StatsLogManager.StatsLogger = mock()
     private val recentsView: LauncherRecentsView = mock()
@@ -71,6 +75,7 @@
     private val overlayFactory: TaskOverlayFactory = mock()
     private val factory: TaskShortcutFactory =
         ExternalDisplaySystemShortcut.createFactory(abstractFloatingViewHelper)
+    private val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
 
     private lateinit var mockitoSession: StaticMockitoSession
 
@@ -83,6 +88,7 @@
                 .startMocking()
         whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
         whenever(overlayFactory.createOverlay(any())).thenReturn(mock<TaskOverlay<*>>())
+        whenever(launcher.asContext()).thenReturn(context)
     }
 
     @After
@@ -102,6 +108,59 @@
     }
 
     @Test
+    @EnableFlags(
+        Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT,
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+    )
+    fun createExternalDisplayTaskShortcut_transparentTask() {
+        val baseComponent = ComponentName("", /* class */ "")
+        val taskKey =
+            TaskKey(
+                /* id */ 1,
+                /* windowingMode */ 0,
+                Intent(),
+                baseComponent,
+                /* userId */ 0,
+                /* lastActiveTime */ 2000,
+                DEFAULT_DISPLAY,
+                baseComponent,
+                /* numActivities */ 1,
+                /* isTopActivityNoDisplay */ false,
+                /* isActivityStackTransparent */ true,
+            )
+        val taskContainer = createTaskContainer(Task(taskKey))
+        val shortcuts = factory.getShortcuts(launcher, taskContainer)
+        assertThat(shortcuts).isNull()
+    }
+
+    @Test
+    @EnableFlags(
+        Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT,
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+    )
+    fun createExternalDisplayTaskShortcut_systemUiTask() {
+        val sysUiPackageName: String = context.resources.getString(R.string.config_systemUi)
+        val baseComponent = ComponentName(sysUiPackageName, /* class */ "")
+        val taskKey =
+            TaskKey(
+                /* id */ 1,
+                /* windowingMode */ 0,
+                Intent(),
+                baseComponent,
+                /* userId */ 0,
+                /* lastActiveTime */ 2000,
+                DEFAULT_DISPLAY,
+                baseComponent,
+                /* numActivities */ 1,
+                /* isTopActivityNoDisplay */ false,
+                /* isActivityStackTransparent */ false,
+            )
+        val taskContainer = createTaskContainer(Task(taskKey))
+        val shortcuts = factory.getShortcuts(launcher, taskContainer)
+        assertThat(shortcuts).isNull()
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT)
     fun externalDisplaySystemShortcutClicked() {
         val task = createTask()
@@ -134,7 +193,22 @@
         verify(statsLogger).log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_EXTERNAL_DISPLAY_TAP)
     }
 
-    private fun createTask() = Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000))
+    private fun createTask() =
+        Task(
+            TaskKey(
+                /* id */ 1,
+                /* windowingMode */ 0,
+                Intent(),
+                ComponentName("", ""),
+                /* userId */ 0,
+                /* lastActiveTime */ 2000,
+                DEFAULT_DISPLAY,
+                ComponentName("", ""),
+                /* numActivities */ 1,
+                /* isTopActivityNoDisplay */ false,
+                /* isActivityStackTransparent */ false,
+            )
+        )
 
     private fun createTaskContainer(task: Task) =
         TaskContainer(
diff --git a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
index 3c5e1e8..e2ca91a 100644
--- a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
@@ -16,8 +16,6 @@
 
 package com.android.quickstep;
 
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
 import static com.android.quickstep.InputConsumerUtils.newBaseConsumer;
 import static com.android.quickstep.InputConsumerUtils.newConsumer;
 
@@ -40,6 +38,9 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppModule;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.taskbar.TaskbarManager;
 import com.android.launcher3.taskbar.bubbles.BubbleBarController;
@@ -54,7 +55,7 @@
 import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController;
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
 import com.android.launcher3.util.LockedUserState;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SandboxApplication;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
 import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
@@ -75,6 +76,9 @@
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
+import dagger.BindsInstance;
+import dagger.Component;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -93,8 +97,8 @@
 @RunWith(AndroidJUnit4.class)
 public class InputConsumerUtilsTest {
 
-    @NonNull private final MainThreadInitializedObject.SandboxContext mContext =
-            new MainThreadInitializedObject.SandboxContext(getApplicationContext());
+    @Rule public final SandboxApplication mContext = new SandboxApplication();
+
     @NonNull private final InputMonitorCompat mInputMonitorCompat = new InputMonitorCompat("", 0);
 
     private TaskAnimationManager mTaskAnimationManager;
@@ -125,10 +129,12 @@
     }
 
     @Before
-    public void setupMainThreadInitializedObjects() {
-        mContext.putObject(LockedUserState.INSTANCE, mLockedUserState);
-        mContext.putObject(RotationTouchHelper.INSTANCE, mock(RotationTouchHelper.class));
-        mContext.putObject(RecentsAnimationDeviceState.INSTANCE, mDeviceState);
+    public void setupDaggerGraphOverrides() {
+        mContext.initDaggerComponent(DaggerInputConsumerUtilsTest_TestComponent
+                .builder()
+                .bindLockedState(mLockedUserState)
+                .bindRotationHelper(mock(RotationTouchHelper.class))
+                .bindRecentsState(mDeviceState));
     }
 
     @Before
@@ -595,4 +601,18 @@
 
         return bubbleControllers;
     }
+
+    @LauncherAppSingleton
+    @Component(modules = {LauncherAppModule.class})
+    interface TestComponent extends LauncherAppComponent {
+        @Component.Builder
+        interface Builder extends LauncherAppComponent.Builder {
+            @BindsInstance Builder bindLockedState(LockedUserState state);
+            @BindsInstance Builder bindRotationHelper(RotationTouchHelper helper);
+            @BindsInstance Builder bindRecentsState(RecentsAnimationDeviceState state);
+
+            @Override
+            TestComponent build();
+        }
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsLockedTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsLockedTaskbar.java
new file mode 100644
index 0000000..8fedf5c
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsLockedTaskbar.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+
+import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT;
+import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP;
+
+import android.app.WindowConfiguration;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.WindowManagerGlobal;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
+import com.android.launcher3.util.rule.SetPropRule;
+import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
+import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
+import com.android.window.flags.Flags;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TaplTestsLockedTaskbar extends AbstractTaplTestsTaskbar {
+    private static final String TAG = "TaplTestsLockedTaskbar";
+
+    @Rule
+    public SetPropRule mSetPropRule =
+            new SetPropRule(ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP, "true");
+
+    @Override
+    public void setUp() throws Exception {
+        Assume.assumeTrue(mLauncher.isTablet());
+        Assume.assumeTrue(Flags.enterDesktopByDefaultOnFreeformDisplays());
+        Assume.assumeTrue(DesktopModeStatus.canEnterDesktopMode(getTargetContext()));
+        super.setUp();
+
+        // Default-to-desktop feature requires the display to be freeform mode.
+        setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        // Reset the display windowing mode to the device default.
+        setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_UNDEFINED);
+
+        mLauncher.recreateTaskbar();
+
+        super.tearDown();
+    }
+
+    @Test
+    @PortraitLandscape
+    @NavigationModeSwitch
+    @TaskbarModeSwitch(mode = PERSISTENT)
+    public void testTaskbarVisibility() {
+        // The taskbar should be visible on home.
+        mDevice.pressHome();
+        waitForResumed("Launcher internal state is still Background");
+        mLauncher.getLaunchedAppState().assertTaskbarVisible();
+
+        // The taskbar should be visible when a freeform task is active.
+        startAppFast(CALCULATOR_APP_PACKAGE);
+        mLauncher.getLaunchedAppState().assertTaskbarVisible();
+    }
+
+    private void setDisplayWindowingMode(int windowingMode) {
+        try {
+            WindowManagerGlobal.getWindowManagerService().setWindowingMode(
+                    DEFAULT_DISPLAY, windowingMode);
+        } catch (RemoteException e) {
+            Log.e(TAG, "error setting windowing mode", e);
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt b/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt
index b744039..75947ab 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt
@@ -122,25 +122,25 @@
         val desktop = moveTaskToDesktop(TEST_ACTIVITY_EXTRA)
         var overview = desktop.switchToOverview()
 
-        // Open focused task and go back to Overview to validate whether it has adjacent tasks in
-        // its both sides (grid task on left and desktop tasks at its right side)
-        val focusedTaskOpened = overview.getTestActivityTask(TEST_ACTIVITY_2).open()
+        // Open first fullscreen task and go back to Overview to validate whether it has adjacent
+        // tasks in its both sides (grid task on left and desktop tasks at its right side)
+        val firstFullscreenTaskOpened = overview.getTestActivityTask(TEST_ACTIVITY_2).open()
 
-        // Fling to desktop task and dismiss the focused task to check repositioning of
+        // Fling to desktop task and dismiss the first fullscreen task to check repositioning of
         // grid tasks.
-        overview = focusedTaskOpened.switchToOverview().apply { flingBackward() }
+        overview = firstFullscreenTaskOpened.switchToOverview().apply { flingBackward() }
         val desktopTask = overview.currentTask
         assertWithMessage("The current task is not a Desktop.").that(desktopTask.isDesktop).isTrue()
 
-        // Get focused task (previously opened task) then dismiss this task
-        val focusedTaskInOverview = overview.getTestActivityTask(TEST_ACTIVITY_2)
-        assertTaskContentDescription(focusedTaskInOverview, TEST_ACTIVITY_2)
-        focusedTaskInOverview.dismiss()
+        // Get first fullscreen task (previously opened task) then dismiss this task
+        val firstFullscreenTaskInOverview = overview.getTestActivityTask(TEST_ACTIVITY_2)
+        assertTaskContentDescription(firstFullscreenTaskInOverview, TEST_ACTIVITY_2)
+        firstFullscreenTaskInOverview.dismiss()
 
-        // Dismiss DesktopTask to validate whether the new focused task will take its position
+        // Dismiss DesktopTask to validate whether the new task will take its position
         desktopTask.dismiss()
 
-        // Dismiss last focused task
+        // Dismiss last fullscreen task
         val lastFocusedTask = overview.currentTask
         assertTaskContentDescription(lastFocusedTask, TEST_ACTIVITY_1)
         lastFocusedTask.dismiss()
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index afe6368..37ac4a0 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
@@ -30,7 +30,6 @@
 import com.android.launcher3.tapl.Taskbar;
 import com.android.launcher3.tapl.TaskbarAppIcon;
 import com.android.quickstep.util.SplitScreenTestUtils;
-import com.android.wm.shell.Flags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -95,9 +94,6 @@
 
     @Test
     public void testSaveAppPairMenuItemOrActionExistsOnSplitPair() {
-        assumeTrue("App pairs feature is currently not enabled, no test needed",
-                Flags.enableAppPairs());
-
         Overview overview = SplitScreenTestUtils.createAndLaunchASplitPairInOverview(mLauncher);
 
         if (mLauncher.isGridOnlyOverviewEnabled() || !mLauncher.isTablet()) {
@@ -110,9 +106,6 @@
 
     @Test
     public void testSaveAppPairMenuItemDoesNotExistOnSingleTask() throws Exception {
-        assumeTrue("App pairs feature is currently not enabled, no test needed",
-                Flags.enableAppPairs());
-
         startAppFast(CALCULATOR_APP_PACKAGE);
 
         assertFalse("Save app pair menu item is erroneously appearing on single task",
diff --git a/res/drawable/ic_desktop_add.xml b/res/drawable/ic_desktop_add.xml
index fa5e0de..d31b04b 100644
--- a/res/drawable/ic_desktop_add.xml
+++ b/res/drawable/ic_desktop_add.xml
@@ -14,8 +14,8 @@
   ~ limitations under the License.
   -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="48dp"
-    android:height="48dp"
+    android:width="24dp"
+    android:height="24dp"
     android:viewportWidth="960"
     android:viewportHeight="960">
     <path
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index b62bbfa..c45d372 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -189,13 +189,13 @@
     <string name="all_apps_work_tab" msgid="4884822796154055118">"کاری"</string>
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"نمایه کاری"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"برنامه‌های کاری نشان‌دار هستند و سرپرست فناوری اطلاعات می‌تواند آن‌ها را ببیند"</string>
-    <string name="work_profile_edu_accept" msgid="6069788082535149071">"متوجه‌ام"</string>
+    <string name="work_profile_edu_accept" msgid="6069788082535149071">"متوجهم"</string>
     <string name="work_apps_paused_title" msgid="3040901117349444598">"برنامه‌های کاری موقتاً متوقف شده‌اند."</string>
     <string name="work_apps_paused_info_body" msgid="1687828929959237477">"از برنامه‌های کاری‌تان اعلان دریافت نخواهید کرد"</string>
     <string name="work_apps_paused_body" msgid="261634750995824906">"برنامه‌های کاری نمی‌توانند برای شما اعلان ارسال کنند، از باتری استفاده کنند، یا به مکانتان دسترسی داشته باشند"</string>
     <string name="work_apps_paused_telephony_unavailable_body" msgid="8358872357502756790">"از برنامه‌های کاری‌تان تماس تلفنی، پیام نوشتاری، یا اعلان دریافت نخواهید کرد"</string>
     <string name="work_apps_paused_edu_banner" msgid="8872412121608402058">"برنامه‌های کاری نشان‌دار هستند و سرپرست فناوری اطلاعات می‌تواند آن‌ها را ببیند."</string>
-    <string name="work_apps_paused_edu_accept" msgid="6377476824357318532">"متوجه‌ام"</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_scheduler_button_content_description" msgid="917340740986764967">"برنامه زمانی برنامه‌های کاری"</string>
diff --git a/shared/src/com/android/launcher3/testing/shared/TestProtocol.java b/shared/src/com/android/launcher3/testing/shared/TestProtocol.java
index 5fcbbf1..0583d6d 100644
--- a/shared/src/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/shared/src/com/android/launcher3/testing/shared/TestProtocol.java
@@ -169,7 +169,6 @@
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
     public static final String ICON_MISSING = "b/282963545";
     public static final String REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW = "enable-grid-only-overview";
-    public static final String REQUEST_FLAG_ENABLE_APP_PAIRS = "enable-app-pairs";
     public static final String REQUEST_IS_RECENTS_WINDOW_ENABLED = "recents-window-enabled";
 
     public static final String REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED =
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 76c0f90..753b2e2 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -75,7 +75,8 @@
             TYPE_ADD_TO_HOME_CONFIRMATION,
             TYPE_TASKBAR_OVERLAY_PROXY,
             TYPE_TASKBAR_PINNING_POPUP,
-            TYPE_PIN_IME_POPUP
+            TYPE_PIN_IME_POPUP,
+            TYPE_ONE_GRID_MIGRATION_EDU,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface FloatingViewType {}
@@ -104,6 +105,7 @@
     public static final int TYPE_TASKBAR_OVERLAY_PROXY = 1 << 20;
     public static final int TYPE_TASKBAR_PINNING_POPUP = 1 << 21;
     public static final int TYPE_PIN_IME_POPUP = 1 << 22;
+    public static final int TYPE_ONE_GRID_MIGRATION_EDU = 1 << 23;
 
     public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
             | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
@@ -112,19 +114,19 @@
             | TYPE_ICON_SURFACE | TYPE_DRAG_DROP_POPUP | TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP
             | TYPE_TASKBAR_EDUCATION_DIALOG | TYPE_TASKBAR_ALL_APPS | TYPE_OPTIONS_POPUP_DIALOG
             | TYPE_ADD_TO_HOME_CONFIRMATION | TYPE_TASKBAR_OVERLAY_PROXY
-            | TYPE_TASKBAR_PINNING_POPUP | TYPE_PIN_IME_POPUP;
+            | TYPE_TASKBAR_PINNING_POPUP | TYPE_PIN_IME_POPUP | TYPE_ONE_GRID_MIGRATION_EDU;
 
     // Type of popups which should be kept open during launcher rebind
     public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
             | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
             | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE | TYPE_TASKBAR_EDUCATION_DIALOG
             | TYPE_TASKBAR_ALL_APPS | TYPE_OPTIONS_POPUP_DIALOG | TYPE_TASKBAR_OVERLAY_PROXY
-            | TYPE_PIN_IME_POPUP;
+            | TYPE_PIN_IME_POPUP | TYPE_ONE_GRID_MIGRATION_EDU;
 
     /** Type of popups that should get exclusive accessibility focus. */
     public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER
             & ~TYPE_ALL_APPS_EDU & ~TYPE_TASKBAR_ALL_APPS & ~TYPE_PIN_IME_POPUP
-            & ~TYPE_WIDGET_RESIZE_FRAME;
+            & ~TYPE_WIDGET_RESIZE_FRAME & ~TYPE_ONE_GRID_MIGRATION_EDU & ~TYPE_ON_BOARD_POPUP;
 
     // These view all have particular operation associated with swipe down interaction.
     public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET |
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 175d6ec..468cee8 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -261,6 +261,13 @@
         return count;
     }
 
+    private void addProfileId(XmlPullParser parser) {
+        Long profileId = mUserTypeToSerial.get(getAttributeValue(parser, ATTR_USER_TYPE));
+        if (profileId != null) {
+            mValues.put(Favorites.PROFILE_ID, profileId);
+        }
+    }
+
     /**
      * Parses container and screenId attribute from the current tag, and puts it in the out.
      * @param out array of size 2.
@@ -305,10 +312,6 @@
                 convertToDistanceFromEnd(getAttributeValue(parser, ATTR_X), mColumnCount));
         mValues.put(Favorites.CELLY,
                 convertToDistanceFromEnd(getAttributeValue(parser, ATTR_Y), mRowCount));
-        Long profileId = mUserTypeToSerial.get(getAttributeValue(parser, ATTR_USER_TYPE));
-        if (profileId != null) {
-            mValues.put(Favorites.PROFILE_ID, profileId);
-        }
 
         TagParser tagParser = tagParserMap.get(parser.getName());
         if (tagParser == null) {
@@ -382,7 +385,7 @@
         public int parseAndAdd(XmlPullParser parser) {
             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
             final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
-
+            addProfileId(parser);
             if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
                 ActivityInfo info;
                 try {
@@ -431,6 +434,7 @@
         public int parseAndAdd(XmlPullParser parser) {
             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
             final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
+            addProfileId(parser);
             if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
                 if (LOGD) Log.d(TAG, "Skipping invalid <favorite> with no component");
                 return -1;
@@ -452,7 +456,7 @@
         public int parseAndAdd(XmlPullParser parser) {
             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
             final String shortcutId = getAttributeValue(parser, ATTR_SHORTCUT_ID);
-
+            addProfileId(parser);
             try {
                 LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
                 launcherApps.pinShortcuts(packageName, Collections.singletonList(shortcutId),
@@ -482,13 +486,13 @@
         public ComponentName getComponentName(XmlPullParser parser) {
             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
             final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
+            addProfileId(parser);
             if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
                 return null;
             }
             return new ComponentName(packageName, className);
         }
 
-
         @Override
         public int parseAndAdd(XmlPullParser parser)
                 throws XmlPullParserException, IOException {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 9aa06bf..315096c 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -75,6 +75,7 @@
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.graphics.ThemeManager;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
@@ -448,7 +449,9 @@
     @VisibleForTesting
     @UiThread
     public void applyIconAndLabel(ItemInfoWithIcon info) {
-        int flags = shouldUseTheme() ? FLAG_THEMED : 0;
+        ThemeManager themeManager = ThemeManager.INSTANCE.get(getContext());
+        int flags = (shouldUseTheme()
+                && themeManager.isMonoThemeEnabled()) ? FLAG_THEMED : 0;
         // Remove badge on icons smaller than 48dp.
         if (mHideBadge || mDisplay == DISPLAY_SEARCH_RESULT_SMALL) {
             flags |= FLAG_NO_BADGE;
@@ -1236,6 +1239,7 @@
         mIcon = icon;
         if (mIcon != null) {
             mIcon.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
+            mIcon.setHoverScaleEnabledForDisplay(mDisplay != DISPLAY_TASKBAR);
         }
     }
 
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 3d71ff1..988d164 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.dragndrop.DraggableView.DRAGGABLE_ICON;
 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_REORDER_PREVIEW_OFFSET;
@@ -47,6 +48,7 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
@@ -69,6 +71,7 @@
 import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
+import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.util.CellAndSpan;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.MSDLPlayerWrapper;
@@ -529,9 +532,11 @@
 
         for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
             DelegatedCellDrawing cellDrawing = mDelegatedCellDrawings.get(i);
-            cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
             canvas.save();
-            canvas.translate(mTempLocation[0], mTempLocation[1]);
+            if (cellDrawing.mDelegateCellX >= 0 && cellDrawing.mDelegateCellY >= 0) {
+                cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
+                canvas.translate(mTempLocation[0], mTempLocation[1]);
+            }
             cellDrawing.drawUnderItem(canvas);
             canvas.restore();
         }
@@ -660,9 +665,11 @@
 
         for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
             DelegatedCellDrawing bg = mDelegatedCellDrawings.get(i);
-            cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
             canvas.save();
-            canvas.translate(mTempLocation[0], mTempLocation[1]);
+            if (bg.mDelegateCellX >= 0 && bg.mDelegateCellY >= 0) {
+                cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
+                canvas.translate(mTempLocation[0], mTempLocation[1]);
+            }
             bg.drawOverItem(canvas);
             canvas.restore();
         }
@@ -781,6 +788,22 @@
             }
             mShortcutsAndWidgets.addView(child, index, lp);
 
+            // Whenever an app is added, if Accessibility service is enabled, focus on that app.
+            if (mActivity instanceof Launcher) {
+                Launcher.cast(mActivity).getStateManager().addStateListener(
+                        new StateManager.StateListener<LauncherState>() {
+                            @Override
+                            public void onStateTransitionComplete(LauncherState finalState) {
+                                if (finalState == NORMAL) {
+                                    child.performAccessibilityAction(
+                                            AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+                                    Launcher.cast(mActivity).getStateManager()
+                                            .removeStateListener(this);
+                                }
+                            }
+                        });
+            }
+
             if (markCells) markCellsAsOccupiedForView(child);
 
             return true;
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 9ebae9f..9f47da7 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -26,7 +26,6 @@
 import static com.android.launcher3.Utilities.dpiFromPx;
 import static com.android.launcher3.Utilities.pxFromSp;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
-import static com.android.launcher3.icons.GraphicsUtils.getShapePath;
 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
 import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
 import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp;
@@ -53,6 +52,7 @@
 
 import com.android.launcher3.CellLayout.ContainerType;
 import com.android.launcher3.DevicePaddings.DevicePadding;
+import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.model.data.ItemInfo;
@@ -792,8 +792,7 @@
         int leftRightSplitPortraitResId = Resources.getSystem().getIdentifier(
                 "config_leftRightSplitInPortrait", "bool", "android");
         boolean allowLeftRightSplitInPortrait =
-                com.android.wm.shell.Flags.enableLeftRightSplitInPortrait()
-                    && leftRightSplitPortraitResId > 0
+                    leftRightSplitPortraitResId > 0
                     && res.getBoolean(leftRightSplitPortraitResId);
         if (allowLeftRightSplitInPortrait && isTablet) {
             isLeftRightSplit = !isLandscape;
@@ -872,7 +871,9 @@
             @NonNull Context context, int size, @NonNull SparseArray<DotRenderer> cache) {
         DotRenderer renderer = cache.get(size);
         if (renderer == null) {
-            renderer = new DotRenderer(size, getShapePath(context, DEFAULT_DOT_SIZE),
+            renderer = new DotRenderer(
+                    size,
+                    IconShape.INSTANCE.get(context).getShapeOverridePath(DEFAULT_DOT_SIZE),
                     DEFAULT_DOT_SIZE);
             cache.put(size, renderer);
         }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 7563493..d93c07f 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -659,7 +659,8 @@
                 // Only fetch badge if the icon is on workspace
                 if (info.id != ItemInfo.NO_ID && badge == null) {
                     badge = appState.getIconCache().getShortcutInfoBadge(si)
-                            .newIcon(context, FLAG_THEMED);
+                            .newIcon(context, ThemeManager.INSTANCE.get(context)
+                                    .isMonoThemeEnabled() ? FLAG_THEMED : 0);
                 }
             }
         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 0d050b2..86c49d0 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -3572,7 +3572,7 @@
         int nScreens = getChildCount();
         int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
         if (extraScreenId >= 0 && nScreens > 1) {
-            if (page == extraScreenId) {
+            if (page == extraScreenId || (isTwoPanelEnabled() && page == extraScreenId + 1)) {
                 return getContext().getString(R.string.workspace_new_page);
             }
             nScreens--;
@@ -3584,6 +3584,11 @@
         int panelCount = getPanelCount();
         int currentPage = (page / panelCount) + 1;
         int totalPages = nScreens / panelCount + nScreens % panelCount;
+
+        // When dragging, a blank screen is added. This increases the total page count, but we still
+        // want to describe the original page count where icons are currently pinned
+        if (extraScreenId > 0) totalPages--;
+
         return getContext().getString(R.string.workspace_scroll_format, currentPage, totalPages);
     }
 
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 1fe42f7..44dcc06 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -144,15 +144,6 @@
         return ENABLE_TASKBAR_PINNING.get() || Flags.enableTaskbarPinning();
     }
 
-    // Aconfig migration complete for ENABLE_APP_PAIRS.
-    public static final BooleanFlag ENABLE_APP_PAIRS = getDebugFlag(274189428,
-            "ENABLE_APP_PAIRS", DISABLED,
-            "Enables the ability to create and save app pairs on the Home screen for easy"
-                    + " split screen launching.");
-    public static boolean enableAppPairs() {
-        return ENABLE_APP_PAIRS.get() || com.android.wm.shell.Flags.enableAppPairs();
-    }
-
     // TODO(Block 20): Clean up flags
     // Aconfig migration complete for ENABLE_HOME_TRANSITION_LISTENER.
     public static final BooleanFlag ENABLE_HOME_TRANSITION_LISTENER = getDebugFlag(306053414,
diff --git a/src/com/android/launcher3/dagger/LauncherAppModule.java b/src/com/android/launcher3/dagger/LauncherAppModule.java
index ef136d0..c58a414 100644
--- a/src/com/android/launcher3/dagger/LauncherAppModule.java
+++ b/src/com/android/launcher3/dagger/LauncherAppModule.java
@@ -21,7 +21,9 @@
 @Module(includes = {
         WindowManagerProxyModule.class,
         ApiWrapperModule.class,
-        PluginManagerWrapperModule.class
+        PluginManagerWrapperModule.class,
+        StaticObjectModule.class,
+        AppModule.class
 })
 public class LauncherAppModule {
 }
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 5883a88..0b7b20f 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -27,12 +27,14 @@
 import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DynamicResource;
+import com.android.launcher3.util.LockedUserState;
 import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PluginManagerWrapper;
 import com.android.launcher3.util.ScreenOnTracker;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.VibratorWrapper;
+import com.android.launcher3.util.WallpaperColorHints;
 import com.android.launcher3.util.window.RefreshRateTracker;
 import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
@@ -66,6 +68,8 @@
     LauncherPrefs getLauncherPrefs();
     ThemeManager getThemeManager();
     DisplayController getDisplayController();
+    WallpaperColorHints getWallpaperColorHints();
+    LockedUserState getLockedUserState();
 
     /** Builder for LauncherBaseAppComponent. */
     interface Builder {
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index f1b461b..9c82748 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -60,6 +60,7 @@
 import com.android.app.animation.Interpolators;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.ItemInfo;
@@ -272,7 +273,7 @@
                 Rect shrunkBounds = new Rect(bounds);
                 Utilities.scaleRectAboutCenter(shrunkBounds, 0.98f);
                 adaptiveIcon.setBounds(shrunkBounds);
-                final Path mask = adaptiveIcon.getIconMask();
+                final Path mask = IconShape.INSTANCE.get(getContext()).getShapeOverridePath(w);
 
                 mTranslateX = new SpringFloatValue(DragView.this,
                         w * AdaptiveIconDrawable.getExtraInsetFraction());
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index be5f8f7..8a1f96d 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.folder;
 
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
+
 import android.annotation.SuppressLint;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
@@ -37,6 +39,7 @@
 import com.android.launcher3.model.StringCache;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.CollectionInfo;
+import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.Preconditions;
@@ -197,9 +200,18 @@
         @Override
         public void execute(@NonNull ModelTaskController taskController,
                 @NonNull BgDataModel dataModel, @NonNull AllAppsList apps) {
-            mCollectionInfos = dataModel.collections.clone();
+            mCollectionInfos = getCollectionForSuggestions(dataModel);
             mAppInfos = Arrays.asList(apps.copyData());
         }
     }
 
+    public static IntSparseArrayMap<CollectionInfo> getCollectionForSuggestions(
+            BgDataModel dataModel) {
+        IntSparseArrayMap<CollectionInfo> result = new IntSparseArrayMap<>();
+        dataModel.itemsIdMap.stream()
+                .filter(item -> item.itemType == ITEM_TYPE_FOLDER)
+                .forEach(item -> result.put(item.id, (FolderInfo) item));
+        return result;
+    }
+
 }
diff --git a/src/com/android/launcher3/graphics/IconShape.kt b/src/com/android/launcher3/graphics/IconShape.kt
index 2d2ee30..2c4d8e4 100644
--- a/src/com/android/launcher3/graphics/IconShape.kt
+++ b/src/com/android/launcher3/graphics/IconShape.kt
@@ -34,6 +34,7 @@
 import android.view.View
 import android.view.ViewOutlineProvider
 import androidx.annotation.VisibleForTesting
+import androidx.core.graphics.PathParser
 import androidx.graphics.shapes.CornerRounding
 import androidx.graphics.shapes.Morph
 import androidx.graphics.shapes.RoundedPolygon
@@ -65,7 +66,7 @@
 constructor(
     @ApplicationContext private val context: Context,
     private val prefs: LauncherPrefs,
-    themeManager: ThemeManager,
+    private val themeManager: ThemeManager,
     lifeCycle: DaggerSingletonTracker,
 ) {
 
@@ -89,6 +90,17 @@
         lifeCycle.addCloseable { themeManager.removeChangeListener(changeListener) }
     }
 
+    fun getShapeOverridePath(pathSize: Float = DEFAULT_PATH_SIZE): Path {
+        val path = PathParser.createPathFromPathData(themeManager.iconState.iconMask)
+        if (pathSize != DEFAULT_PATH_SIZE) {
+            val matrix = Matrix()
+            val scale: Float = pathSize / DEFAULT_PATH_SIZE
+            matrix.setScale(scale, scale)
+            path.transform(matrix)
+        }
+        return path
+    }
+
     /** Initializes the shape which is closest to the [AdaptiveIconDrawable] */
     private fun pickBestShape(themeManager: ThemeManager): ShapeDelegate {
         val drawable =
@@ -281,6 +293,7 @@
 
         const val TAG = "IconShape"
         const val KEY_ICON_SHAPE = "icon_shape"
+        const val DEFAULT_PATH_SIZE = 100f
         const val AREA_CALC_SIZE = 1000
         // .1% error margin
         const val AREA_DIFF_THRESHOLD = AREA_CALC_SIZE * AREA_CALC_SIZE / 1000
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index f0e4fc4..a0b73ae 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -24,10 +24,10 @@
 import static com.android.launcher3.BubbleTextView.DISPLAY_WORKSPACE;
 import static com.android.launcher3.DeviceProfile.DEFAULT_SCALE;
 import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_PREVIEW_RENDERER;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
-import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
-import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
+import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
 
 import android.app.Fragment;
 import android.app.WallpaperColors;
@@ -105,7 +105,9 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 /**
  * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
@@ -456,54 +458,48 @@
 
     private void populate(BgDataModel dataModel,
             Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
-        // Separate the items that are on the current screen, and the other remaining items.
-        ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
-        ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
-        ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
-        ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
+        IntSet missingHotseatRank = new IntSet();
+        IntStream.range(0, mDp.numShownHotseatIcons).forEach(missingHotseatRank::add);
 
-        IntSet currentScreenIds = IntSet.wrap(mWorkspaceScreens.keySet());
-        filterCurrentWorkspaceItems(currentScreenIds, dataModel.workspaceItems,
-                currentWorkspaceItems, otherWorkspaceItems);
-        filterCurrentWorkspaceItems(currentScreenIds, dataModel.appWidgets, currentAppWidgets,
-                otherAppWidgets);
-        for (ItemInfo itemInfo : currentWorkspaceItems) {
-            switch (itemInfo.itemType) {
-                case Favorites.ITEM_TYPE_APPLICATION:
-                case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                    inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
-                    break;
-                case Favorites.ITEM_TYPE_FOLDER:
-                case Favorites.ITEM_TYPE_APP_PAIR:
-                    inflateAndAddCollectionIcon((CollectionInfo) itemInfo);
-                    break;
-                default:
-                    break;
-            }
-        }
-        Map<ComponentKey, AppWidgetProviderInfo> widgetsMap = widgetProviderInfoMap;
-        for (ItemInfo itemInfo : currentAppWidgets) {
-            switch (itemInfo.itemType) {
-                case Favorites.ITEM_TYPE_APPWIDGET:
-                case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
-                    if (widgetsMap == null) {
-                        widgetsMap = dataModel.widgetsModel.getWidgetsByComponentKey()
-                                .entrySet()
-                                .stream()
-                                .filter(entry -> entry.getValue().widgetInfo != null)
-                                .collect(Collectors.toMap(
-                                        Map.Entry::getKey,
-                                        entry -> entry.getValue().widgetInfo
-                                ));
+        Map<ComponentKey, AppWidgetProviderInfo>[] widgetsMap = new Map[] { widgetProviderInfoMap};
+
+        // Separate the items that are on the current screen, and the other remaining items.
+        dataModel.itemsIdMap.stream()
+                .filter(currentScreenContentFilter(IntSet.wrap(mWorkspaceScreens.keySet())))
+                .forEach(itemInfo -> {
+                    switch (itemInfo.itemType) {
+                        case Favorites.ITEM_TYPE_APPLICATION:
+                        case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                            inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
+                            break;
+                        case Favorites.ITEM_TYPE_FOLDER:
+                        case Favorites.ITEM_TYPE_APP_PAIR:
+                            inflateAndAddCollectionIcon((CollectionInfo) itemInfo);
+                            break;
+                        case Favorites.ITEM_TYPE_APPWIDGET:
+                        case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+                            if (widgetsMap[0] == null) {
+                                widgetsMap[0] = dataModel.widgetsModel.getWidgetsByComponentKey()
+                                        .entrySet()
+                                        .stream()
+                                        .filter(entry -> entry.getValue().widgetInfo != null)
+                                        .collect(Collectors.toMap(
+                                                Entry::getKey,
+                                                entry -> entry.getValue().widgetInfo
+                                        ));
+                            }
+                            inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, widgetsMap[0]);
+                            break;
+                        default:
+                            break;
                     }
-                    inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, widgetsMap);
-                    break;
-                default:
-                    break;
-            }
-        }
-        IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems,
-                mDp.numShownHotseatIcons);
+
+                    if (itemInfo.container == CONTAINER_HOTSEAT) {
+                        missingHotseatRank.remove(itemInfo.screenId);
+                    }
+                });
+
+        IntArray ranks = missingHotseatRank.getArray();
         FixedContainerItems hotseatPredictions =
                 dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
         List<ItemInfo> predictions = hotseatPredictions == null
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 9fffcc1..3464e9b 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -40,7 +40,6 @@
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.icons.FastBitmapDrawable;
-import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.util.Themes;
 
@@ -121,7 +120,8 @@
                 IconPalette.getPreloadProgressColor(context, info.bitmap.color),
                 getPreloadColors(context),
                 Utilities.isDarkTheme(context),
-                GraphicsUtils.getShapePath(context, DEFAULT_PATH_SIZE));
+                IconShape.INSTANCE.get(context).getShapeOverridePath(DEFAULT_PATH_SIZE)
+        );
     }
 
     public PreloadIconDrawable(
diff --git a/src/com/android/launcher3/icons/CacheableShortcutInfo.kt b/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
index a78da23..225e12f 100644
--- a/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
+++ b/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
@@ -112,7 +112,9 @@
                 ?.let { d ->
                     li.createBadgedIconBitmap(
                         d,
-                        IconOptions().setExtractedColor(Themes.getColorAccent(context)),
+                        IconOptions()
+                            .setExtractedColor(Themes.getColorAccent(context))
+                            .setSourceHint(getSourceHint(info, cache)),
                     )
                 } ?: BitmapInfo.LOW_RES_INFO
         }
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 88a60ea..9f99e8f 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -594,7 +594,8 @@
 
     @VisibleForTesting
     synchronized boolean isItemInDb(ComponentKey cacheKey) {
-        return getEntryFromDBLocked(cacheKey, new CacheEntry(), DEFAULT_LOOKUP_FLAG);
+        return getEntryFromDBLocked(cacheKey, new CacheEntry(), DEFAULT_LOOKUP_FLAG,
+                LauncherActivityCachingLogic.INSTANCE);
     }
 
     /**
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index c251114..de74ae8 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -19,8 +19,10 @@
 import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
 import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
 import static com.android.launcher3.Flags.enableWorkspaceInflation;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
 import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
-import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
+import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
@@ -44,11 +46,11 @@
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ItemInflater;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.LooperIdleLock;
@@ -102,14 +104,12 @@
         Trace.beginSection("BaseLauncherBinder#bindWorkspace");
         try {
             // Save a copy of all the bg-thread collections
-            ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
-            ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
+            IntSparseArrayMap<ItemInfo> itemsIdMap;
             final IntArray orderedScreenIds = new IntArray();
             ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
             final int workspaceItemCount;
             synchronized (mBgDataModel) {
-                workspaceItems.addAll(mBgDataModel.workspaceItems);
-                appWidgets.addAll(mBgDataModel.appWidgets);
+                itemsIdMap = mBgDataModel.itemsIdMap.clone();
                 orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
                 mBgDataModel.extraItems.forEach(extraItems::add);
                 if (incrementBindId) {
@@ -122,7 +122,7 @@
 
             for (Callbacks cb : mCallbacksList) {
                 new UnifiedWorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
-                        workspaceItems, appWidgets, extraItems, orderedScreenIds)
+                        itemsIdMap, extraItems, orderedScreenIds)
                         .bind(isBindSync, workspaceItemCount);
             }
         } finally {
@@ -258,8 +258,7 @@
         private final BgDataModel mBgDataModel;
 
         private final int mMyBindingId;
-        private final ArrayList<ItemInfo> mWorkspaceItems;
-        private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
+        private final IntSparseArrayMap<ItemInfo> mItemIdMap;
         private final IntArray mOrderedScreenIds;
         private final ArrayList<FixedContainerItems> mExtraItems;
 
@@ -268,8 +267,7 @@
                 LauncherAppState app,
                 BgDataModel bgDataModel,
                 int myBindingId,
-                ArrayList<ItemInfo> workspaceItems,
-                ArrayList<LauncherAppWidgetInfo> appWidgets,
+                IntSparseArrayMap<ItemInfo> itemIdMap,
                 ArrayList<FixedContainerItems> extraItems,
                 IntArray orderedScreenIds) {
             mCallbacks = callbacks;
@@ -277,8 +275,7 @@
             mApp = app;
             mBgDataModel = bgDataModel;
             mMyBindingId = myBindingId;
-            mWorkspaceItems = workspaceItems;
-            mAppWidgets = appWidgets;
+            mItemIdMap = itemIdMap;
             mExtraItems = extraItems;
             mOrderedScreenIds = orderedScreenIds;
         }
@@ -294,10 +291,15 @@
             ArrayList<ItemInfo> currentAppWidgets = new ArrayList<>();
             ArrayList<ItemInfo> otherAppWidgets = new ArrayList<>();
 
-            filterCurrentWorkspaceItems(currentScreenIds, mWorkspaceItems, currentWorkspaceItems,
-                    otherWorkspaceItems);
-            filterCurrentWorkspaceItems(currentScreenIds, mAppWidgets, currentAppWidgets,
-                    otherAppWidgets);
+            Predicate<ItemInfo> currentScreenCheck = currentScreenContentFilter(currentScreenIds);
+            mItemIdMap.forEach(item -> {
+                if (currentScreenCheck.test(item)) {
+                    (WIDGET_FILTER.test(item) ? currentAppWidgets : currentWorkspaceItems)
+                            .add(item);
+                } else if (item.container == CONTAINER_DESKTOP) {
+                    (WIDGET_FILTER.test(item) ? otherAppWidgets : otherWorkspaceItems).add(item);
+                }
+            });
             final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
             sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);
             sortWorkspaceItemsSpatially(idp, otherWorkspaceItems);
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index b9b1e98..a04cbfb 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -20,6 +20,11 @@
 import static com.android.launcher3.BuildConfig.QSB_ON_FIRST_SCREEN;
 import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
 import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
 import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
 import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
 
@@ -31,7 +36,6 @@
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 import android.text.TextUtils;
-import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 import android.view.View;
@@ -39,14 +43,11 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.BuildConfig;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.CollectionInfo;
-import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -93,22 +94,6 @@
     public final IntSparseArrayMap<ItemInfo> itemsIdMap = new IntSparseArrayMap<>();
 
     /**
-     * List of all the folders and shortcuts directly on the home screen (no widgets
-     * or shortcuts within folders).
-     */
-    public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
-
-    /**
-     * All LauncherAppWidgetInfo created by LauncherModel.
-     */
-    public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
-
-    /**
-     * Map of id to CollectionInfos of all the folders or app pairs created by LauncherModel
-     */
-    public final IntSparseArrayMap<CollectionInfo> collections = new IntSparseArrayMap<>();
-
-    /**
      * Extra container based items
      */
     public final IntSparseArrayMap<FixedContainerItems> extraItems = new IntSparseArrayMap<>();
@@ -144,9 +129,6 @@
      * Clears all the data
      */
     public synchronized void clear() {
-        workspaceItems.clear();
-        appWidgets.clear();
-        collections.clear();
         itemsIdMap.clear();
         deepShortcutMap.clear();
         extraItems.clear();
@@ -158,7 +140,7 @@
     public synchronized IntArray collectWorkspaceScreens() {
         IntSet screenSet = new IntSet();
         for (ItemInfo item: itemsIdMap) {
-            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+            if (item.container == CONTAINER_DESKTOP) {
                 screenSet.add(item.screenId);
             }
         }
@@ -173,26 +155,14 @@
     public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
             String[] args) {
         writer.println(prefix + "Data Model:");
-        writer.println(prefix + " ---- workspace items ");
-        for (int i = 0; i < workspaceItems.size(); i++) {
-            writer.println(prefix + '\t' + workspaceItems.get(i).toString());
-        }
-        writer.println(prefix + " ---- appwidget items ");
-        for (int i = 0; i < appWidgets.size(); i++) {
-            writer.println(prefix + '\t' + appWidgets.get(i).toString());
-        }
-        writer.println(prefix + " ---- collection items ");
-        for (int i = 0; i < collections.size(); i++) {
-            writer.println(prefix + '\t' + collections.valueAt(i).toString());
+        writer.println(prefix + " ---- items id map ");
+        for (int i = 0; i < itemsIdMap.size(); i++) {
+            writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
         }
         writer.println(prefix + " ---- extra items ");
         for (int i = 0; i < extraItems.size(); i++) {
             writer.println(prefix + '\t' + extraItems.valueAt(i).toString());
         }
-        writer.println(prefix + " ---- items id map ");
-        for (int i = 0; i < itemsIdMap.size(); i++) {
-            writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
-        }
 
         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
             writer.println(prefix + "shortcut counts ");
@@ -207,94 +177,38 @@
         removeItem(context, Arrays.asList(items));
     }
 
-    public synchronized void removeItem(Context context, Iterable<? extends ItemInfo> items) {
-        ArraySet<UserHandle> updatedDeepShortcuts = new ArraySet<>();
-        for (ItemInfo item : items) {
-            switch (item.itemType) {
-                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
-                    collections.remove(item.id);
-                    if (FeatureFlags.IS_STUDIO_BUILD) {
-                        for (ItemInfo info : itemsIdMap) {
-                            if (info.container == item.id) {
-                                // We are deleting a collection which still contains items that
-                                // think they are contained by that collection.
-                                String msg = "deleting a collection (" + item + ") which still "
-                                        + "contains items (" + info + ")";
-                                Log.e(TAG, msg);
-                            }
-                        }
-                    }
-                    workspaceItems.remove(item);
-                    break;
-                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
-                    updatedDeepShortcuts.add(item.user);
-                    // Fall through.
-                }
-                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                    workspaceItems.remove(item);
-                    break;
-                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-                case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
-                    appWidgets.remove(item);
-                    break;
-            }
-            itemsIdMap.remove(item.id);
+    public synchronized void removeItem(Context context, List<? extends ItemInfo> items) {
+        if (BuildConfig.IS_STUDIO_BUILD) {
+            items.stream()
+                    .filter(item -> item.itemType == ITEM_TYPE_FOLDER
+                            || item.itemType == ITEM_TYPE_APP_PAIR)
+                    .forEach(item -> itemsIdMap.stream()
+                            .filter(info -> info.container == item.id)
+                            // We are deleting a collection which still contains items that
+                            // think they are contained by that collection.
+                            .forEach(info -> Log.e(TAG,
+                                    "deleting a collection (" + item + ") which still contains"
+                                            + " items (" + info + ")")));
         }
-        updatedDeepShortcuts.forEach(user -> updateShortcutPinnedState(context, user));
+
+        items.forEach(item -> itemsIdMap.remove(item.id));
+        items.stream().map(info -> info.user).distinct().forEach(
+                user -> updateShortcutPinnedState(context, user));
     }
 
     public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
-        addItem(context, item, newItem, null);
-    }
-
-    public synchronized void addItem(
-            Context context, ItemInfo item, boolean newItem, @Nullable LoaderMemoryLogger logger) {
-        if (logger != null) {
-            logger.addLog(
-                    Log.DEBUG,
-                    TAG,
-                    String.format("Adding item to ID map: %s", item.toString()),
-                    /* stackTrace= */ null);
-        }
         itemsIdMap.put(item.id, item);
-        switch (item.itemType) {
-            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                collections.put(item.id, (FolderInfo) item);
-                workspaceItems.add(item);
-                break;
-            case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
-                collections.put(item.id, (AppPairInfo) item);
-                // Fall through here. App pairs are both containers (like folders) and containable
-                // items (can be placed in folders). So we need to add app pairs to the folders
-                // array (above) but also verify the existence of their container, like regular
-                // apps (below).
-            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
-                        item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                    workspaceItems.add(item);
-                } else {
-                    if (newItem) {
-                        if (!collections.containsKey(item.container)) {
-                            // Adding an item to a nonexistent collection.
-                            String msg = "attempted to add item: " + item + " to a nonexistent app"
-                                    + " collection";
-                            Log.e(TAG, msg);
-                        }
-                    } else {
-                        findOrMakeFolder(item.container).add(item);
-                    }
-                }
-                break;
-            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
-                appWidgets.add((LauncherAppWidgetInfo) item);
-                break;
-        }
-        if (newItem && item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+        if (newItem && item.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
             updateShortcutPinnedState(context, item.user);
         }
+        if (BuildConfig.IS_DEBUG_DEVICE
+                && newItem
+                && item.container != CONTAINER_DESKTOP
+                && item.container != CONTAINER_HOTSEAT
+                && !(itemsIdMap.get(item.container) instanceof CollectionInfo)) {
+            // Adding an item to a nonexistent collection.
+            Log.e(TAG, "attempted to add item: " + item + " to a nonexistent app collection");
+        }
     }
 
     /**
@@ -334,7 +248,7 @@
         Map<String, Set<String>> modelMap = Stream.concat(
                     // Model shortcuts
                     itemStream.build()
-                        .filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
+                        .filter(wi -> wi.itemType == ITEM_TYPE_DEEP_SHORTCUT)
                         .map(ShortcutKey::fromItemInfo),
                     // Pending shortcuts
                     ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts(user))
@@ -375,24 +289,6 @@
     }
 
     /**
-     * Return an existing FolderInfo object if we have encountered this ID previously,
-     * or make a new one.
-     */
-    public synchronized CollectionInfo findOrMakeFolder(int id) {
-        // See if a placeholder was created for us already
-        CollectionInfo collectionInfo = collections.get(id);
-        if (collectionInfo == null) {
-            // No placeholder -- create a new blank folder instance. At this point, we don't know
-            // if the desired container is supposed to be a folder or an app pair. In the case that
-            // it is an app pair, the blank folder will be replaced by a blank app pair when the app
-            // pair is getting processed, in WorkspaceItemProcessor.processFolderOrAppPair().
-            collectionInfo = new FolderInfo();
-            collections.put(id, collectionInfo);
-        }
-        return collectionInfo;
-    }
-
-    /**
      * Clear all the deep shortcut counts for the given package, and re-add the new shortcut counts.
      */
     public synchronized void updateDeepShortcutCounts(
@@ -424,16 +320,6 @@
     }
 
     /**
-     * Returns a list containing all workspace items including widgets.
-     */
-    public synchronized ArrayList<ItemInfo> getAllWorkspaceItems() {
-        ArrayList<ItemInfo> items = new ArrayList<>(workspaceItems.size() + appWidgets.size());
-        items.addAll(workspaceItems);
-        items.addAll(appWidgets);
-        return items;
-    }
-
-    /**
      * Calls the provided {@code op} for all workspaceItems in the in-memory model (both persisted
      * items and dynamic/predicted items for the provided {@code userHandle}.
      * Note the call is not synchronized over the model, that should be handled by the called.
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
index aa62c32..6ad52ea 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
+++ b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
@@ -30,7 +30,6 @@
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.LauncherAppWidgetInfo
 import com.android.launcher3.model.data.WorkspaceItemInfo
-import com.android.launcher3.pm.InstallSessionHelper
 import com.android.launcher3.util.Executors
 import com.android.launcher3.util.PackageManagerHelper
 import com.android.launcher3.util.PackageUserKey
@@ -80,21 +79,22 @@
         packageManagerHelper: PackageManagerHelper,
         firstScreenItems: List<ItemInfo>,
         userKeyToSessionMap: Map<PackageUserKey, SessionInfo>,
-        allWidgets: List<LauncherAppWidgetInfo>
+        allWidgets: List<ItemInfo>,
     ): List<FirstScreenBroadcastModel> {
 
         // installers for installing items
-        val pendingItemInstallerMap: Map<String, MutableSet<String>> =
+        val pendingItemInstallerMap: Map<String, Set<String>> =
             createPendingItemsMap(userKeyToSessionMap)
+
         val installingPackages = pendingItemInstallerMap.values.flatten().toSet()
 
         // installers for installed items on first screen
-        val installedItemInstallerMap: Map<String, MutableSet<ItemInfo>> =
+        val installedItemInstallerMap: Map<String, List<ItemInfo>> =
             createInstalledItemsMap(firstScreenItems, installingPackages, packageManagerHelper)
 
         // installers for widgets on all screens
-        val allInstalledWidgetsMap: Map<String, MutableSet<LauncherAppWidgetInfo>> =
-            createAllInstalledWidgetsMap(allWidgets, installingPackages, packageManagerHelper)
+        val allInstalledWidgetsMap: Map<String, List<ItemInfo>> =
+            createInstalledItemsMap(allWidgets, installingPackages, packageManagerHelper)
 
         val allInstallers: Set<String> =
             pendingItemInstallerMap.keys +
@@ -131,39 +131,39 @@
                             context,
                             0 /* requestCode */,
                             Intent(),
-                            PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
-                        )
+                            PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
+                        ),
                     )
                     .putStringArrayListExtra(
                         PENDING_COLLECTION_ITEM_EXTRA,
-                        ArrayList(model.pendingCollectionItems)
+                        ArrayList(model.pendingCollectionItems),
                     )
                     .putStringArrayListExtra(
                         PENDING_WORKSPACE_ITEM_EXTRA,
-                        ArrayList(model.pendingWorkspaceItems)
+                        ArrayList(model.pendingWorkspaceItems),
                     )
                     .putStringArrayListExtra(
                         PENDING_HOTSEAT_ITEM_EXTRA,
-                        ArrayList(model.pendingHotseatItems)
+                        ArrayList(model.pendingHotseatItems),
                     )
                     .putStringArrayListExtra(
                         PENDING_WIDGET_ITEM_EXTRA,
-                        ArrayList(model.pendingWidgetItems)
+                        ArrayList(model.pendingWidgetItems),
                     )
                     .putStringArrayListExtra(
                         INSTALLED_WORKSPACE_ITEMS_EXTRA,
-                        ArrayList(model.installedWorkspaceItems)
+                        ArrayList(model.installedWorkspaceItems),
                     )
                     .putStringArrayListExtra(
                         INSTALLED_HOTSEAT_ITEMS_EXTRA,
-                        ArrayList(model.installedHotseatItems)
+                        ArrayList(model.installedHotseatItems),
                     )
                     .putStringArrayListExtra(
                         ALL_INSTALLED_WIDGETS_ITEM_EXTRA,
                         ArrayList(
                             model.firstScreenInstalledWidgets +
                                 model.secondaryScreenInstalledWidgets
-                        )
+                        ),
                     )
             context.sendBroadcast(intent)
         }
@@ -172,66 +172,46 @@
     /** Maps Installer packages to Set of app packages from install sessions */
     private fun createPendingItemsMap(
         userKeyToSessionMap: Map<PackageUserKey, SessionInfo>
-    ): Map<String, MutableSet<String>> {
+    ): Map<String, Set<String>> {
         val myUser = Process.myUserHandle()
-        val result = mutableMapOf<String, MutableSet<String>>()
-        userKeyToSessionMap.forEach { entry ->
-            if (!myUser.equals(InstallSessionHelper.getUserHandle(entry.value))) return@forEach
-            val installer = entry.value.installerPackageName
-            val appPackage = entry.value.appPackageName
-            if (installer.isNullOrEmpty() || appPackage.isNullOrEmpty()) return@forEach
-            result.getOrPut(installer) { mutableSetOf() }.add(appPackage)
-        }
-        return result
-    }
-
-    /**
-     * Maps Installer packages to Set of ItemInfo from first screen. Filter out installing packages.
-     */
-    private fun createInstalledItemsMap(
-        firstScreenItems: List<ItemInfo>,
-        installingPackages: Set<String>,
-        packageManagerHelper: PackageManagerHelper
-    ): Map<String, MutableSet<ItemInfo>> {
-        val result = mutableMapOf<String, MutableSet<ItemInfo>>()
-        firstScreenItems.forEach { item ->
-            val appPackage = getPackageName(item) ?: return@forEach
-            if (installingPackages.contains(appPackage)) return@forEach
-            val installer = packageManagerHelper.getAppInstallerPackage(appPackage)
-            if (installer.isNullOrEmpty()) return@forEach
-            result.getOrPut(installer) { mutableSetOf() }.add(item)
-        }
-        return result
-    }
-
-    /**
-     * Maps Installer packages to Set of AppWidget packages installed on all screens. Filter out
-     * installing packages.
-     */
-    private fun createAllInstalledWidgetsMap(
-        allWidgets: List<LauncherAppWidgetInfo>,
-        installingPackages: Set<String>,
-        packageManagerHelper: PackageManagerHelper
-    ): Map<String, MutableSet<LauncherAppWidgetInfo>> {
-        val result = mutableMapOf<String, MutableSet<LauncherAppWidgetInfo>>()
-        allWidgets
-            .sortedBy { widget -> widget.screenId }
-            .forEach { widget ->
-                val appPackage = getPackageName(widget) ?: return@forEach
-                if (installingPackages.contains(appPackage)) return@forEach
-                val installer = packageManagerHelper.getAppInstallerPackage(appPackage)
-                if (installer.isNullOrEmpty()) return@forEach
-                result.getOrPut(installer) { mutableSetOf() }.add(widget)
+        return userKeyToSessionMap.values
+            .filter {
+                it.user == myUser &&
+                    !it.installerPackageName.isNullOrEmpty() &&
+                    !it.appPackageName.isNullOrEmpty()
             }
-        return result
+            .groupBy(
+                keySelector = { it.installerPackageName },
+                valueTransform = { it.appPackageName },
+            )
+            .mapValues { it.value.filterNotNull().toSet() } as Map<String, Set<String>>
     }
 
+    /** Maps Installer packages to Set of ItemInfos. Filter out installing packages. */
+    private fun createInstalledItemsMap(
+        allItems: Iterable<ItemInfo>,
+        installingPackages: Set<String>,
+        packageManagerHelper: PackageManagerHelper,
+    ): Map<String, List<ItemInfo>> =
+        allItems
+            .sortedBy { it.screenId }
+            .groupByTo(mutableMapOf()) {
+                getPackageName(it)?.let { pkg ->
+                    if (installingPackages.contains(pkg)) {
+                        null
+                    } else {
+                        packageManagerHelper.getAppInstallerPackage(pkg)
+                    }
+                }
+            }
+            .apply { remove(null) } as Map<String, List<ItemInfo>>
+
     /**
      * Add first screen Pending Items from Map to [FirstScreenBroadcastModel] for given installer
      */
     private fun FirstScreenBroadcastModel.addPendingItems(
         installingItems: Set<String>?,
-        firstScreenItems: List<ItemInfo>
+        firstScreenItems: List<ItemInfo>,
     ) {
         if (installingItems == null) return
         for (info in firstScreenItems) {
@@ -251,7 +231,7 @@
      */
     private fun FirstScreenBroadcastModel.addInstalledItems(
         installer: String,
-        installedItemInstallerMap: Map<String, Set<ItemInfo>>,
+        installedItemInstallerMap: Map<String, List<ItemInfo>>,
     ) {
         installedItemInstallerMap[installer]?.forEach { info ->
             val packageName: String = getPackageName(info) ?: return@forEach
@@ -265,7 +245,7 @@
     /** Add Widgets on every screen from Map to [FirstScreenBroadcastModel] for given installer */
     private fun FirstScreenBroadcastModel.addAllScreenWidgets(
         installer: String,
-        allInstalledWidgetsMap: Map<String, Set<LauncherAppWidgetInfo>>
+        allInstalledWidgetsMap: Map<String, List<ItemInfo>>,
     ) {
         allInstalledWidgetsMap[installer]?.forEach { widget ->
             val packageName: String = getPackageName(widget) ?: return@forEach
@@ -279,7 +259,7 @@
 
     private fun FirstScreenBroadcastModel.addCollectionItems(
         info: ItemInfo,
-        installingPackages: Set<String>
+        installingPackages: Set<String>,
     ) {
         if (info !is CollectionInfo) return
         pendingCollectionItems.addAll(
@@ -336,7 +316,7 @@
             Log.d(
                 TAG,
                 "Sending First Screen Broadcast for installer=$installerPackage" +
-                    ", total packages=${getTotalItemCount()}"
+                    ", total packages=${getTotalItemCount()}",
             )
             pendingCollectionItems.forEach {
                 Log.d(TAG, "$installerPackage:Pending Collection item:$it")
@@ -361,15 +341,7 @@
         }
     }
 
-    private fun getPackageName(info: ItemInfo): String? {
-        var packageName: String? = null
-        if (info is LauncherAppWidgetInfo) {
-            info.providerName?.let { packageName = info.providerName.packageName }
-        } else if (info.targetComponent != null) {
-            packageName = info.targetComponent?.packageName
-        }
-        return packageName
-    }
+    private fun getPackageName(info: ItemInfo): String? = info.targetComponent?.packageName
 
     /**
      * Clone the provided list on UI thread. This is used for [FolderInfo.getContents] which is
diff --git a/src/com/android/launcher3/model/GridSizeMigrationDBController.java b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
index 0732379..8cbe764 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationDBController.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
@@ -140,22 +140,13 @@
         try (SQLiteTransaction t = new SQLiteTransaction(target.getWritableDatabase())) {
 
             if (shouldMigrateToStrictlyTallerGrid) {
-                // This is a special case where if the grid is the same amount of columns but a
-                // larger amount of rows we simply copy over the source grid to the destination
-                // grid, rather than undergoing the general grid migration. If there are more icons
-                // on the bottom of the first page then we shift the icons down to the bottom of the
-                // grid so that the icons remain bottom-anchored.
+                // We want to add the extra row(s) to the top of the screen, so we shift the grid
+                // down.
                 if (oneGridSpecs()) {
-                    DbReader destReader = new DbReader(
-                            target.getWritableDatabase(), TABLE_NAME, context);
-                    boolean shouldShiftCells =
-                            shouldShiftCells(destReader, srcDeviceState.getRows());
-                    if (shouldShiftCells) {
-                        shiftTableByXCells(
-                                target.getWritableDatabase(),
-                                (destDeviceState.getRows() - srcDeviceState.getRows()),
-                                TABLE_NAME);
-                    }
+                    shiftTableByXCells(
+                            target.getWritableDatabase(),
+                            (destDeviceState.getRows() - srcDeviceState.getRows()),
+                            TABLE_NAME);
                 }
 
                 // Save current configuration, so that the migration does not run again.
@@ -455,22 +446,6 @@
         }
     }
 
-    private static boolean shouldShiftCells(final DbReader destReader, final int srcGridRowCount) {
-        List<DbEntry> workspaceItems = destReader.loadAllWorkspaceEntries();
-        int firstPageItemsRowPosSum = workspaceItems.stream()
-                .filter(entry -> entry.screenId == 0)
-                .mapToInt(entry -> entry.cellY).sum();
-        int firstPageWorkspaceItemsCount = (int) workspaceItems.stream()
-                .filter(entry -> entry.screenId == 0).count();
-        if (firstPageWorkspaceItemsCount == 0) {
-            return false;
-        }
-        float srcGridMidPoint = srcGridRowCount / 2f;
-        float firstPageItemPosAvg = (float) firstPageItemsRowPosSum / firstPageWorkspaceItemsCount;
-        return (firstPageItemPosAvg >= srcGridMidPoint);
-    }
-
-
     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
     public static class DbReader {
 
diff --git a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
index 1729153..dfda8e4 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
+++ b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
@@ -77,24 +77,16 @@
         val migrationStartTime = System.currentTimeMillis()
         try {
             SQLiteTransaction(target.writableDatabase).use { t ->
-                // This is a special case where if the grid is the same amount of columns but a
-                // larger amount of rows we simply copy over the source grid to the destination
-                // grid, rather than undergoing the general grid migration. If there are more icons
-                // on the bottom of the first page then we shift the icons down to the bottom of the
-                // grid so that the icons remain bottom-anchored.
+                // We want to add the extra row(s) to the top of the screen, so we shift the grid
+                // down.
                 if (shouldMigrateToStrtictlyTallerGrid) {
                     Log.d(TAG, "Migrating to strictly taller grid")
                     if (oneGridSpecs()) {
-                        val destReader = DbReader(target.writableDatabase, TABLE_NAME, context)
-                        val shouldShiftCells = shouldShiftCells(destReader, srcDeviceState.rows)
-                        if (shouldShiftCells) {
-                            Log.i("TAGTAG", "should shift cells")
-                            shiftTableByXCells(
-                                target.writableDatabase,
-                                (destDeviceState.rows - srcDeviceState.rows),
-                                TABLE_NAME,
-                            )
-                        }
+                        shiftTableByXCells(
+                            target.writableDatabase,
+                            (destDeviceState.rows - srcDeviceState.rows),
+                            TABLE_NAME,
+                        )
                     }
                     // Save current configuration, so that the migration does not run again.
                     destDeviceState.writeToPrefs(context)
@@ -139,19 +131,6 @@
         }
     }
 
-    private fun shouldShiftCells(destReader: DbReader, srcGridRowCount: Int): Boolean {
-        val workspaceItems = destReader.loadAllWorkspaceEntries()
-        val firstPageItemsRowPosSum =
-            workspaceItems.sumOf { entry -> if (entry.screenId == 0) entry.cellY else 0 }
-        val firstPageWorkspaceItemsCount = workspaceItems.count { entry -> entry.screenId == 0 }
-        if (firstPageWorkspaceItemsCount == 0) {
-            return false
-        }
-        val srcGridMidPoint = srcGridRowCount / 2f
-        val firstPageItemPosAvg = firstPageItemsRowPosSum / firstPageWorkspaceItemsCount.toFloat()
-        return (firstPageItemPosAvg >= srcGridMidPoint)
-    }
-
     /** Handles hotseat migration. */
     @VisibleForTesting
     fun migrateHotseat(
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 536d4c9..1623881 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -16,6 +16,11 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
 import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
@@ -48,6 +53,8 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.CollectionInfo;
+import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.IconRequestInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -84,6 +91,11 @@
     private final IntArray mRestoredRows = new IntArray();
     private final IntSparseArrayMap<GridOccupancy> mOccupied = new IntSparseArrayMap<>();
 
+    // CollectionInfo objects, which have not yet been loaded from the DB, but are expected to
+    // found eventually as the loading progresses
+    private final IntSparseArrayMap<CollectionInfo> mPendingCollectionInfo =
+            new IntSparseArrayMap<>();
+
     private final int mIconIndex;
     public final int mTitleIndex;
 
@@ -479,8 +491,26 @@
         info.cellY = getInt(mCellYIndex);
     }
 
-    public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) {
-        checkAndAddItem(info, dataModel, null);
+    /**
+     * Return an existing FolderInfo object if we have encountered this ID previously,
+     * or make a new one.
+     */
+    public CollectionInfo findOrMakeFolder(int id, BgDataModel dataModel) {
+        // See if a placeholder was created for us already
+        ItemInfo info = dataModel.itemsIdMap.get(id);
+        if (info instanceof CollectionInfo c) return c;
+
+        CollectionInfo pending = mPendingCollectionInfo.get(id);
+        if (pending != null) return pending;
+
+        // No placeholder -- create a new blank folder instance. At this point, we don't know
+        // if the desired container is supposed to be a folder or an app pair. In the case that
+        // it is an app pair, the blank folder will be replaced by a blank app pair when the app
+        // pair is getting processed, in WorkspaceItemProcessor.processFolderOrAppPair().
+        pending = new FolderInfo();
+        pending.id = id;
+        mPendingCollectionInfo.put(id, pending);
+        return pending;
     }
 
     /**
@@ -495,7 +525,21 @@
             ShortcutKey.fromItemInfo(info);
         }
         if (checkItemPlacement(info, dataModel.isFirstPagePinnedItemEnabled)) {
-            dataModel.addItem(mContext, info, false, logger);
+            if (logger != null) {
+                logger.addLog(
+                        Log.DEBUG,
+                        TAG,
+                        String.format("Adding item to ID map: %s", info),
+                        /* stackTrace= */ null);
+            }
+            dataModel.addItem(mContext, info, false);
+            if ((info.itemType == ITEM_TYPE_APP_PAIR
+                    || info.itemType == ITEM_TYPE_DEEP_SHORTCUT
+                    || info.itemType == ITEM_TYPE_APPLICATION)
+                    && info.container != CONTAINER_DESKTOP
+                    && info.container != CONTAINER_HOTSEAT) {
+                findOrMakeFolder(info.container, dataModel).add(info);
+            }
             if (mRestoreEventLogger != null) {
                 mRestoreEventLogger.logSingleFavoritesItemRestored(itemType);
             }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 44b7e8b..fee9696 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -31,7 +31,8 @@
 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;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
-import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
+import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
@@ -82,7 +83,6 @@
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.AppPairInfo;
-import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.IconRequestInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -210,10 +210,10 @@
         final int firstScreen = allScreens.get(0);
         IntSet firstScreens = IntSet.wrap(firstScreen);
 
-        ArrayList<ItemInfo> allItems = mBgDataModel.getAllWorkspaceItems();
-        ArrayList<ItemInfo> firstScreenItems = new ArrayList<>();
-        filterCurrentWorkspaceItems(firstScreens, allItems, firstScreenItems,
-                new ArrayList<>() /* otherScreenItems are ignored */);
+        List<ItemInfo> firstScreenItems =
+                mBgDataModel.itemsIdMap.stream()
+                        .filter(currentScreenContentFilter(firstScreens))
+                        .toList();
         final int disableArchivingLauncherBroadcast = Settings.Secure.getInt(
                 mApp.getContext().getContentResolver(),
                 "disable_launcher_broadcast_installed_apps",
@@ -227,7 +227,7 @@
                             mPmHelper,
                             firstScreenItems,
                             mInstallingPkgsCached,
-                            mBgDataModel.appWidgets
+                            mBgDataModel.itemsIdMap.stream().filter(WIDGET_FILTER).toList()
                     );
             logASplit("Sending first screen broadcast with additional archiving Extras");
             FirstScreenBroadcastHelper.sendBroadcastsForModels(mApp.getContext(), broadcastModels);
@@ -523,14 +523,13 @@
      * requests high-res icons for the items that are part of an app pair.
      */
     private void processAppPairItems() {
-        for (CollectionInfo collection : mBgDataModel.collections) {
-            if (!(collection instanceof AppPairInfo appPair)) {
-                continue;
-            }
-
-            appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR);
-            appPair.fetchHiResIconsIfNeeded(mIconCache);
-        }
+        mBgDataModel.itemsIdMap.stream()
+                .filter(item -> item instanceof AppPairInfo)
+                .forEach(item -> {
+                    AppPairInfo appPair = (AppPairInfo) item;
+                    appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR);
+                    appPair.fetchHiResIconsIfNeeded(mIconCache);
+                });
     }
 
     /**
@@ -586,8 +585,8 @@
         // Sort the folder items, update ranks, and make sure all preview items are high res.
         List<FolderGridOrganizer> verifiers = mApp.getInvariantDeviceProfile().supportedProfiles
                 .stream().map(FolderGridOrganizer::createFolderGridOrganizer).toList();
-        for (CollectionInfo collection : mBgDataModel.collections) {
-            if (!(collection instanceof FolderInfo folder)) {
+        for (ItemInfo itemInfo : mBgDataModel.itemsIdMap) {
+            if (!(itemInfo instanceof FolderInfo folder)) {
                 continue;
             }
 
@@ -657,8 +656,6 @@
             IntArray deletedFolderIds = mApp.getModel().getModelDbController().deleteEmptyFolders();
             synchronized (mBgDataModel) {
                 for (int folderId : deletedFolderIds) {
-                    mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(folderId));
-                    mBgDataModel.collections.remove(folderId);
                     mBgDataModel.itemsIdMap.remove(folderId);
                 }
             }
@@ -676,8 +673,6 @@
 
         synchronized (mBgDataModel) {
             for (int id : deleted) {
-                mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(id));
-                mBgDataModel.collections.remove(id);
                 mBgDataModel.itemsIdMap.remove(id);
             }
         }
@@ -819,18 +814,19 @@
 
     private void loadFolderNames() {
         FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext(),
-                mBgAllAppsList.data, mBgDataModel.collections);
+                mBgAllAppsList.data, FolderNameProvider.getCollectionForSuggestions(mBgDataModel));
 
         synchronized (mBgDataModel) {
-            for (int i = 0; i < mBgDataModel.collections.size(); i++) {
-                FolderNameInfos suggestionInfos = new FolderNameInfos();
-                CollectionInfo info = mBgDataModel.collections.valueAt(i);
-                if (info instanceof FolderInfo fi && fi.suggestedFolderNames == null) {
-                    provider.getSuggestedFolderName(mApp.getContext(), fi.getAppContents(),
-                            suggestionInfos);
-                    fi.suggestedFolderNames = suggestionInfos;
-                }
-            }
+            mBgDataModel.itemsIdMap.stream()
+                    .filter(item ->
+                            item instanceof FolderInfo fi && fi.suggestedFolderNames == null)
+                    .forEach(info -> {
+                        FolderInfo fi = (FolderInfo) info;
+                        FolderNameInfos suggestionInfos = new FolderNameInfos();
+                        provider.getSuggestedFolderName(mApp.getContext(), fi.getAppContents(),
+                                suggestionInfos);
+                        fi.suggestedFolderNames = suggestionInfos;
+                    });
         }
     }
 
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index 9e72e28..da79982 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -15,15 +15,15 @@
  */
 package com.android.launcher3.model;
 
-import com.android.launcher3.LauncherSettings;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
+
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.IntStream;
+import java.util.function.Predicate;
 
 /**
  * Utils class for {@link com.android.launcher3.LauncherModel}.
@@ -31,54 +31,17 @@
 public class ModelUtils {
 
     /**
-     * Filters the set of items who are directly or indirectly (via another container) on the
-     * specified screen.
+     * Returns a filter for items on hotseat or current screens
      */
-    public static <T extends ItemInfo> void filterCurrentWorkspaceItems(
-            final IntSet currentScreenIds,
-            List<? extends T> allWorkspaceItems,
-            List<T> currentScreenItems,
-            List<T> otherScreenItems) {
-        // Purge any null ItemInfos
-        allWorkspaceItems.removeIf(Objects::isNull);
-        // Order the set of items by their containers first, this allows use to walk through the
-        // list sequentially, build up a list of containers that are in the specified screen,
-        // as well as all items in those containers.
-        IntSet itemsOnScreen = new IntSet();
-        Collections.sort(allWorkspaceItems,
-                (lhs, rhs) -> Integer.compare(lhs.container, rhs.container));
-        for (T info : allWorkspaceItems) {
-            if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                if (currentScreenIds.contains(info.screenId)) {
-                    currentScreenItems.add(info);
-                    itemsOnScreen.add(info.id);
-                } else {
-                    otherScreenItems.add(info);
-                }
-            } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                currentScreenItems.add(info);
-                itemsOnScreen.add(info.id);
-            } else {
-                if (itemsOnScreen.contains(info.container)) {
-                    currentScreenItems.add(info);
-                    itemsOnScreen.add(info.id);
-                } else {
-                    otherScreenItems.add(info);
-                }
-            }
-        }
+    public static Predicate<ItemInfo> currentScreenContentFilter(IntSet currentScreenIds) {
+        return item -> item.container == CONTAINER_HOTSEAT
+                || (item.container == CONTAINER_DESKTOP
+                        && currentScreenIds.contains(item.screenId));
     }
 
     /**
-     * Iterates though current workspace items and returns available hotseat ranks for prediction.
+     * Returns a filter for widget items
      */
-    public static IntArray getMissingHotseatRanks(List<ItemInfo> items, int len) {
-        IntSet seen = new IntSet();
-        items.stream().filter(
-                info -> info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)
-                .forEach(i -> seen.add(i.screenId));
-        IntArray result = new IntArray(len);
-        IntStream.range(0, len).filter(i -> !seen.contains(i)).forEach(result::add);
-        return result;
-    }
+    public static final Predicate<ItemInfo> WIDGET_FILTER = item ->
+            item.itemType == ITEM_TYPE_APPWIDGET || item.itemType == ITEM_TYPE_CUSTOM_APPWIDGET;
 }
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index b477cb1..0332775 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -459,37 +459,14 @@
                 if (item.container != Favorites.CONTAINER_DESKTOP &&
                         item.container != Favorites.CONTAINER_HOTSEAT) {
                     // Item is in a collection, make sure this collection exists
-                    if (!mBgDataModel.collections.containsKey(item.container)) {
+                    if (!(mBgDataModel.itemsIdMap.get(item.container) instanceof CollectionInfo)) {
                         // An items container is being set to a that of an item which is not in
-                        // the list of Folders.
+                        // the list of collections.
                         String msg = "item: " + item + " container being set to: " +
                                 item.container + ", not in the list of collections";
                         Log.e(TAG, msg);
                     }
                 }
-
-                // Items are added/removed from the corresponding FolderInfo elsewhere, such
-                // as in Workspace.onDrop. Here, we just add/remove them from the list of items
-                // that are on the desktop, as appropriate
-                ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
-                if (modelItem != null &&
-                        (modelItem.container == Favorites.CONTAINER_DESKTOP ||
-                                modelItem.container == Favorites.CONTAINER_HOTSEAT)) {
-                    switch (modelItem.itemType) {
-                        case Favorites.ITEM_TYPE_APPLICATION:
-                        case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                        case Favorites.ITEM_TYPE_FOLDER:
-                        case Favorites.ITEM_TYPE_APP_PAIR:
-                            if (!mBgDataModel.workspaceItems.contains(modelItem)) {
-                                mBgDataModel.workspaceItems.add(modelItem);
-                            }
-                            break;
-                        default:
-                            break;
-                    }
-                } else {
-                    mBgDataModel.workspaceItems.remove(modelItem);
-                }
                 mVerifier.verifyModel();
             }
         }
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index d238213..4103937 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
+
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -85,12 +87,16 @@
                 }
             });
 
-            for (LauncherAppWidgetInfo widget : dataModel.appWidgets) {
-                if (widget.providerName.getPackageName().equals(mInstallInfo.packageName)) {
-                    widget.installProgress = mInstallInfo.progress;
-                    updates.add(widget);
-                }
-            }
+            dataModel.itemsIdMap.stream()
+                    .filter(WIDGET_FILTER)
+                    .filter(item -> mInstallInfo.user.equals(item.user))
+                    .map(item -> (LauncherAppWidgetInfo) item)
+                    .filter(widget -> widget.providerName.getPackageName()
+                            .equals(mInstallInfo.packageName))
+                    .forEach(widget -> {
+                        widget.installProgress = mInstallInfo.progress;
+                        updates.add(widget);
+                    });
 
             if (!updates.isEmpty()) {
                 taskController.scheduleCallbackTask(
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index d619965..1153f48 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -18,7 +18,9 @@
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
+import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
 import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
 import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_ICON;
 
@@ -347,24 +349,25 @@
                     }
                 });
 
-                for (LauncherAppWidgetInfo widgetInfo : dataModel.appWidgets) {
-                    if (mUser.equals(widgetInfo.user)
-                            && widgetInfo.hasRestoreFlag(
-                            LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
-                            && packageSet.contains(widgetInfo.providerName.getPackageName())) {
-                        widgetInfo.restoreStatus &=
-                                ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY
-                                        & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
+                dataModel.itemsIdMap.stream()
+                        .filter(WIDGET_FILTER)
+                        .filter(item -> mUser.equals(item.user))
+                        .map(item -> (LauncherAppWidgetInfo) item)
+                        .filter(widget -> widget.hasRestoreFlag(FLAG_PROVIDER_NOT_READY)
+                                && packageSet.contains(widget.providerName.getPackageName()))
+                        .forEach(widgetInfo -> {
+                            widgetInfo.restoreStatus &=
+                                    ~FLAG_PROVIDER_NOT_READY
+                                            & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
 
-                        // adding this flag ensures that launcher shows 'click to setup'
-                        // if the widget has a config activity. In case there is no config
-                        // activity, it will be marked as 'restored' during bind.
-                        widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+                            // adding this flag ensures that launcher shows 'click to setup'
+                            // if the widget has a config activity. In case there is no config
+                            // activity, it will be marked as 'restored' during bind.
+                            widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
 
-                        widgets.add(widgetInfo);
-                        taskController.getModelWriter().updateItemInDatabase(widgetInfo);
-                    }
-                }
+                            widgets.add(widgetInfo);
+                            taskController.getModelWriter().updateItemInDatabase(widgetInfo);
+                        });
             }
 
             taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItems);
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index 0272bd9..de1df2e 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -32,7 +32,6 @@
 import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherSettings.Favorites
 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
-import com.android.launcher3.config.FeatureFlags
 import com.android.launcher3.icons.CacheableShortcutInfo
 import com.android.launcher3.icons.cache.CacheLookupFlag.Companion.DEFAULT_LOOKUP_FLAG
 import com.android.launcher3.logging.FileLog
@@ -405,24 +404,14 @@
      * stored in the BgDataModel.
      */
     private fun processFolderOrAppPair() {
-        var collection = bgDataModel.findOrMakeFolder(c.id)
+        var collection = c.findOrMakeFolder(c.id, bgDataModel)
         // If we generated a placeholder Folder before this point, it may need to be replaced with
         // an app pair.
         if (c.itemType == Favorites.ITEM_TYPE_APP_PAIR && collection is FolderInfo) {
-            if (!FeatureFlags.enableAppPairs()) {
-                // If app pairs are not enabled, stop loading.
-                Log.e(TAG, "app pairs flag is off, did not load app pair")
-                return
-            }
-
-            val folderInfo: FolderInfo = collection
             val newAppPair = AppPairInfo()
             // Move the placeholder's contents over to the new app pair.
-            folderInfo.getContents().forEach(newAppPair::add)
+            collection.getContents().forEach(newAppPair::add)
             collection = newAppPair
-            // Remove the placeholder and add the app pair into the data model.
-            bgDataModel.collections.remove(c.id)
-            bgDataModel.collections.put(c.id, collection)
         }
 
         c.applyCommonProperties(collection)
@@ -576,7 +565,7 @@
                 logWidgetInfo(app.invariantDeviceProfile, lapi)
             }
         }
-        c.checkAndAddItem(appWidgetInfo, bgDataModel)
+        c.checkAndAddItem(appWidgetInfo, bgDataModel, memoryLogger)
     }
 
     companion object {
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 772ea7f..7fb0152 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.model.data;
 
+import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
+
 import android.content.Context;
 import android.content.Intent;
 import android.os.Process;
@@ -23,6 +25,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Flags;
+import com.android.launcher3.graphics.ThemeManager;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.BitmapInfo.DrawableCreationFlags;
 import com.android.launcher3.icons.FastBitmapDrawable;
@@ -320,6 +323,9 @@
      * Returns a FastBitmapDrawable with the icon and context theme applied
      */
     public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags) {
+        if (!ThemeManager.INSTANCE.get(context).isMonoThemeEnabled()) {
+            creationFlags &= ~FLAG_THEMED;
+        }
         FastBitmapDrawable drawable = bitmap.newIcon(context, creationFlags);
         drawable.setIsDisabled(isDisabled());
         return drawable;
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index 0a5dd62..9a7c347 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.ContentWriter;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
 
 import java.util.Arrays;
 
@@ -178,7 +179,7 @@
 
     public void updateFromDeepShortcutInfo(@NonNull final ShortcutInfo shortcutInfo,
             @NonNull final Context context) {
-        if (com.android.wm.shell.Flags.enableBubbleAnything()) {
+        if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
             mShortcutInfo = shortcutInfo;
         }
         // {@link ShortcutInfo#getActivity} can change during an update. Recreate the intent
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 836ea4a..864fed0 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -170,6 +170,9 @@
                     for (NotificationsChangedListener listener : sNotificationsChangedListeners) {
                         listener.onNotificationPosted(msg.first, msg.second);
                     }
+                    Log.i(TAG, "received notification posted event - " + msg.first);
+                } else {
+                    Log.i(TAG, "received notification posted event, but there are no listeners");
                 }
                 break;
             case MSG_NOTIFICATION_REMOVED:
@@ -178,6 +181,9 @@
                     for (NotificationsChangedListener listener : sNotificationsChangedListeners) {
                         listener.onNotificationRemoved(msg.first, msg.second);
                     }
+                    Log.i(TAG, "received notification removed event - " + msg.first);
+                } else {
+                    Log.i(TAG, "received notification removed event, but there are no listeners");
                 }
                 break;
             case MSG_NOTIFICATION_FULL_REFRESH:
@@ -186,6 +192,11 @@
                         listener.onNotificationFullRefresh(
                                 (List<StatusBarNotification>) message.obj);
                     }
+                    ((List<StatusBarNotification>) message.obj).forEach(sbn -> Log.i(TAG,
+                            "Handling notification state refresh for " + sbn.getPackageName() + "#"
+                                    + sbn.getUserId()));
+                } else {
+                    Log.i(TAG, "received notification refresh event, but there are no listeners");
                 }
                 break;
         }
@@ -205,6 +216,7 @@
     @Override
     public void onListenerConnected() {
         super.onListenerConnected();
+        Log.i(TAG, "onListenerConnected");
         sIsConnected = true;
 
         // Register an observer to rebind the notification listener when dots are re-enabled.
@@ -230,6 +242,7 @@
     @Override
     public void onListenerDisconnected() {
         super.onListenerDisconnected();
+        Log.i(TAG, "onListenerDisconnected");
         sIsConnected = false;
         mSettingsCache.unregister(NOTIFICATION_BADGING_URI, mNotificationSettingsChangedListener);
         onNotificationFullRefresh();
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 329d9df..7e08c6e 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -431,7 +431,7 @@
                         && !(itemInfo instanceof WorkspaceItemInfo)) {
                     return null;
                 }
-                return new BubbleShortcut(activity, itemInfo, originalView);
+                return new BubbleShortcut<>(activity, itemInfo, originalView);
             };
 
     public interface BubbleActivityStarter {
@@ -439,7 +439,7 @@
         void showShortcutBubble(ShortcutInfo info);
 
         /** Tell SysUI to show the provided intent in a bubble. */
-        void showAppBubble(Intent intent);
+        void showAppBubble(Intent intent, UserHandle user);
     }
 
     public static class BubbleShortcut<T extends ActivityContext> extends SystemShortcut<T> {
@@ -476,7 +476,7 @@
                 if (intent.getPackage() == null) {
                     intent.setPackage(mItemInfo.getTargetPackage());
                 }
-                mStarter.showAppBubble(intent);
+                mStarter.showAppBubble(intent, mItemInfo.user);
             } else {
                 Log.w(TAG, "unable to bubble, no intent: " + mItemInfo);
             }
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index daa6e67..943a913 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
-import static com.android.launcher3.config.FeatureFlags.enableAppPairs;
 import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -326,11 +325,6 @@
                 return response;
             }
 
-            case TestProtocol.REQUEST_FLAG_ENABLE_APP_PAIRS: {
-                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, enableAppPairs());
-                return response;
-            }
-
             case TestProtocol.REQUEST_IS_RECENTS_WINDOW_ENABLED: {
                 response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
                         enableLauncherOverviewInWindow() || enableFallbackOverviewInWindow());
diff --git a/src/com/android/launcher3/util/IntSparseArrayMap.java b/src/com/android/launcher3/util/IntSparseArrayMap.java
index 9d5391b..70f74e3 100644
--- a/src/com/android/launcher3/util/IntSparseArrayMap.java
+++ b/src/com/android/launcher3/util/IntSparseArrayMap.java
@@ -19,6 +19,8 @@
 import android.util.SparseArray;
 
 import java.util.Iterator;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 
 /**
  * Extension of {@link SparseArray} with some utility methods.
@@ -43,6 +45,10 @@
         return new ValueIterator();
     }
 
+    public Stream<E> stream() {
+        return StreamSupport.stream(spliterator(), false);
+    }
+
     @Thunk class ValueIterator implements Iterator<E> {
 
         private int mNextIndex = 0;
diff --git a/src/com/android/launcher3/util/LayoutImportExportHelper.kt b/src/com/android/launcher3/util/LayoutImportExportHelper.kt
index 4033f60..8559f3b 100644
--- a/src/com/android/launcher3/util/LayoutImportExportHelper.kt
+++ b/src/com/android/launcher3/util/LayoutImportExportHelper.kt
@@ -56,7 +56,7 @@
 
         model.enqueueModelUpdateTask { _, dataModel, _ ->
             val builder = LauncherLayoutBuilder()
-            dataModel.workspaceItems.forEach { info ->
+            dataModel.itemsIdMap.forEach { info ->
                 val loc =
                     when (info.container) {
                         CONTAINER_DESKTOP ->
@@ -67,9 +67,6 @@
                     }
                 loc.addItem(context, info)
             }
-            dataModel.appWidgets.forEach { info ->
-                builder.atWorkspace(info.cellX, info.cellY, info.screenId).addItem(context, info)
-            }
 
             val layoutXml = builder.build()
             callback(layoutXml)
@@ -136,4 +133,4 @@
                 )
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt
index c8d86d4..a6a6ceb 100644
--- a/src/com/android/launcher3/util/LockedUserState.kt
+++ b/src/com/android/launcher3/util/LockedUserState.kt
@@ -20,10 +20,17 @@
 import android.os.Process
 import android.os.UserManager
 import androidx.annotation.VisibleForTesting
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.util.Executors.MAIN_EXECUTOR
 import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import javax.inject.Inject
 
-class LockedUserState(private val mContext: Context) : SafeCloseable {
+@LauncherAppSingleton
+class LockedUserState
+@Inject
+constructor(@ApplicationContext private val context: Context, lifeCycle: DaggerSingletonTracker) {
     val isUserUnlockedAtLauncherStartup: Boolean
     var isUserUnlocked = false
         private set(value) {
@@ -36,7 +43,7 @@
     private val mUserUnlockedActions: RunnableList = RunnableList()
 
     @VisibleForTesting
-    val mUserUnlockedReceiver =
+    val userUnlockedReceiver =
         SimpleBroadcastReceiver(UI_HELPER_EXECUTOR) {
             if (Intent.ACTION_USER_UNLOCKED == it.action) {
                 isUserUnlocked = true
@@ -53,8 +60,8 @@
         isUserUnlocked = checkIsUserUnlocked()
         isUserUnlockedAtLauncherStartup = isUserUnlocked
         if (!isUserUnlocked) {
-            mUserUnlockedReceiver.register(
-                mContext,
+            userUnlockedReceiver.register(
+                context,
                 {
                     // If user is unlocked while registering broadcast receiver, we should update
                     // [isUserUnlocked], which will call [notifyUserUnlocked] in setter
@@ -62,22 +69,18 @@
                         MAIN_EXECUTOR.execute { isUserUnlocked = true }
                     }
                 },
-                Intent.ACTION_USER_UNLOCKED
+                Intent.ACTION_USER_UNLOCKED,
             )
         }
+        lifeCycle.addCloseable { userUnlockedReceiver.unregisterReceiverSafely(context) }
     }
 
     private fun checkIsUserUnlocked() =
-        mContext.getSystemService(UserManager::class.java)!!.isUserUnlocked(Process.myUserHandle())
+        context.getSystemService(UserManager::class.java)!!.isUserUnlocked(Process.myUserHandle())
 
     private fun notifyUserUnlocked() {
         mUserUnlockedActions.executeAllAndDestroy()
-        mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
-    }
-
-    /** Stops the receiver from listening for ACTION_USER_UNLOCK broadcasts. */
-    override fun close() {
-        mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
+        userUnlockedReceiver.unregisterReceiverSafely(context)
     }
 
     /**
@@ -88,9 +91,7 @@
         mUserUnlockedActions.add(action)
     }
 
-    /**
-     * Removes a previously queued `Runnable` to be run when the user is unlocked.
-     */
+    /** Removes a previously queued `Runnable` to be run when the user is unlocked. */
     fun removeOnUserUnlockedRunnable(action: Runnable) {
         mUserUnlockedActions.remove(action)
     }
@@ -98,7 +99,7 @@
     companion object {
         @VisibleForTesting
         @JvmField
-        val INSTANCE = MainThreadInitializedObject { LockedUserState(it) }
+        val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getLockedUserState)
 
         @JvmStatic fun get(context: Context): LockedUserState = INSTANCE.get(context)
     }
diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java
index 8fe6e93..fa183c8 100644
--- a/src/com/android/launcher3/util/SettingsCache.java
+++ b/src/com/android/launcher3/util/SettingsCache.java
@@ -34,11 +34,11 @@
 import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.dagger.LauncherBaseAppComponent;
 
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Function;
 
 import javax.inject.Inject;
 
@@ -57,7 +57,7 @@
  * Cache will also be updated if a key queried is missing (even if it has no listeners registered).
  */
 @LauncherAppSingleton
-public class SettingsCache extends ContentObserver implements SafeCloseable {
+public class SettingsCache extends ContentObserver {
 
     /** Hidden field Settings.Secure.NOTIFICATION_BADGING */
     public static final Uri NOTIFICATION_BADGING_URI =
@@ -79,11 +79,17 @@
     private static final String SYSTEM_URI_PREFIX = Settings.System.CONTENT_URI.toString();
     private static final String GLOBAL_URI_PREFIX = Settings.Global.CONTENT_URI.toString();
 
+    private final Function<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMapper = uri -> {
+        registerUriAsync(uri);
+        return new CopyOnWriteArrayList<>();
+    };
+
     /**
      * Caches the last seen value for registered keys.
      */
-    private Map<Uri, Boolean> mKeyCache = new ConcurrentHashMap<>();
-    private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap = new HashMap<>();
+    private final Map<Uri, Boolean> mKeyCache = new ConcurrentHashMap<>();
+    private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap =
+            new ConcurrentHashMap<>();
     protected final ContentResolver mResolver;
 
     /**
@@ -96,12 +102,8 @@
     SettingsCache(@ApplicationContext Context context, DaggerSingletonTracker tracker) {
         super(new Handler(Looper.getMainLooper()));
         mResolver = context.getContentResolver();
-        tracker.addCloseable(this);
-    }
-
-    @Override
-    public void close() {
-        UI_HELPER_EXECUTOR.execute(() -> mResolver.unregisterContentObserver(this));
+        tracker.addCloseable(() ->
+                UI_HELPER_EXECUTOR.execute(() -> mResolver.unregisterContentObserver(this)));
     }
 
     @Override
@@ -109,11 +111,12 @@
         // We use default of 1, but if we're getting an onChange call, can assume a non-default
         // value will exist
         boolean newVal = updateValue(uri, 1 /* Effectively Unused */);
-        if (!mListenerMap.containsKey(uri)) {
+        List<OnChangeListener> listeners = mListenerMap.get(uri);
+        if (listeners == null) {
             return;
         }
 
-        for (OnChangeListener listener : mListenerMap.get(uri)) {
+        for (OnChangeListener listener : listeners) {
             listener.onSettingsChanged(newVal);
         }
     }
@@ -138,22 +141,17 @@
         }
     }
 
+    private void registerUriAsync(Uri uri) {
+        UI_HELPER_EXECUTOR.execute(() -> mResolver.registerContentObserver(uri, false, this));
+    }
+
     /**
      * Does not de-dupe if you add same listeners for the same key multiple times.
      * Unregister once complete using {@link #unregister(Uri, OnChangeListener)}
      */
     @UiThread
     public void register(Uri uri, OnChangeListener changeListener) {
-        Preconditions.assertUIThread();
-        if (mListenerMap.containsKey(uri)) {
-            mListenerMap.get(uri).add(changeListener);
-        } else {
-            CopyOnWriteArrayList<OnChangeListener> l = new CopyOnWriteArrayList<>();
-            l.add(changeListener);
-            mListenerMap.put(uri, l);
-            UI_HELPER_EXECUTOR.execute(
-                    () -> mResolver.registerContentObserver(uri, false, this));
-        }
+        mListenerMap.computeIfAbsent(uri, mListenerMapper).add(changeListener);
     }
 
     private boolean updateValue(Uri keyUri, int defaultValue) {
diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java
index 2fa8bf4..1627057 100644
--- a/src/com/android/launcher3/util/ViewPool.java
+++ b/src/com/android/launcher3/util/ViewPool.java
@@ -17,6 +17,7 @@
 
 import android.content.Context;
 import android.os.Handler;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -33,6 +34,7 @@
  * During initialization, views are inflated on the background thread.
  */
 public class ViewPool<T extends View & Reusable> {
+    private static final String TAG = ViewPool.class.getSimpleName();
 
     private final Object[] mPool;
 
@@ -42,6 +44,9 @@
 
     private int mCurrentSize = 0;
 
+    @Nullable
+    private Thread mViewPoolInitThread;
+
     public ViewPool(Context context, @Nullable ViewGroup parent,
             int layoutId, int maxSize, int initialSize) {
         this(LayoutInflater.from(context).cloneInContext(context),
@@ -72,12 +77,15 @@
 
         // Inflate views on a non looper thread. This allows us to catch errors like calling
         // "new Handler()" in constructor easily.
-        new Thread(() -> {
+        mViewPoolInitThread = new Thread(() -> {
             for (int i = 0; i < initialSize; i++) {
                 T view = inflateNewView(inflater);
                 handler.post(() -> addToPool(view));
             }
-        }, "ViewPool-init").start();
+            Log.d(TAG, "initPool complete");
+            mViewPoolInitThread = null;
+        }, "ViewPool-init");
+        mViewPoolInitThread.start();
     }
 
     @UiThread
@@ -114,6 +122,12 @@
         return (T) inflater.inflate(mLayoutId, mParent, false);
     }
 
+    public void killOngoingInitializations() throws InterruptedException {
+        if (mViewPoolInitThread != null) {
+            mViewPoolInitThread.join();
+        }
+    }
+
     /**
      * Interface to indicate that a view is reusable
      */
diff --git a/src/com/android/launcher3/util/WallpaperColorHints.kt b/src/com/android/launcher3/util/WallpaperColorHints.kt
index 11d4c25..29fe31a 100644
--- a/src/com/android/launcher3/util/WallpaperColorHints.kt
+++ b/src/com/android/launcher3/util/WallpaperColorHints.kt
@@ -23,14 +23,21 @@
 import android.content.Context
 import androidx.annotation.MainThread
 import androidx.annotation.VisibleForTesting
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.util.Executors.MAIN_EXECUTOR
 import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import javax.inject.Inject
 
 /**
  * This class caches the system's wallpaper color hints for use by other classes as a performance
  * enhancer. It also centralizes all the WallpaperManager color hint code in one location.
  */
-class WallpaperColorHints(private val context: Context) : SafeCloseable {
+@LauncherAppSingleton
+class WallpaperColorHints
+@Inject
+constructor(@ApplicationContext private val context: Context, tracker: DaggerSingletonTracker) {
     var hints: Int = 0
         private set
 
@@ -38,7 +45,6 @@
         get() = context.getSystemService(WallpaperManager::class.java)!!
 
     private val onColorHintsChangedListeners = mutableListOf<OnColorHintListener>()
-    private val onClose: SafeCloseable
 
     init {
         hints = wallpaperManager.getWallpaperColors(FLAG_SYSTEM)?.colorHints ?: 0
@@ -51,7 +57,7 @@
                 MAIN_EXECUTOR.handler,
             )
         }
-        onClose = SafeCloseable {
+        tracker.addCloseable {
             UI_HELPER_EXECUTOR.execute {
                 wallpaperManager.removeOnColorsChangedListener(onColorsChangedListener)
             }
@@ -69,8 +75,6 @@
         }
     }
 
-    override fun close() = onClose.close()
-
     fun registerOnColorHintsChangedListener(listener: OnColorHintListener) {
         onColorHintsChangedListeners.add(listener)
     }
@@ -82,7 +86,7 @@
     companion object {
         @VisibleForTesting
         @JvmField
-        val INSTANCE = MainThreadInitializedObject { WallpaperColorHints(it) }
+        val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getWallpaperColorHints)
 
         @JvmStatic fun get(context: Context): WallpaperColorHints = INSTANCE.get(context)
     }
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
index bb4f040..abb0081 100644
--- a/src/com/android/launcher3/views/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -73,7 +73,7 @@
                 }
             };
 
-    private final ActivityContext mActivityContext;
+    protected final ActivityContext mActivityContext;
     private final Handler mHandler = new Handler();
     private boolean mIsPointingUp;
     private Runnable mOnClosed;
@@ -103,16 +103,26 @@
                 R.dimen.arrow_toast_arrow_width);
         mArrowMinOffset = context.getResources().getDimensionPixelSize(
                 R.dimen.dynamic_grid_cell_border_spacing);
-        TypedArray ta = context.obtainStyledAttributes(R.styleable.ArrowTipView);
+        Context localContext = context;
+        TypedArray ta = localContext.obtainStyledAttributes(R.styleable.ArrowTipView);
         // Set style to default to avoid inflation issues with missing attributes.
         if (!ta.hasValue(R.styleable.ArrowTipView_arrowTipBackground)
                 || !ta.hasValue(R.styleable.ArrowTipView_arrowTipTextColor)) {
-            context = new ContextThemeWrapper(context, R.style.ArrowTipStyle);
+            localContext = new ContextThemeWrapper(localContext, R.style.ArrowTipStyle);
         }
-        mArrowViewPaintColor = ta.getColor(R.styleable.ArrowTipView_arrowTipBackground,
+        mArrowViewPaintColor = applyArrowPaintColor(ta, localContext);
+        init(localContext, layoutId);
+    }
+
+    protected int applyArrowPaintColor(TypedArray typedArray, Context context) {
+        int arrowPaintColor = typedArray.getColor(R.styleable.ArrowTipView_arrowTipBackground,
                 context.getColor(R.color.arrow_tip_view_bg));
-        ta.recycle();
-        init(context, layoutId);
+        typedArray.recycle();
+        return arrowPaintColor;
+    }
+
+    protected int getArrowId() {
+        return R.id.arrow;
     }
 
     @Override
@@ -154,7 +164,7 @@
         inflate(context, layoutId, this);
         setOrientation(LinearLayout.VERTICAL);
 
-        mArrowView = findViewById(R.id.arrow);
+        mArrowView = findViewById(getArrowId());
         updateArrowTipInView(mIsPointingUp);
         setAlpha(0);
 
@@ -343,6 +353,34 @@
             parent.addView(this);
             requestLayout();
         }
+        return showAtLocation(arrowXCoord, yCoordDownPointingTip, yCoordUpPointingTip,
+                minViewMargin, parentViewWidth, parentViewHeight, shouldAutoClose);
+    }
+
+    /**
+     * Show the ArrowTipView (tooltip) custom aligned. The tooltip is vertically flipped if it
+     * cannot fit on screen in the requested orientation.
+     *
+     * @param arrowXCoord The X coordinate for the arrow on the tooltip. The arrow is usually in the
+     *                    center of tooltip unless the tooltip goes beyond screen margin.
+     * @param yCoordDownPointingTip The Y coordinate of the pointed tip end of the tooltip when the
+     *                              tooltip is placed pointing downwards.
+     * @param yCoordUpPointingTip The Y coordinate of the pointed tip end of the tooltip when the
+     *                            tooltip is placed pointing upwards.
+     * @param minViewMargin The view margin in pixels from the tip end to the y coordinate.
+     * @param parentViewWidth The width in pixels of the parent view.
+     * @param parentViewHeight The height in pixels of the parent view.
+     * @param shouldAutoClose If Tooltip should be auto close.
+     * @return The tool tip view. {@code null} if the tip can not be shown.
+     */
+    protected ArrowTipView showAtLocation(
+            @Px int arrowXCoord,
+            @Px int yCoordDownPointingTip,
+            @Px int yCoordUpPointingTip,
+            @Px int minViewMargin,
+            @Px int parentViewWidth,
+            @Px int parentViewHeight,
+            boolean shouldAutoClose) {
 
         post(() -> {
             // Adjust the tooltip horizontally.
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index d850fc6..ab0f9a7 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -577,14 +577,11 @@
     public void exitSearchMode() {
         if (!mIsInSearchMode) return;
         onSearchResults(new ArrayList<>());
-        WidgetsRecyclerView searchRecyclerView = mAdapters.get(
-                AdapterHolder.SEARCH).mWidgetsRecyclerView;
         // Remove all views when exiting the search mode; this prevents animating from stale results
         // to new ones the next time we enter search mode. By the time recycler view is hidden,
         // layout may not have happened to clear up existing results. So, instead of waiting for it
         // to happen, we clear the views here.
-        searchRecyclerView.swapAdapter(
-                searchRecyclerView.getAdapter(), /*removeAndRecycleExistingViews=*/ true);
+        mAdapters.get(AdapterHolder.SEARCH).reset();
         setViewVisibilityBasedOnSearch(/*isInSearchMode=*/ false);
         if (mHasWorkProfile) {
             mViewPager.snapToPage(AdapterHolder.PRIMARY);
@@ -613,13 +610,12 @@
             mNoWidgetsView.setVisibility(GONE);
         } else {
             mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.setVisibility(GONE);
-            mAdapters.get(getCurrentAdapterHolderType()).mWidgetsRecyclerView.setVisibility(
-                    VISIBLE);
-            if (mRecommendedWidgetsCount > 0) {
-                // Display recommendations immediately, if present, so that other parts of sticky
-                // header (e.g. personal / work tabs) don't flash in interim.
-                mWidgetRecommendationsContainer.setVisibility(VISIBLE);
-            }
+            AdapterHolder currentAdapterHolder = mAdapters.get(getCurrentAdapterHolderType());
+            // Remove all views when exiting the search mode; this prevents animating / flashing old
+            // list position / state.
+            currentAdapterHolder.reset();
+            currentAdapterHolder.mWidgetsRecyclerView.setVisibility(VISIBLE);
+            post(this::onRecommendedWidgetsBound);
             // Visibility of recycler views and headers are handled in methods below.
             onWidgetsBound();
         }
@@ -1126,6 +1122,21 @@
             mWidgetsListItemAnimator = new WidgetsListItemAnimator();
         }
 
+        /**
+         * Swaps the adapter to existing adapter to prevent the recycler view from using stale view
+         * to animate in the new visibility update.
+         *
+         * <p>For instance, when clearing search text and re-entering search with new list shouldn't
+         * use stale results to animate in new results. Alternative is setting list animators to
+         * null, but, we need animations with the default item animator.
+         */
+        private void reset() {
+            mWidgetsRecyclerView.swapAdapter(
+                    mWidgetsListAdapter,
+                    /*removeAndRecycleExistingViews=*/ true
+            );
+        }
+
         private int getEmptySpaceHeight() {
             return mStickyHeaderLayout != null ? mStickyHeaderLayout.getHeaderHeight() : 0;
         }
diff --git a/src_no_quickstep/com/android/launcher3/dagger/Modules.kt b/src_no_quickstep/com/android/launcher3/dagger/Modules.kt
index dab33a0..c3bf7c5 100644
--- a/src_no_quickstep/com/android/launcher3/dagger/Modules.kt
+++ b/src_no_quickstep/com/android/launcher3/dagger/Modules.kt
@@ -25,3 +25,8 @@
 @Module abstract class ApiWrapperModule {}
 
 @Module abstract class PluginManagerWrapperModule {}
+
+@Module object StaticObjectModule {}
+
+// Module containing bindings for the final derivative app
+@Module abstract class AppModule {}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 5cf96c8..862b862 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -29,6 +29,8 @@
         android:functionalTest="false"
         android:handleProfiling="false"
         android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.launcher3" >
+      android:targetPackage="com.android.launcher3" >
+      <meta-data android:name="listener"
+      android:value="com.android.launcher3.util.GlobalTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/tests/assets/databases/GridMigrationTest/result5x5to5x8.db b/tests/assets/databases/GridMigrationTest/result5x5to5x8.db
index 311a112..d750774 100644
--- a/tests/assets/databases/GridMigrationTest/result5x5to5x8.db
+++ b/tests/assets/databases/GridMigrationTest/result5x5to5x8.db
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/result5x5to5x8WithShift.db b/tests/assets/databases/GridMigrationTest/result5x5to5x8WithShift.db
deleted file mode 100644
index d750774..0000000
--- a/tests/assets/databases/GridMigrationTest/result5x5to5x8WithShift.db
+++ /dev/null
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/test_launcher_2.db b/tests/assets/databases/GridMigrationTest/test_launcher_2.db
deleted file mode 100644
index b538e26..0000000
--- a/tests/assets/databases/GridMigrationTest/test_launcher_2.db
+++ /dev/null
Binary files differ
diff --git a/tests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt b/tests/multivalentTests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
similarity index 83%
rename from tests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
index cf03adc..7ddd859 100644
--- a/tests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
@@ -20,28 +20,33 @@
 import android.view.ViewGroup.MarginLayoutParams
 import android.widget.ImageView
 import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.launcher3.util.ActivityContextWrapper
 import com.google.common.truth.Truth
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
 
+@RunWith(AndroidJUnit4::class)
 class FloatingMaskViewTest {
-    @Mock
-    private val mockAllAppsRecyclerView: AllAppsRecyclerView? = null
+    @Mock private val mockAllAppsRecyclerView: AllAppsRecyclerView? = null
 
-    @Mock
-    private val mockBottomBox: ImageView? = null
+    @Mock private val mockBottomBox: ImageView? = null
     private var mVut: FloatingMaskView? = null
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         val context: Context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
         mVut = FloatingMaskView(context)
-        mVut!!.layoutParams = MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT)
+        mVut!!.layoutParams =
+            MarginLayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+            )
     }
 
     @Test
diff --git a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java b/tests/multivalentTests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
similarity index 99%
rename from tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
rename to tests/multivalentTests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
index 430e496..d757d10 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
@@ -42,7 +42,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.pm.UserCache;
diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java b/tests/multivalentTests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
similarity index 98%
rename from tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
rename to tests/multivalentTests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
index 398f9c5..23b00c2 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
@@ -52,8 +52,8 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.R;
 import com.android.launcher3.logging.StatsLogManager;
@@ -300,7 +300,7 @@
                 && info.user.equals(MAIN_HANDLE));
 
         int rows = (int) (ALL_APPS_HEIGHT - PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT);
-        int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+        int position = rows * NUM_APP_COLS - (NUM_APP_COLS - 1) + 1;
 
         assertEquals(NUM_PRIVATE_SPACE_APPS + MAIN_USER_APP_COUNT
                         + CONTAINER_HEADER_ELEMENT_COUNT + VIEW_AT_END_OF_APP_LIST,
@@ -335,7 +335,7 @@
                 && info.user.equals(MAIN_HANDLE));
 
         int rows = (int) (ALL_APPS_HEIGHT - PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT) - 1;
-        int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+        int position = rows * NUM_APP_COLS - (NUM_APP_COLS - 1) + 1;
 
         assertEquals(NUM_PRIVATE_SPACE_APPS + MAIN_USER_APP_COUNT
                         + CONTAINER_HEADER_ELEMENT_COUNT + VIEW_AT_END_OF_APP_LIST,
@@ -370,7 +370,7 @@
                 && info.user.equals(MAIN_HANDLE));
 
         int rows = (int) (ALL_APPS_HEIGHT - BIGGER_PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT);
-        int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+        int position = rows * NUM_APP_COLS - (NUM_APP_COLS - 1) + 1;
 
         assertEquals(NUM_PRIVATE_SPACE_APPS + MAIN_USER_APP_COUNT
                         + CONTAINER_HEADER_ELEMENT_COUNT + VIEW_AT_END_OF_APP_LIST,
diff --git a/tests/src/com/android/launcher3/allapps/WorkUtilityViewTest.java b/tests/multivalentTests/src/com/android/launcher3/allapps/WorkUtilityViewTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/allapps/WorkUtilityViewTest.java
rename to tests/multivalentTests/src/com/android/launcher3/allapps/WorkUtilityViewTest.java
diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
index 553d08c..15accbd 100644
--- a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
@@ -17,11 +17,14 @@
 package com.android.launcher3.folder
 
 import android.R
-import android.graphics.Bitmap
 import android.os.Process
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.graphics.PreloadIconDrawable
 import com.android.launcher3.graphics.ThemeManager
 import com.android.launcher3.icons.BitmapInfo
@@ -30,13 +33,14 @@
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver
 import com.android.launcher3.icons.PlaceHolderIconDrawable
 import com.android.launcher3.icons.UserBadgeDrawable
-import com.android.launcher3.icons.mono.MonoThemedBitmap
 import com.android.launcher3.model.data.FolderInfo
 import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED
 import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE
 import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.AllModulesForTest
 import com.android.launcher3.util.Executors
+import com.android.launcher3.util.FakePrefsModule
 import com.android.launcher3.util.FlagOp
 import com.android.launcher3.util.LauncherLayoutBuilder
 import com.android.launcher3.util.LauncherModelHelper
@@ -44,10 +48,19 @@
 import com.android.launcher3.util.TestUtil
 import com.android.launcher3.util.UserIconInfo
 import com.google.common.truth.Truth.assertThat
+import dagger.Component
+import kotlin.annotation.AnnotationRetention.RUNTIME
+import kotlin.annotation.AnnotationTarget.FUNCTION
+import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER
+import kotlin.annotation.AnnotationTarget.PROPERTY_SETTER
 import org.junit.After
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
+import org.junit.rules.TestRule
+import org.junit.runner.Description
 import org.junit.runner.RunWith
+import org.junit.runners.model.Statement
 import org.mockito.kotlin.any
 import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.doReturn
@@ -61,6 +74,8 @@
 @RunWith(AndroidJUnit4::class)
 class PreviewItemManagerTest {
 
+    @get:Rule val theseStateRule = ThemeStateRule()
+
     private lateinit var previewItemManager: PreviewItemManager
     private lateinit var context: SandboxModelContext
     private lateinit var folderItems: ArrayList<WorkspaceItemInfo>
@@ -68,15 +83,14 @@
     private lateinit var folderIcon: FolderIcon
     private lateinit var iconCache: IconCache
 
-    private var defaultThemedIcons = false
-
-    private val themeManager: ThemeManager
-        get() = ThemeManager.INSTANCE.get(context)
-
     @Before
     fun setup() {
         modelHelper = LauncherModelHelper()
         context = modelHelper.sandboxContext
+        context.initDaggerComponent(DaggerPreviewItemManagerTestComponent.builder())
+        theseStateRule.themeState?.let {
+            LauncherPrefs.get(context).putSync(ThemeManager.THEMED_ICONS.to(it))
+        }
         folderIcon = FolderIcon(ActivityContextWrapper(context))
 
         val app = spy(LauncherAppState.getInstance(context))
@@ -99,27 +113,16 @@
             )
             .loadModelSync()
 
+        folderIcon.mInfo =
+            modelHelper.bgDataModel.itemsIdMap.find { it.itemType == ITEM_TYPE_FOLDER }
+                as FolderInfo
         // Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps
-        folderItems = modelHelper.bgDataModel.collections.valueAt(0).getAppContents()
-        folderIcon.mInfo = modelHelper.bgDataModel.collections.valueAt(0) as FolderInfo
-        folderIcon.mInfo.getContents().addAll(folderItems)
-
-        // Set first icon to be themed.
-        folderItems[0].bitmap.themedBitmap =
-            MonoThemedBitmap(
-                folderItems[0].bitmap.icon,
-                Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
-            )
+        folderItems = folderIcon.mInfo.getAppContents()
 
         // Set second icon to be non-themed.
         folderItems[1].bitmap.themedBitmap = null
 
         // Set third icon to be themed with badge.
-        folderItems[2].bitmap.themedBitmap =
-            MonoThemedBitmap(
-                folderItems[2].bitmap.icon,
-                Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
-            )
         folderItems[2].bitmap =
             folderItems[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
 
@@ -127,20 +130,17 @@
         folderItems[3].bitmap =
             folderItems[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
         folderItems[3].bitmap.themedBitmap = null
-
-        defaultThemedIcons = themeManager.isMonoThemeEnabled
     }
 
     @After
     @Throws(Exception::class)
     fun tearDown() {
-        themeManager.isMonoThemeEnabled = defaultThemedIcons
         modelHelper.destroy()
     }
 
     @Test
+    @MonoThemeEnabled(true)
     fun checkThemedIconWithThemingOn_iconShouldBeThemed() {
-        themeManager.isMonoThemeEnabled = true
         val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
 
         previewItemManager.setDrawable(drawingParams, folderItems[0])
@@ -149,8 +149,8 @@
     }
 
     @Test
+    @MonoThemeEnabled(false)
     fun checkThemedIconWithThemingOff_iconShouldNotBeThemed() {
-        themeManager.isMonoThemeEnabled = false
         val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
 
         previewItemManager.setDrawable(drawingParams, folderItems[0])
@@ -159,8 +159,8 @@
     }
 
     @Test
+    @MonoThemeEnabled(true)
     fun checkUnthemedIconWithThemingOn_iconShouldNotBeThemed() {
-        themeManager.isMonoThemeEnabled = true
         val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
 
         previewItemManager.setDrawable(drawingParams, folderItems[1])
@@ -169,8 +169,8 @@
     }
 
     @Test
+    @MonoThemeEnabled(false)
     fun checkUnthemedIconWithThemingOff_iconShouldNotBeThemed() {
-        themeManager.isMonoThemeEnabled = false
         val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
 
         previewItemManager.setDrawable(drawingParams, folderItems[1])
@@ -179,8 +179,8 @@
     }
 
     @Test
+    @MonoThemeEnabled(true)
     fun checkThemedIconWithBadgeWithThemingOn_iconAndBadgeShouldBeThemed() {
-        themeManager.isMonoThemeEnabled = true
         val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
 
         previewItemManager.setDrawable(drawingParams, folderItems[2])
@@ -192,8 +192,8 @@
     }
 
     @Test
+    @MonoThemeEnabled(true)
     fun checkUnthemedIconWithBadgeWithThemingOn_badgeShouldBeThemed() {
-        themeManager.isMonoThemeEnabled = true
         val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
 
         previewItemManager.setDrawable(drawingParams, folderItems[3])
@@ -205,8 +205,8 @@
     }
 
     @Test
+    @MonoThemeEnabled(false)
     fun checkUnthemedIconWithBadgeWithThemingOff_iconAndBadgeShouldNotBeThemed() {
-        themeManager.isMonoThemeEnabled = false
         val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
 
         previewItemManager.setDrawable(drawingParams, folderItems[3])
@@ -278,3 +278,28 @@
     private fun profileFlagOp(type: Int) =
         UserIconInfo(Process.myUserHandle(), type).applyBitmapInfoFlags(FlagOp.NO_OP)
 }
+
+class ThemeStateRule : TestRule {
+
+    var themeState: Boolean? = null
+
+    override fun apply(base: Statement, description: Description): Statement {
+        themeState = description.getAnnotation(MonoThemeEnabled::class.java)?.value
+        return base
+    }
+}
+
+// Annotation for tests that need to be run with quickstep enabled and disabled.
+@Retention(RUNTIME)
+@Target(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
+annotation class MonoThemeEnabled(val value: Boolean = false)
+
+@LauncherAppSingleton
+@Component(modules = [AllModulesForTest::class, FakePrefsModule::class])
+interface PreviewItemManagerTestComponent : LauncherAppComponent {
+
+    @Component.Builder
+    interface Builder : LauncherAppComponent.Builder {
+        override fun build(): PreviewItemManagerTestComponent
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
index 4af564e..12c14fb 100644
--- a/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
@@ -16,9 +16,11 @@
 
 package com.android.launcher3.icons.mono
 
+import android.content.ComponentName
 import android.graphics.Color
 import android.graphics.drawable.AdaptiveIconDrawable
 import android.graphics.drawable.ColorDrawable
+import android.os.Process
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
@@ -29,6 +31,9 @@
 import com.android.launcher3.Flags
 import com.android.launcher3.icons.BaseIconFactory
 import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.SourceHint
+import com.android.launcher3.icons.cache.LauncherActivityCachingLogic
+import com.android.launcher3.util.ComponentKey
 import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertNull
@@ -45,6 +50,12 @@
 
     private val iconFactory = BaseIconFactory(context, DisplayMetrics.DENSITY_MEDIUM, 30)
 
+    private val sourceHint =
+        SourceHint(
+            key = ComponentKey(ComponentName("a", "a"), Process.myUserHandle()),
+            logic = LauncherActivityCachingLogic,
+        )
+
     @Test
     fun `createThemedBitmap when mono drawable is present`() {
         val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
@@ -81,7 +92,8 @@
         val themeBitmap =
             MonoIconThemeController().createThemedBitmap(icon, iconInfo, iconFactory)!!
         assertNotNull(
-            MonoIconThemeController().decode(themeBitmap.serialize(), iconInfo, iconFactory)
+            MonoIconThemeController()
+                .decode(themeBitmap.serialize(), iconInfo, iconFactory, sourceHint)
         )
     }
 
@@ -90,7 +102,10 @@
         ensureBitmapSerializationSupported()
         val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
         val iconInfo = iconFactory.createBadgedIconBitmap(icon)
-        assertNull(MonoIconThemeController().decode(byteArrayOf(1, 1, 1, 1), iconInfo, iconFactory))
+        assertNull(
+            MonoIconThemeController()
+                .decode(byteArrayOf(1, 1, 1, 1), iconInfo, iconFactory, sourceHint)
+        )
     }
 
     @Test
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
index 1e2431f..0ae4d00 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
 import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
 import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
 
@@ -42,6 +44,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+
 /**
  * Tests for layout parser for remote layout
  */
@@ -63,14 +67,23 @@
         mModelHelper.destroy();
     }
 
+    private List<ItemInfo> getWorkspaceItems() {
+        return mModelHelper
+                .getBgDataModel()
+                .itemsIdMap
+                .stream()
+                .filter(i -> i.container == CONTAINER_DESKTOP || i.container == CONTAINER_HOTSEAT)
+                .toList();
+    }
+
     @Test
     public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception {
         writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0)
                 .putApp(TEST_PACKAGE, TEST_ACTIVITY));
 
         // Verify one item in hotseat
-        assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
-        ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+        assertEquals(1, getWorkspaceItems().size());
+        ItemInfo info = getWorkspaceItems().get(0);
         assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container);
         assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPLICATION, info.itemType);
     }
@@ -84,8 +97,8 @@
                 .build());
 
         // Verify folder
-        assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
-        ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+        assertEquals(1, getWorkspaceItems().size());
+        ItemInfo info = getWorkspaceItems().get(0);
         assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
         assertEquals(3, ((FolderInfo) info).getContents().size());
     }
@@ -99,8 +112,8 @@
                 .build());
 
         // Verify folder
-        assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
-        ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+        assertEquals(1, getWorkspaceItems().size());
+        ItemInfo info = getWorkspaceItems().get(0);
         assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
         assertEquals(3, ((FolderInfo) info).getContents().size());
         assertEquals("CustomFolder", info.title.toString());
@@ -124,8 +137,8 @@
                 .putWidget(pendingAppPkg, "PlaceholderWidget", 2, 2));
 
         // Verify widget
-        assertEquals(1, mModelHelper.getBgDataModel().appWidgets.size());
-        ItemInfo info = mModelHelper.getBgDataModel().appWidgets.get(0);
+        assertEquals(1, getWorkspaceItems().size());
+        ItemInfo info = getWorkspaceItems().get(0);
         assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET, info.itemType);
         assertEquals(2, info.spanX);
         assertEquals(2, info.spanY);
@@ -138,8 +151,8 @@
                 .putShortcut(TEST_PACKAGE, "shortcut2"));
 
         // Verify one item in hotseat
-        assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
-        ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+        assertEquals(1, getWorkspaceItems().size());
+        ItemInfo info = getWorkspaceItems().get(0);
         assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container);
         assertEquals(LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT, info.itemType);
     }
@@ -154,8 +167,8 @@
                 .build());
 
         // Verify folder
-        assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
-        FolderInfo info = (FolderInfo) mModelHelper.getBgDataModel().workspaceItems.get(0);
+        assertEquals(1, getWorkspaceItems().size());
+        FolderInfo info = (FolderInfo) getWorkspaceItems().get(0);
         assertEquals(3, info.getContents().size());
 
         // Verify last icon
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
index e8f778f..f357487 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
@@ -18,8 +18,10 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
 import com.android.launcher3.icons.BitmapInfo
 import com.android.launcher3.icons.waitForUpdateHandlerToFinish
+import com.android.launcher3.model.data.FolderInfo
 import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.util.Executors
 import com.android.launcher3.util.LauncherLayoutBuilder
@@ -149,11 +151,13 @@
         // Reload again with correct icon state
         app.model.forceReload()
         modelHelper.loadModelSync()
-        val collections = modelHelper.getBgDataModel().collections
-
-        assertThat(collections.size()).isEqualTo(1)
-        assertThat(collections.valueAt(0).getAppContents().size).isEqualTo(itemCount)
-        return collections.valueAt(0).getAppContents()
+        val collections =
+            modelHelper.bgDataModel.itemsIdMap
+                .filter { it.itemType == ITEM_TYPE_FOLDER }
+                .map { it as FolderInfo }
+        assertThat(collections.size).isEqualTo(1)
+        assertThat(collections[0].getAppContents().size).isEqualTo(itemCount)
+        return collections[0].getAppContents()
     }
 
     private fun verifyHighRes(items: ArrayList<WorkspaceItemInfo>, vararg indices: Int) {
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
index d699eee..da87dfc 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
@@ -494,8 +494,7 @@
     @Test
     fun `When processing Folder then create FolderInfo and mark restored`() {
         val actualFolderInfo = FolderInfo()
-        mockBgDataModel =
-            mock<BgDataModel>().apply { whenever(findOrMakeFolder(1)).thenReturn(actualFolderInfo) }
+        mockBgDataModel = mock<BgDataModel>()
         mockCursor =
             mock<LoaderCursor>().apply {
                 user = UserHandle(0)
@@ -509,6 +508,7 @@
                 whenever(getColumnIndex(Favorites.TITLE)).thenReturn(4)
                 whenever(getString(4)).thenReturn("title")
                 whenever(options).thenReturn(5)
+                whenever(findOrMakeFolder(eq(1), any())).thenReturn(actualFolderInfo)
             }
         val expectedFolderInfo =
             FolderInfo().apply {
@@ -600,7 +600,8 @@
 
         // Then
         val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java)
-        verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel))
+        verify(mockCursor)
+            .checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel), anyOrNull())
         val actualWidgetInfo = widgetInfoCaptor.value
         with(actualWidgetInfo) {
             assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName)
@@ -655,7 +656,7 @@
         itemProcessorUnderTest.processItem()
 
         // Then
-        verify(mockCursor).checkAndAddItem(any(), any())
+        verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
     }
 
     @Test
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt b/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt
index 68da9ff..b66a9d3 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt
@@ -19,6 +19,8 @@
 import com.android.launcher3.FakeLauncherPrefs
 import com.android.launcher3.LauncherPrefs
 import com.android.launcher3.dagger.ApiWrapperModule
+import com.android.launcher3.dagger.AppModule
+import com.android.launcher3.dagger.StaticObjectModule
 import com.android.launcher3.dagger.WindowManagerProxyModule
 import dagger.Binds
 import dagger.Module
@@ -31,11 +33,21 @@
 }
 
 /** All modules. We also exclude the plugin module from tests */
-@Module(includes = [ApiWrapperModule::class, WindowManagerProxyModule::class])
+@Module(
+    includes =
+        [
+            ApiWrapperModule::class,
+            WindowManagerProxyModule::class,
+            StaticObjectModule::class,
+            AppModule::class,
+        ]
+)
 class AllModulesForTest
 
 /** All modules except the WMProxy */
-@Module(includes = [ApiWrapperModule::class]) class AllModulesMinusWMProxy
+@Module(includes = [ApiWrapperModule::class, StaticObjectModule::class, AppModule::class])
+class AllModulesMinusWMProxy
 
 /** All modules except the ApiWrapper */
-@Module(includes = [WindowManagerProxyModule::class]) class AllModulesMinusApiWrapper
+@Module(includes = [WindowManagerProxyModule::class, StaticObjectModule::class, AppModule::class])
+class AllModulesMinusApiWrapper
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/GlobalTestRunListener.java b/tests/multivalentTests/src/com/android/launcher3/util/GlobalTestRunListener.java
new file mode 100644
index 0000000..667f540
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/GlobalTestRunListener.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import org.junit.runner.Description;
+import org.junit.runner.notification.RunListener;
+import org.mockito.Mockito;
+
+public class GlobalTestRunListener extends RunListener {
+    /**
+     * See {@link RunListener#testFinished} which executes per atomic test.
+     * {@link RunListener#testSuiteFinished} which executes per test suite. Test suite = test class
+     * in this context.
+     * {@link RunListener#testRunFinished} which executes after everything (all test suites) is
+     * completed.
+     */
+    @Override
+    public void testSuiteFinished(Description description) throws Exception {
+        // This method runs after every test class and will clear mocks after every test class
+        // execution is completed.
+        Mockito.framework().clearInlineMocks();
+        super.testSuiteFinished(description);
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt
index 2711d7a..99f5a5b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt
@@ -22,7 +22,10 @@
 import android.os.UserManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
 import com.google.common.truth.Truth.assertThat
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,17 +41,24 @@
 
     private val userManager: UserManager = mock()
     private val context: Context = mock()
+    private val lifeCycle: DaggerSingletonTracker = mock()
 
     @Before
     fun setup() {
         whenever(context.getSystemService(UserManager::class.java)).thenReturn(userManager)
     }
 
+    @After
+    fun tearDown() {
+        UI_HELPER_EXECUTOR.submit {}.get()
+        MAIN_EXECUTOR.submit {}.get()
+    }
+
     @Test
     fun runOnUserUnlocked_runs_action_immediately_if_already_unlocked() {
         whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
         val action: Runnable = mock()
-        LockedUserState(context).runOnUserUnlocked(action)
+        LockedUserState(context, lifeCycle).runOnUserUnlocked(action)
         verify(action).run()
     }
 
@@ -56,23 +66,23 @@
     fun runOnUserUnlocked_waits_to_run_action_until_user_is_unlocked() {
         whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
         val action: Runnable = mock()
-        val state = LockedUserState(context)
+        val state = LockedUserState(context, lifeCycle)
         state.runOnUserUnlocked(action)
         // b/343530737
         verifyNoMoreInteractions(action)
-        state.mUserUnlockedReceiver.onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED))
+        state.userUnlockedReceiver.onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED))
         verify(action).run()
     }
 
     @Test
     fun isUserUnlocked_returns_true_when_user_is_unlocked() {
         whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
-        assertThat(LockedUserState(context).isUserUnlocked).isTrue()
+        assertThat(LockedUserState(context, lifeCycle).isUserUnlocked).isTrue()
     }
 
     @Test
     fun isUserUnlocked_returns_false_when_user_is_locked() {
         whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
-        assertThat(LockedUserState(context).isUserUnlocked).isFalse()
+        assertThat(LockedUserState(context, lifeCycle).isUserUnlocked).isFalse()
     }
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
index efe7637..0da8891 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
@@ -48,6 +48,7 @@
 class SandboxApplication private constructor(private val base: SandboxApplicationWrapper) :
     SandboxModelContext(base), TestRule {
 
+    @JvmOverloads
     constructor(
         base: Context = ApplicationProvider.getApplicationContext()
     ) : this(SandboxApplicationWrapper(base))
diff --git a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
index 38fad6b..1e54603 100644
--- a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
+++ b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
@@ -32,6 +32,7 @@
 import com.android.launcher3.util.rule.BackAndRestoreRule
 import com.android.launcher3.util.rule.setFlags
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -69,6 +70,7 @@
     }
 
     @Test
+    @Ignore("b/385147987")
     fun oldDatabasesNotPresentAfterRestore() {
         val dbController = ModelDbController(getInstrumentation().targetContext)
         if (Flags.gridMigrationRefactor()) {
diff --git a/tests/src/com/android/launcher3/model/GridMigrationTest.kt b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
index 8bd0c60..380c208 100644
--- a/tests/src/com/android/launcher3/model/GridMigrationTest.kt
+++ b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
@@ -82,8 +82,6 @@
 @RunWith(AndroidJUnit4::class)
 class GridMigrationTest {
     private val DB_FILE = "test_launcher.db"
-    // This DB is used for testing the heuristic where we add an extra row at the bottom.
-    private val DB_FILE_NO_SHIFT = "test_launcher_2.db"
 
     @JvmField
     @Rule
@@ -230,42 +228,6 @@
 
     @JvmField
     @Rule
-    val result5x5to5x8WithShift =
-        TestToPhoneFileCopier(
-            src = "databases/GridMigrationTest/result5x5to5x8WithShift.db",
-            dest = "databases/result5x5to5x8WithShift.db",
-            removeOnFinish = true,
-        )
-
-    @Test
-    fun `5x5 to 5x8 with cells shifting down`() =
-        runTest(
-            src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)),
-            dst =
-                GridMigrationData(
-                    null, // in memory db, to download a new db change null
-                    // for
-                    // the filename of the db name to store it. Do not use existing names.
-                    DeviceGridState(5, 8, 5, TYPE_PHONE, ""),
-                ),
-            target =
-                GridMigrationData(
-                    "result5x5to5x8WithShift.db",
-                    DeviceGridState(5, 8, 5, TYPE_PHONE, ""),
-                ),
-        )
-
-    @JvmField
-    @Rule
-    val fileCopierNoShift =
-        TestToPhoneFileCopier(
-            src = "databases/GridMigrationTest/$DB_FILE_NO_SHIFT",
-            dest = "databases/$DB_FILE_NO_SHIFT",
-            removeOnFinish = true,
-        )
-
-    @JvmField
-    @Rule
     val result5x5to5x8 =
         TestToPhoneFileCopier(
             src = "databases/GridMigrationTest/result5x5to5x8.db",
@@ -274,16 +236,13 @@
         )
 
     @Test
-    fun `5x5 to 5x8 without cell shift`() =
+    fun `5x5 to 5x8`() =
         runTest(
-            src =
-                GridMigrationData(
-                    DB_FILE_NO_SHIFT,
-                    DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE_NO_SHIFT),
-                ),
+            src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)),
             dst =
                 GridMigrationData(
-                    null, // in memory db, to download a new db change null for
+                    null, // in memory db, to download a new db change null
+                    // for
                     // the filename of the db name to store it. Do not use existing names.
                     DeviceGridState(5, 8, 5, TYPE_PHONE, ""),
                 ),
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index d2229c4..f04688d 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -19,6 +19,10 @@
 import com.android.launcher3.LauncherPrefs
 import com.android.launcher3.LauncherPrefs.Companion.IS_FIRST_LOAD_AFTER_RESTORE
 import com.android.launcher3.LauncherPrefs.Companion.RESTORE_DEVICE
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
 import com.android.launcher3.icons.IconCache
 import com.android.launcher3.icons.cache.CachingLogic
 import com.android.launcher3.icons.cache.IconCacheUpdateHandler
@@ -155,9 +159,24 @@
                     widgetsFilterDataProvider,
                 )
                 .runSyncOnBackgroundThread()
-            Truth.assertThat(workspaceItems.size).isAtLeast(25)
-            Truth.assertThat(appWidgets.size).isAtLeast(7)
-            Truth.assertThat(collections.size()).isAtLeast(8)
+            Truth.assertThat(
+                    itemsIdMap
+                        .filter {
+                            it.container == CONTAINER_DESKTOP || it.container == CONTAINER_HOTSEAT
+                        }
+                        .size
+                )
+                .isAtLeast(32)
+            Truth.assertThat(itemsIdMap.filter { ModelUtils.WIDGET_FILTER.test(it) }.size)
+                .isAtLeast(7)
+            Truth.assertThat(
+                    itemsIdMap
+                        .filter {
+                            it.itemType == ITEM_TYPE_FOLDER || it.itemType == ITEM_TYPE_APP_PAIR
+                        }
+                        .size
+                )
+                .isAtLeast(8)
             Truth.assertThat(itemsIdMap.size()).isAtLeast(40)
             Truth.assertThat(widgetsModel.defaultWidgetsFilter).isNotNull()
         }
diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
index d553f47..8db049c 100644
--- a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
+++ b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
@@ -58,6 +58,7 @@
 import org.mockito.Mockito.RETURNS_DEEP_STUBS
 import org.mockito.Mockito.verify
 import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
 import org.mockito.kotlin.doAnswer
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
@@ -220,7 +221,8 @@
             )
             .commit()
         val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java)
-        verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel))
+        verify(mockCursor)
+            .checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel), anyOrNull())
         val actualWidgetInfo = widgetInfoCaptor.value
         with(actualWidgetInfo) {
             assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName)
@@ -271,7 +273,7 @@
         itemProcessorUnderTest.processItem()
 
         // Then
-        verify(mockCursor).checkAndAddItem(any(), any())
+        verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
     }
 
     private fun createWorkspaceItemProcessorUnderTest(
diff --git a/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt b/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
index 94b2c7e..707c2c1 100644
--- a/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
+++ b/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
@@ -52,7 +52,7 @@
     private val sandboxContext = spy(launcherModelHelper.sandboxContext)
     private val packageManager = sandboxContext.packageManager
     private val expectedAppPackage = "expectedAppPackage"
-    private val expectedInstallerPackage = "expectedInstallerPackage"
+    private val expectedInstallerPackage = sandboxContext.packageName
     private val mockPackageInstaller: PackageInstaller = mock()
 
     private lateinit var installSessionHelper: InstallSessionHelper
@@ -61,7 +61,6 @@
     @Before
     fun setup() {
         whenever(packageManager.packageInstaller).thenReturn(mockPackageInstaller)
-        whenever(sandboxContext.packageName).thenReturn(expectedInstallerPackage)
         launcherApps = sandboxContext.spyService(LauncherApps::class.java)
         installSessionHelper = InstallSessionHelper(sandboxContext)
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
index bbcc6a8..033cfb0 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
@@ -63,5 +63,12 @@
         return new SplitScreenMenuItem(mLauncher, menuItem);
     }
 
+    /** Returns the Bubble menu item. */
+    public BubbleMenuItem getBubbleMenuItem() {
+        final UiObject2 menuItem = mLauncher.waitForObjectInContainer(mDeepShortcutsContainer,
+                AppIcon.getMenuItemSelector("Bubble", mLauncher));
+        return new BubbleMenuItem(mLauncher, menuItem);
+    }
+
     protected abstract AppIconMenuItem createMenuItem(UiObject2 menuItem);
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index b15afc1..ef72a0f 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -440,7 +440,7 @@
                     "Not expecting an actions bar: device is tablet and task is not centered");
             return false;
         }
-        if (task.isGrouped() && (!mLauncher.isAppPairsEnabled() || !isTablet)) {
+        if (task.isGrouped() && !isTablet) {
             testLogD(TAG, "Not expecting an actions bar: device is phone and task is split");
             // Overview actions aren't visible for split screen tasks, except for save app pair
             // button on tablets.
diff --git a/tests/tapl/com/android/launcher3/tapl/BubbleMenuItem.kt b/tests/tapl/com/android/launcher3/tapl/BubbleMenuItem.kt
new file mode 100644
index 0000000..77391f1
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/BubbleMenuItem.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.tapl
+
+import androidx.test.uiautomator.UiObject2
+
+/**
+ * A class representing the Bubble menu item in the app long-press menu, which moves the app into a
+ * bubble.
+ */
+class BubbleMenuItem(
+    private val launcher: LauncherInstrumentation,
+    private val uiObject: UiObject2,
+) {
+
+    fun click() {
+        launcher.addContextLayer("want to create bubble from app long-press menu").use {
+            LauncherInstrumentation.log(
+                "clicking on bubble menu item ${uiObject.visibleCenter} in ${
+                    launcher.getVisibleBounds(
+                        uiObject
+                    )
+                }"
+            )
+            launcher.clickLauncherObject(uiObject)
+        }
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 0d9f5ce..edca6dc 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -2015,11 +2015,6 @@
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
-    boolean isAppPairsEnabled() {
-        return getTestInfo(TestProtocol.REQUEST_FLAG_ENABLE_APP_PAIRS).getBoolean(
-                TestProtocol.TEST_INFO_RESPONSE_FIELD);
-    }
-
     public void sendPointer(long downTime, long currentTime, int action, Point point,
             GestureScope gestureScope) {
         sendPointer(downTime, currentTime, action, point, gestureScope,
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 70bfb60..2431ef5 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -157,6 +157,7 @@
             }
 
             boolean taskWasFocused = mLauncher.isTablet()
+                    && !isDesktop()
                     && getVisibleHeight() == mLauncher.getOverviewTaskSize().height();
             List<Integer> originalTasksCenterX =
                     getCurrentTasksCenterXList().stream().sorted().toList();