Merge "Focus on the right pane when choosing an app on left" into main
diff --git a/OWNERS b/OWNERS
index a66bf54..22efa33 100644
--- a/OWNERS
+++ b/OWNERS
@@ -30,6 +30,7 @@
 jeremysim@google.com
 atsjenk@google.com
 brianji@google.com
+hwwang@google.com
 
 # Overview eng team
 alexchau@google.com
@@ -52,4 +53,4 @@
 per-file DeviceConfigWrapper.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com
 
 # Predictive Back
-per-file LauncherBackAnimationController.java = shanh@google.com, gallmann@google.com
\ No newline at end of file
+per-file LauncherBackAnimationController.java = shanh@google.com, gallmann@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index ff97b22..9051ca8 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -5,7 +5,7 @@
 ktfmt = --kotlinlang-style
 
 [Tool Paths]
-ktfmt = ${REPO_ROOT}/prebuilts/build-tools/common/framework/ktfmt.jar
+ktfmt = ${REPO_ROOT}/external/ktfmt/ktfmt.sh
 
 [Hook Scripts]
 checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --config_xml tools/checkstyle.xml --sha ${PREUPLOAD_COMMIT}
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 9147e4c..f1f9966 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -79,6 +79,13 @@
 }
 
 flag {
+    name: "enable_taskbar_customization"
+    namespace: "launcher"
+    description: "Enables taskbar customization framework."
+    bug: "347281365"
+}
+
+flag {
     name: "enable_unfolded_two_pane_picker"
     namespace: "launcher"
     description: "Enables two pane widget picker for unfolded foldables"
@@ -284,3 +291,28 @@
     description: "Enables folders in all apps"
     bug: "341582436"
 }
+
+flag {
+    name: "enable_recents_in_taskbar"
+    namespace: "launcher"
+    description: "Replace hybrid hotseat app predictions with strictly Recent Apps"
+    bug: "315354060"
+}
+
+flag {
+  name: "enable_first_screen_broadcast_archiving_extras"
+  namespace: "launcher"
+  description: "adds Extras to first screen broadcast for archived apps"
+  bug: "322314760"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+    name: "floating_search_bar"
+    namespace: "launcher"
+    description: "Search bar persists at the bottom of the screen across Launcher states"
+    bug: "346408388"
+}
diff --git a/quickstep/res/layout/transient_taskbar.xml b/quickstep/res/layout/transient_taskbar.xml
index 7c55bf8..f3c3383 100644
--- a/quickstep/res/layout/transient_taskbar.xml
+++ b/quickstep/res/layout/transient_taskbar.xml
@@ -45,8 +45,6 @@
         android:layout_gravity="bottom|end"
         android:layout_marginHorizontal="@dimen/transient_taskbar_bottom_margin"
         android:paddingTop="@dimen/bubblebar_pointer_visible_size"
-        android:paddingEnd="@dimen/taskbar_icon_spacing"
-        android:paddingStart="@dimen/taskbar_icon_spacing"
         android:visibility="gone"
         android:gravity="center"
         android:clipChildren="false"
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index f7bf719..73c8129 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Wys nog # app.}other{Wys nog # apps.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Wys # rekenaarapp.}other{Wys # rekenaarapps.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> en <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Borrel"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Oorvloei"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> vanaf <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> en nog <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index 1700823..f9eed39 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{ተጨማሪ # መተግበሪያ አሳይ።}one{ተጨማሪ # መተግበሪያ አሳይ።}other{ተጨማሪ # መተግበሪያዎች አሳይ።}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# የዴስክቶፕ መተግበሪያ አሳይ።}one{# የዴስክቶፕ መተግበሪያ አሳይ።}other{# የዴስክቶፕ መተግበሪያዎች አሳይ።}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"አረፋ"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ትርፍ ፍሰት"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ከ<xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> እና <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> ተጨማሪ"</string>
 </resources>
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index 38e9f70..7a0be9b 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{إظهار تطبيق واحد آخر}zero{إظهار # تطبيق آخر}two{إظهار تطبيقَين آخرَين}few{إظهار # تطبيقات أخرى}many{إظهار # تطبيقًا آخر}other{إظهار # تطبيق آخر}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{عرض تطبيق واحد متوافق مع الكمبيوتر المكتبي}zero{عرض # تطبيق متوافق مع الكمبيوتر المكتبي}two{عرض تطبيقَين متوافقين مع الكمبيوتر المكتبي}few{عرض # تطبيقات متوافقة مع الكمبيوتر المكتبي}many{عرض # تطبيقًا متوافقًا مع الكمبيوتر المكتبي}other{عرض # تطبيق متوافق مع الكمبيوتر المكتبي}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"فقاعة"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"القائمة الكاملة"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"‫\"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>\" من \"<xliff:g id="APP_NAME">%2$s</xliff:g>\""</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"‫\"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>\" و<xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> غيرها"</string>
 </resources>
diff --git a/quickstep/res/values-as/strings.xml b/quickstep/res/values-as/strings.xml
index 5ba9163..d440400 100644
--- a/quickstep/res/values-as/strings.xml
+++ b/quickstep/res/values-as/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{আৰু # টা এপ্‌ দেখুৱাওক।}one{আৰু # টা এপ্‌ দেখুৱাওক।}other{আৰু # টা এপ্‌ দেখুৱাওক।}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# টা ডেস্কটপ এপ্ দেখুৱাওক।}one{# টা ডেস্কটপ এপ্ দেখুৱাওক।}other{# টা ডেস্কটপ এপ্ দেখুৱাওক।}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"বাবল"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"অ’ভাৰফ্ল’"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g>ৰ পৰা <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> আৰু <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> টা"</string>
 </resources>
diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml
index ab75367..9cdcc01 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Daha # tətbiqi göstərin.}other{Daha # tətbiqi göstərin.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# masaüstü tətbiqini göstərin.}other{# masaüstü tətbiqini göstərin.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> və <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Yumrucuq"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Kənara çıxma"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>, <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> və daha <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> yumrucuq"</string>
 </resources>
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index d23ba50..b6271a9 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Prikaži još # aplikaciju.}one{Prikaži još # aplikaciju.}few{Prikaži još # aplikacije.}other{Prikaži još # aplikacija.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Prikaži # aplikaciju za računare.}one{Prikaži # aplikaciju za računare.}few{Prikaži # aplikacije za računare.}other{Prikaži # aplikacija za računare.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> i <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Oblačić"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Preklopni"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> – <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> i još <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml
index 2829fd9..bb6c764 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Паказаць ячшэ # праграму.}one{Паказаць ячшэ # праграму.}few{Паказаць ячшэ # праграмы.}many{Паказаць ячшэ # праграм.}other{Паказаць ячшэ # праграмы.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Паказаць # праграму для ПК.}one{Паказаць # праграму для ПК.}few{Паказаць # праграмы для ПК.}many{Паказаць # праграм для ПК.}other{Паказаць # праграмы для ПК.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Бурбалкі"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Меню з пашырэннем"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>, крыніца: <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> і яшчэ <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index bb7d7be..d674dbc 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Показване на още # приложение.}other{Показване на още # приложения.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Показване на # настолно приложение.}other{Показване на # настолни приложения.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Балонче"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Препълване"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> от <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> и още <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index ebde96d..3e974f5 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{আরও #টি অ্যাপ দেখুন।}one{আরও #টি অ্যাপ দেখুন।}other{আরও #টি অ্যাপ দেখুন।}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{#টি ডেস্কটপ অ্যাপ দেখুন।}one{#টি ডেস্কটপ অ্যাপ দেখুন।}other{#টি ডেস্কটপ অ্যাপ দেখুন।}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"বাবল"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ওভারফ্লো"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> থেকে <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> এবং আরও <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>টি"</string>
 </resources>
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index 89cdc28..19268be 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Prikaži još # aplikaciju.}one{Prikaži još # aplikaciju.}few{Prikaži još # aplikacije.}other{Prikaži još # aplikacija.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Prikaži # aplikaciju za računar.}one{Prikaži # aplikaciju za računar.}few{Prikaži # aplikacije za računar.}other{Prikaži # aplikacija za računar.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> i <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Oblačić"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Preklopni meni"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> iz aplikacije <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> i još <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml
index f1768aa..6a6f131 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostra # aplicació més.}other{Mostra # aplicacions més.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Mostra # aplicació per a ordinadors.}other{Mostra # aplicacions per a ordinadors.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> i <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bombolla"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Desbordament"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> i <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> més"</string>
 </resources>
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index 6c523ca..e134850 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -73,7 +73,7 @@
     <string name="overview_gesture_feedback_swipe_too_far_from_edge" msgid="6402349235265407385">"Přejeďte prstem nahoru z dolního okraje obrazovky"</string>
     <string name="overview_gesture_feedback_home_detected" msgid="663432226180397138">"Zkuste podržet okno delší dobu, než ho uvolníte"</string>
     <string name="overview_gesture_feedback_wrong_swipe_direction" msgid="1191055451018584958">"Přejeďte prstem přímo nahoru a pak udělejte pauzu"</string>
-    <string name="overview_gesture_feedback_complete_with_follow_up" msgid="3544611727467765026">"Naučili jste se používat gesta. Vypnout je můžete v nastavení."</string>
+    <string name="overview_gesture_feedback_complete_with_follow_up" msgid="3544611727467765026">"Naučili jste se používat gesta. Vypnout je můžete v Nastavení."</string>
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Dokončili jste gesto pro přepínání aplikací"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Přepínání aplikací přejetím prstem"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Přejeďte nahoru z dolního okraje obrazovky, podržte obrazovku a uvolněte."</string>
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Zobrazit # další aplikaci.}few{Zobrazit # další aplikace.}many{Zobrazit # další aplikace.}other{Zobrazit # dalších aplikací.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Zobrazit # aplikaci pro počítač.}few{Zobrazit # aplikace pro počítač.}many{Zobrazit # aplikace pro počítač.}other{Zobrazit # aplikací pro počítač.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> a <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bublina"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Rozbalovací nabídka"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> z aplikace <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> a ještě <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index a8d3612..e9cdbce 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Vis # app mere.}one{Vis # app mere.}other{Vis # apps mere.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Vis # computerprogram.}one{Vis # computerprogram.}other{Vis # computerprogrammer.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Boble"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overløb"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> fra <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> og <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> mere"</string>
 </resources>
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index bc15ca5..9e5cb12 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# weitere App anzeigen}other{# weitere Apps anzeigen}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# Desktop-App anzeigen.}other{# Desktop-Apps anzeigen.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> und <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubble"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Weitere Optionen"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"„<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>“ aus <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> und <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> weitere"</string>
 </resources>
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index c317aa4..bbb1282 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Εμφάνιση # ακόμα εφαρμογής.}other{Εμφάνιση # ακόμα εφαρμογών.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Εμφάνιση # εφαρμογής υπολογιστή.}other{Εμφάνιση # εφαρμογών υπολογιστή.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Συννεφάκι"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Υπερχείλιση"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> από <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> και <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> ακόμα"</string>
 </resources>
diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index 519146b..eedb29e 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Show # more app.}other{Show # more apps.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Show # desktop app.}other{Show # desktop apps.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> and <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubble"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overflow"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> from <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> and <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> more"</string>
 </resources>
diff --git a/quickstep/res/values-en-rCA/strings.xml b/quickstep/res/values-en-rCA/strings.xml
index 4205e49..88cd0dd 100644
--- a/quickstep/res/values-en-rCA/strings.xml
+++ b/quickstep/res/values-en-rCA/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Show # more app.}other{Show # more apps.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Show # desktop app.}other{Show # desktop apps.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> and <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubble"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overflow"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> from <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> and <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> more"</string>
 </resources>
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index 519146b..eedb29e 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Show # more app.}other{Show # more apps.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Show # desktop app.}other{Show # desktop apps.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> and <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubble"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overflow"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> from <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> and <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> more"</string>
 </resources>
diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index 519146b..eedb29e 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Show # more app.}other{Show # more apps.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Show # desktop app.}other{Show # desktop apps.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> and <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubble"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overflow"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> from <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> and <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> more"</string>
 </resources>
diff --git a/quickstep/res/values-en-rXC/strings.xml b/quickstep/res/values-en-rXC/strings.xml
index 44afedd..76dab0d 100644
--- a/quickstep/res/values-en-rXC/strings.xml
+++ b/quickstep/res/values-en-rXC/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‏‎‎‏‎‎‎‏‎‏‎‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‏‎Show # more app.‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‏‎‏‎‎‎‎‎‎‏‏‎‎‏‎‎‎‏‎‏‎‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‏‎Show # more apps.‎‏‎‎‏‎}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‏‎‎‎‏‎‏‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‎‎Show # desktop app.‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‏‏‏‏‏‎‎‏‏‏‏‎‎‏‎‏‎‎‎‏‎‏‎‏‏‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‏‎‎‎‎Show # desktop apps.‎‏‎‎‏‎}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‏‏‏‎‎‎‎‏‏‏‏‎‎‏‎‏‎‏‏‎‏‎‎‎‏‏‏‏‏‏‎‎‎‎‎‏‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="APP_NAME_1">%1$s</xliff:g>‎‏‎‎‏‏‏‎ and ‎‏‎‎‏‏‎<xliff:g id="APP_NAME_2">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‎‎‎‏‎‏‎‏‏‎‏‏‎‏‏‎‏‎‏‏‎‎‎‏‏‎‏‏‏‎‎‎‏‏‏‎‎‏‎‏‏‎‏‏‏‏‎‏‎‎‏‎Bubble‎‏‎‎‏‎"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‏‎‎‏‏‏‎‏‏‏‎‎‎Overflow‎‏‎‎‏‎"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‎‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‎‏‏‎‎‎‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ from ‎‏‎‎‏‏‎<xliff:g id="APP_NAME">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‎‏‏‏‎‎‏‏‏‏‎‏‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‎‏‎‎‏‎‏‎‎‎‏‎‏‏‎‎‏‎‎‏‏‎‎‎‎‎‎‎‏‎‎‏‏‎<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>‎‏‎‎‏‏‏‎ and ‎‏‎‎‏‏‎<xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>‎‏‎‎‏‏‏‎ more‎‏‎‎‏‎"</string>
 </resources>
diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index 4930c0b..58d540f 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostrar # app más.}other{Mostrar # apps más.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Mostrar # app para computadoras.}other{Mostrar # apps para computadoras.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Burbuja"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Ampliada"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> y <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> más"</string>
 </resources>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index 4b45f5e..2a956f0 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostrar # aplicación más.}other{Mostrar # aplicaciones más.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Mostrar # aplicación para ordenadores.}other{Mostrar # aplicaciones para ordenadores.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Burbuja"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Menú adicional"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> y <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> más"</string>
 </resources>
diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml
index d6b9e78..0ecc0c0 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Kuva veel # rakendus.}other{Kuva veel # rakendust.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Kuva # töölauarakendus.}other{Kuva # töölauarakendust.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ja <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Mull"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Ületäide"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> – <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ja veel <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> mulli"</string>
 </resources>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index bcecfea..e83ee28 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Erakutsi beste # aplikazio.}other{Erakutsi beste # aplikazio.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Erakutsi ordenagailuetarako # aplikazio.}other{Erakutsi ordenagailuetarako # aplikazio.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> eta <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Burbuila"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Luzapena"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> (<xliff:g id="APP_NAME">%2$s</xliff:g>)"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> eta beste <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index a68d77c..b296080 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{نمایش # برنامه دیگر.}one{نمایش # برنامه دیگر.}other{نمایش # برنامه دیگر.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{نمایش # برنامه رایانه.}one{نمایش # برنامه رایانه.}other{نمایش # برنامه رایانه.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"حبابک"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"سرریز"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> از <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> و <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> حبابک دیگر"</string>
 </resources>
diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index bb0516c..5ac124a 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Näytä # muu sovellus.}other{Näytä # muuta sovellusta.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Näytä # työpöytäsovellus.}other{Näytä # työpöytäsovellusta.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ja <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Kupla"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Ylivuoto"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>: <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ja <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> muuta"</string>
 </resources>
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index dd36f2e..edfb59e 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Afficher # autre application.}one{Afficher # autre application.}other{Afficher # autres applications.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Afficher # appli de bureau.}one{Afficher # appli de bureau.}other{Afficher # applis de 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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bulle"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Bulle à développer"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> et <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> autres"</string>
 </resources>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index de335bf..60f8944 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Afficher # autre appli}one{Afficher # autre appli}other{Afficher # autre applis}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Afficher # application de bureau.}one{Afficher # application de bureau.}other{Afficher # applications de 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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bulle"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Dépassement"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> (<xliff:g id="APP_NAME">%2$s</xliff:g>)"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> et <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> autre(s)"</string>
 </resources>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index b116332..bf081d4 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostrar # aplicación máis.}other{Mostrar # aplicacións máis.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Mostrar # aplicación para ordenadores.}other{Mostrar # aplicacións para ordenadores.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> e <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Burbulla"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Menú adicional"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> e <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> máis"</string>
 </resources>
diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml
index efba149..b20f771 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{વધુ # ઍપ બતાવો.}one{વધુ # ઍપ બતાવો.}other{વધુ # ઍપ બતાવો.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# ડેસ્કટૉપ ઍપ બતાવો.}one{# ડેસ્કટૉપ ઍપ બતાવો.}other{# ડેસ્કટૉપ ઍપ બતાવો.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"બબલ"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ઓવરફ્લો"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g>થી <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> અને વધુ <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index 205be27..a645186 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# और ऐप्लिकेशन दिखाएं.}one{# और ऐप्लिकेशन दिखाएं.}other{# और ऐप्लिकेशन दिखाएं.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# डेस्कटॉप ऐप्लिकेशन दिखाएं.}one{# डेस्कटॉप ऐप्लिकेशन दिखाएं.}other{# डेस्कटॉप ऐप्लिकेशन दिखाएं.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"बबल"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ओवरफ़्लो"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> की <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> वाली सूचना"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> और <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> अन्य"</string>
 </resources>
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index d189268..c96381d 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Prikaži još # aplikaciju}one{Prikaži još # aplikaciju}few{Prikaži još # aplikacije}other{Prikaži još # aplikacija}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Prikaži # računalnu aplikaciju.}one{Prikaži # računalnu aplikaciju.}few{Prikaži # računalne aplikacije.}other{Prikaži # računalnih aplikacija.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> i <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Oblačić"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Dodatni izbornik"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>, <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> i još <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index 975966e..03235aa 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# további alkalmazás megjelenítése.}other{# további alkalmazás megjelenítése.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# asztali alkalmazás megjelenítése.}other{# asztali alkalmazás megjelenítése.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> és <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Buborék"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Túlcsordulás"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>, forrás: <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> és <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> további"</string>
 </resources>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index 7e56258..3cb7990 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Ցուցադրել ևս # հավելված։}one{Ցուցադրել ևս # հավելված։}other{Ցուցադրել ևս # հավելված։}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Ցույց տալ # համակարգչային հավելված։}one{Ցույց տալ # համակարգչային հավելված։}other{Ցույց տալ # համակարգչային հավելված։}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Ամպիկ"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Լրացուցիչ ընտրացանկ"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>՝ <xliff:g id="APP_NAME">%2$s</xliff:g> հավելվածից"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ու ևս <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> ամպիկ"</string>
 </resources>
diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml
index d0a7a91..015b09e 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Tampilkan # aplikasi lainnya.}other{Tampilkan # aplikasi lainnya.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Tampilkan # aplikasi desktop.}other{Tampilkan # aplikasi desktop.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> dan <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Balon"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Tambahan"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> dari <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> dan <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> lainnya"</string>
 </resources>
diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml
index 34e5f26..883fe82 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Sýna # forrit í viðbót.}one{Sýna # forrit í viðbót.}other{Sýna # forrit í viðbót.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Sýna # skjáborðsforrit.}one{Sýna # skjáborðsforrit.}other{Sýna # skjáborðsforrit.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Blaðra"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Yfirflæði"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> frá <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> og <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> í viðbót"</string>
 </resources>
diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml
index a5d11f8..59b195a 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostra # altra app.}other{Mostra altre # app.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Mostra # app desktop.}other{Mostra # app desktop.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> e <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Fumetto"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Extra"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> da <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> e altri <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index 30c53d5..9f0ed14 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{הצגת אפליקציה אחת (#) נוספת.}one{הצגת # אפליקציות נוספות.}two{הצגת # אפליקציות נוספות.}other{הצגת # אפליקציות נוספות.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{הצגת אפליקציה אחת (#) למחשב.}one{הצגת # אפליקציות למחשב.}two{הצגת # אפליקציות למחשב.}other{הצגת # אפליקציות למחשב.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"בועה"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"אפשרויות נוספות"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"‫<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> מתוך <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"‫<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ועוד <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml
index ead3c19..4f1a162 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{他 # 件のアプリを表示できます。}other{他 # 件のアプリを表示できます。}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# 個のデスクトップ アプリが表示されます。}other{# 個のデスクトップ アプリが表示されます。}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ふきだし"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"オーバーフロー"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>(<xliff:g id="APP_NAME">%2$s</xliff:g>)"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>、他 <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> 件"</string>
 </resources>
diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml
index bcc28b4..1fb6077 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{#-ით მეტი აპის ჩენება}other{#-ით მეტი აპის ჩვენება.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# დესკტოპის აპის ჩვენება.}other{# დესკტოპის აპის ჩვენება.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ბუშტი"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"გადავსება"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>: <xliff:g id="APP_NAME">%2$s</xliff:g>-იდან"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> და <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> სხვა"</string>
 </resources>
diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index 0002024..d83e2d3 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Тағы # қолданбаны көрсету.}other{Тағы # қолданбаны көрсету.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Компьютерге арналған # қолданбаны көрсету}other{Компьютерге арналған # қолданбаны көрсету}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Қалқыма терезе"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Қосымша мәзір"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> ұсынатын <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> және тағы <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml
index 8c38c48..5448433 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{បង្ហាញកម្មវិធី # ទៀត។}other{បង្ហាញ​កម្មវិធី # ទៀត។}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{បង្ហាញកម្មវិធី​កុំព្យូទ័រ #។}other{បង្ហាញកម្មវិធី​កុំព្យូទ័រ #។}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ផ្ទាំងសារ"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ម៉ឺនុយបន្ថែម"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ពី <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> និង <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> នាក់ទៀត"</string>
 </resources>
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index 1231157..74c7750 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{ಇನ್ನೂ # ಆ್ಯಪ್ ಅನ್ನು ತೋರಿಸಿ.}one{ಇನ್ನೂ # ಆ್ಯಪ್‌ಗಳನ್ನು ತೋರಿಸಿ.}other{ಇನ್ನೂ # ಆ್ಯಪ್‌ಗಳನ್ನು ತೋರಿಸಿ.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# ಡೆಸ್ಕ್‌ಟಾಪ್ ಆ್ಯಪ್ ತೋರಿಸಿ.}one{# ಡೆಸ್ಕ್‌ಟಾಪ್ ಆ್ಯಪ್‌ಗಳನ್ನು ತೋರಿಸಿ.}other{# ಡೆಸ್ಕ್‌ಟಾಪ್ ಆ್ಯಪ್‌ಗಳನ್ನು ತೋರಿಸಿ.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ಬಬಲ್"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ಓವರ್‌ಫ್ಲೋ"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> ನಿಂದ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ಮತ್ತು ಇನ್ನೂ <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index e0fc21f..1f4275a 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{앱 #개 더 표시}other{앱 #개 더 표시}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{데스크톱 앱 #개를 표시합니다.}other{데스크톱 앱 #개를 표시합니다.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"풍선"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"오버플로"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g>의 <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> 외 <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>개"</string>
 </resources>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index fbb1c42..f0d2af8 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Дагы # колдонмону көрсөтүү.}other{Дагы # колдонмону көрсөтүү.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# иш такта колдонмосун көрсөтүү.}other{# иш такта колдонмосун көрсөтүү.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Көбүкчө"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Кошумча меню"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> колдонмосунан <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> жана дагы <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index bb36330..f54c712 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{ສະແດງອີກ # ແອັບ.}other{ສະແດງອີກ # ແອັບ.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{ສະແດງແອັບເດັສທັອບ # ລາຍການ.}other{ສະແດງແອັບເດັສທັອບ # ລາຍການ.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ຟອງ"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ລາຍການເພີ່ມເຕີມ"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> ຈາກ <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ແລະ ອີກ <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> ລາຍການ"</string>
 </resources>
diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index b5dceb6..554745e 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Rodyti dar # programą.}one{Rodyti dar # programą.}few{Rodyti dar # programas.}many{Rodyti dar # programos.}other{Rodyti dar # programų.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Rodyti # darbalaukio programą.}one{Rodyti # darbalaukio programą.}few{Rodyti # darbalaukio programas.}many{Rodyti # darbalaukio programos.}other{Rodyti # darbalaukio programų.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"„<xliff:g id="APP_NAME_1">%1$s</xliff:g>“ ir „<xliff:g id="APP_NAME_2">%2$s</xliff:g>“"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Burbulas"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Perpildymas"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"„<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>“ iš „<xliff:g id="APP_NAME">%2$s</xliff:g>“"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"„<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>“ ir dar <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml
index 2b6208a..a6a0dab 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Rādīt vēl # lietotni}zero{Rādīt vēl # lietotnes}one{Rādīt vēl # lietotni}other{Rādīt vēl # lietotnes}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Rādīt # datora lietotni.}zero{Rādīt # datora lietotnes.}one{Rādīt # datora lietotni.}other{Rādīt # datora lietotnes.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"“<xliff:g id="APP_NAME_1">%1$s</xliff:g>” un “<xliff:g id="APP_NAME_2">%2$s</xliff:g>”"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Burbulis"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Pārpilde"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> no lietotnes <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> un vēl <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index 6b8c592..4859055 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Прикажи уште # апликација.}one{Прикажи уште # апликација.}other{Прикажи уште # апликации.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Прикажи # апликација за компјутер.}one{Прикажи # апликација за компјутер.}other{Прикажи # апликации за компјутер.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Балонче"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Проширено балонче"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> од <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> и уште <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index d401c20..85b093d 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# ആപ്പ് കൂടി കാണിക്കുക.}other{# ആപ്പുകൾ കൂടി കാണിക്കുക.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# ഡെസ്‌ക്ടോപ്പ് ആപ്പ് കാണിക്കുക.}other{# ഡെസ്‌ക്ടോപ്പ് ആപ്പുകൾ കാണിക്കുക.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ബബിൾ"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ഓവർഫ്ലോ"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> എന്നതിൽ നിന്നുള്ള <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> എന്നതും മറ്റ് <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> എണ്ണവും"</string>
 </resources>
diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index 4491c37..7a4c7e9 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Өөр # аппыг харуулна уу.}other{Өөр # аппыг харуулна уу.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Компьютерын # аппыг харуулна уу.}other{Компьютерын # аппыг харуулна уу.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Бөмбөлөг"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Урт цэс"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g>-с ирсэн <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> болон бусад <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index 3938fd4..b053a21 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -95,7 +95,7 @@
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"सिस्टीम नेव्हिगेशन सेटिंग्ज"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"शेअर करा"</string>
     <string name="action_screenshot" msgid="8171125848358142917">"स्क्रीनशॉट"</string>
-    <string name="action_split" msgid="2098009717623550676">"स्प्लिट"</string>
+    <string name="action_split" msgid="2098009717623550676">"स्प्लिट करा"</string>
     <string name="action_save_app_pair" msgid="5974823919237645229">"ॲपची जोडी सेव्ह करा"</string>
     <string name="toast_split_select_app" msgid="8464310533320556058">"स्प्लिट स्क्रीन वापरण्यासाठी दुसऱ्या ॲपवर टॅप करा"</string>
     <string name="toast_contextual_split_select_app" msgid="433510957123687090">"स्प्लिट स्क्रीन वापरण्यासाठी दुसरे ॲप निवडा"</string>
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{आणखी # अ‍ॅप दाखवा.}other{आणखी # अ‍ॅप्स दाखवा.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# डेस्‍कटॉप अ‍ॅप दाखवा.}other{# डेस्‍कटॉप अ‍ॅप्स दाखवा.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"बबल"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ओव्हरफ्लो"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> वरील <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> आणि आणखी <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml
index 0db53d6..c0219e0 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Tunjukkan # lagi apl.}other{Tunjukkan # lagi apl.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Tunjukkan # apl desktop.}other{Tunjukkan # apl desktop.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> dan <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Gelembung"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Limpahan"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> daripada <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> dan <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> lagi"</string>
 </resources>
diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml
index 4ac838a..7c7ff82 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{နောက်ထပ်အက်ပ် # ခု ပြပါ။}other{နောက်ထပ်အက်ပ် # ခု ပြပါ။}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{ဒက်စတော့ အက်ပ် # ခု ပြပါ။}other{ဒက်စတော့ အက်ပ် # ခု ပြပါ။}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ပူဖောင်းကွက်"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"မီနူးအပို"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> မှ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> နှင့် နောက်ထပ် <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> ခု"</string>
 </resources>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index 46bf14b..dd3d16e 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Vis # app til.}other{Vis # apper til.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Vis # datamaskinprogram.}other{Vis # datamaskinprogrammer.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Boble"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overflyt"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> fra <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> og <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> andre"</string>
 </resources>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index 07437ca..d49fd2d 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{थप # एप देखाउनुहोस्।}other{थप # वटा एप देखाउनुहोस्।}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# डेस्कटप एप देखाउनुहोस्।}other{# वटा डेस्कटप एप देखाउनुहोस्।}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"बबल"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ओभरफ्लो"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> मा देखाइएका <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> र थप <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-night/colors.xml b/quickstep/res/values-night/colors.xml
index 8d03ce6..94100ba 100644
--- a/quickstep/res/values-night/colors.xml
+++ b/quickstep/res/values-night/colors.xml
@@ -22,7 +22,7 @@
     <color name="mock_webpage_url_bar">#202124</color>
     <color name="mock_webpage_url_bar_item">#3c4043</color>
 
-    <color name="all_set_page_background">#FF000000</color>
+    <color name="all_set_page_background">@android:color/system_neutral1_900</color>
 
     <!-- Turn on work apps button -->
     <color name="work_turn_on_stroke">?androidprv:attr/colorAccentSecondaryVariant</color>
diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml
index 3dedd7b..ca44a69 100644
--- a/quickstep/res/values-nl/strings.xml
+++ b/quickstep/res/values-nl/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Nog # app tonen.}other{Nog # apps tonen.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# desktop-app tonen.}other{# desktop-apps tonen.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> en <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubbel"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overloop"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> van <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> en nog <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index 67a5b13..bf0bdc8 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{ଅଧିକ #ଟି ଆପ ଦେଖାନ୍ତୁ।}other{ଅଧିକ #ଟି ଆପ୍ସ ଦେଖାନ୍ତୁ।}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# ଡେସ୍କଟପ ଆପ ଦେଖାନ୍ତୁ।}other{# ଡେସ୍କଟପ ଆପ୍ସ ଦେଖାନ୍ତୁ।}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ବବଲ"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ଓଭରଫ୍ଲୋ"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g>ରୁ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ଏବଂ ଅଧିକ <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index 78f1c79..fc60396 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# ਹੋਰ ਐਪ ਦਿਖਾਓ।}one{# ਹੋਰ ਐਪ ਦਿਖਾਓ।}other{# ਹੋਰ ਐਪਾਂ ਦਿਖਾਓ।}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# ਡੈਸਕਟਾਪ ਐਪ ਦਿਖਾਓ।}one{# ਡੈਸਕਟਾਪ ਐਪ ਦਿਖਾਓ।}other{# ਡੈਸਕਟਾਪ ਐਪਾਂ ਦਿਖਾਓ।}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ਬਬਲ"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ਓਵਰਫ਼ਲੋ"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> ਤੋਂ <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ਅਤੇ <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> ਹੋਰ"</string>
 </resources>
diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index 41ac736..d88e28a 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Pokaż jeszcze # aplikację.}few{Pokaż jeszcze # aplikacje.}many{Pokaż jeszcze # aplikacji.}other{Pokaż jeszcze # aplikacji.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Pokaż # aplikację komputerową.}few{Pokaż # aplikacje komputerowe.}many{Pokaż # aplikacji komputerowych.}other{Pokaż # aplikacji komputerowej.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> i <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Dymek"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Rozwijany"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> z aplikacji <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> i jeszcze <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index f674d68..47498cc 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -57,7 +57,7 @@
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Deslize rapidamente com o dedo para retroceder"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Para voltar ao último ecrã, deslize rapidamente do limite esquerdo ou direito até ao centro do ecrã."</string>
     <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Para voltar ao último ecrã, deslize rapidamente com 2 dedos a partir da extremidade esquerda ou direita até ao centro do ecrã."</string>
-    <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Voltar"</string>
+    <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Retroceder"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Deslize rapidamente a partir da extremidade esquerda ou direita para o meio do ecrã"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Deslize rapidamente com o dedo a partir do limite inferior do ecrã"</string>
     <string name="home_gesture_feedback_overview_detected" msgid="5177627157303895077">"Não faça uma pausa antes de soltar"</string>
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostrar mais # app.}other{Mostrar mais # apps.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Mostrar # app para computador.}other{Mostrar # apps para computador.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> e <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Balão"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Menu adicional"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> da app <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> e mais <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> pessoas"</string>
 </resources>
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index 4f3d75a..4fec4f8 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Mostrar mais # app.}one{Mostrar mais # app.}other{Mostrar mais # apps.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Mostrar # app para computador.}one{Mostrar # app para computador.}other{Mostrar # apps para computador.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> e <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Balão"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Balão flutuante"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> do app <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> e mais <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index dee1ac6..c839602 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Afișează încă # aplicație}few{Afișează încă # aplicații}other{Afișează încă # de aplicații}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Afișează # aplicație pentru computer.}few{Afișează # aplicații pentru computer.}other{Afișează # de aplicații pentru computer.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> și <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Balon"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Suplimentar"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> de la <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> și încă <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index a78561a..da49ad3 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Показать ещё # приложение}one{Показать ещё # приложение}few{Показать ещё # приложения}many{Показать ещё # приложений}other{Показать ещё # приложения}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Показать # компьютерное приложение.}one{Показать # компьютерное приложение.}few{Показать # компьютерных приложения.}many{Показать # компьютерных приложений.}other{Показать # компьютерного приложения.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Всплывающая подсказка"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Дополнительное меню"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"\"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>\" из приложения \"<xliff:g id="APP_NAME">%2$s</xliff:g>\""</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> и ещё <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml
index c1dfa9d..9cbe837 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{තවත් # යෙදුමක් පෙන්වන්න.}one{තවත් යෙදුම් #ක් පෙන්වන්න.}other{තවත් යෙදුම් #ක් පෙන්වන්න.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# ඩෙස්ක්ටොප් යෙදුමක් පෙන්වන්න.}one{ඩෙස්ක්ටොප් යෙදුම් # ක් පෙන්වන්න.}other{ඩෙස්ක්ටොප් යෙදුම් # ක් පෙන්වන්න.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"බුබුළු"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"පිටාර යාම"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> සිට <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> හා තව <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>ක්"</string>
 </resources>
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index 51f09aa..3eca787 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Zobraziť # ďalšiu aplikáciu.}few{Zobraziť # ďalšie aplikácie.}many{Show # more apps.}other{Zobraziť # ďalších aplikácií.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Zobraziť # aplikáciu pre počítač.}few{Zobraziť # aplikácie pre počítač.}many{Show # desktop apps.}other{Zobraziť # aplikácií pre počítač.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> a <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bublina"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Rozbaľovacia ponuka"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> z aplikácie <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> a ešte <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index ea1fd45..52faeb7 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Pokaži še # aplikacijo.}one{Pokaži še # aplikacijo.}two{Pokaži še # aplikaciji.}few{Pokaži še # aplikacije.}other{Pokaži še # aplikacij.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Prikaz # aplikacije za namizni računalnik.}one{Prikaz # aplikacije za namizni računalnik.}two{Prikaz # aplikacij za namizni računalnik.}few{Prikaz # aplikacij za namizni računalnik.}other{Prikaz # aplikacij za namizni računalnik.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> in <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Oblaček"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Oblaček z dodatnimi elementi"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> iz aplikacije <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> in še <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index 6cc894a..cdb9cf9 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Shfaq # aplikacion tjetër.}other{Shfaq # aplikacione të tjera.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Shfaq # aplikacion për desktop.}other{Shfaq # aplikacione për desktop.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> dhe <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Flluska"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Tejkalimi"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"\"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>\" nga <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"\"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>\" dhe <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> të tjera"</string>
 </resources>
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index 7828868..7456a36 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Прикажи још # апликацију.}one{Прикажи још # апликацију.}few{Прикажи још # апликације.}other{Прикажи још # апликација.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Прикажи # апликацију за рачунаре.}one{Прикажи # апликацију за рачунаре.}few{Прикажи # апликације за рачунаре.}other{Прикажи # апликација за рачунаре.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Облачић"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Преклопни"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> – <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> и још <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index 4fecddd..f369dae 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Visa # app till.}other{Visa # appar till.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Visa # datorapp.}other{Visa # datorappar.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubbla"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Fler alternativ"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> från <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> och <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> till"</string>
 </resources>
diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml
index 3c4e5e5..3d8277b 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Onyesha programu # zaidi.}other{Onyesha programu # zaidi.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Onyesha programu # ya kompyuta ya mezani.}other{Onyesha programu # za kompyuta ya mezani.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> na <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Kiputo"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Kiputo cha vipengee vya ziada"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> kutoka <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> na vingine <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index b259dbf..ed3ebee 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{மேலும் # ஆப்ஸைக் காட்டு.}other{மேலும் # ஆப்ஸைக் காட்டு.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# டெஸ்க்டாப் ஆப்ஸைக் காட்டு.}other{# டெஸ்க்டாப் ஆப்ஸைக் காட்டு.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"குமிழ்"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"கூடுதல் விருப்பங்களைக் காட்டும்"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> வழங்கும் <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> மற்றும் <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index 20fc77d..a4e1cbf 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{మరో # యాప్‌ను చూడండి.}other{మరో # యాప్‌లను చూడండి.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# డెస్క్‌టాప్ యాప్‌ను చూపండి.}other{# డెస్క్‌టాప్ యాప్‌లను చూపండి.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"బబుల్"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"ఓవర్‌ఫ్లో"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> నుండి <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>, మరో <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml
index 4adcc52..1bbb137 100644
--- a/quickstep/res/values-th/strings.xml
+++ b/quickstep/res/values-th/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{แสดงเพิ่มเติมอีก # แอป}other{แสดงเพิ่มเติมอีก # แอป}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{แสดงแอปบนเดสก์ท็อป # รายการ}other{แสดงแอปบนเดสก์ท็อป # รายการ}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"บับเบิล"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"การดำเนินการเพิ่มเติม"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> จาก <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> และอีก <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> รายการ"</string>
 </resources>
diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml
index 66ecc00..978a5a3 100644
--- a/quickstep/res/values-tl/strings.xml
+++ b/quickstep/res/values-tl/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Magpakita ng # pang app.}one{Magpakita ng # pang app.}other{Magpakita ng # pang app.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Ipakita ang # desktop app.}one{Ipakita ang # desktop app.}other{Ipakita ang # na desktop app.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> at <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bubble"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Overflow"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> mula sa <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> at <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> pa"</string>
 </resources>
diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index dc5fa6b..0cc5d7f 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# uygulama daha göster.}other{# uygulama daha göster}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# masaüstü uygulamasını göster.}other{# masaüstü uygulamasını göster.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ve <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Balon"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Taşma"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> uygulamasından <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> ve <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> tane daha"</string>
 </resources>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index 5119e56..9c706a8 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Показати ще # додаток.}one{Показати ще # додаток.}few{Показати ще # додатки.}many{Показати ще # додатків.}other{Показати ще # додатка.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Показати # комп’ютерну програму.}one{Показати # комп’ютерну програму.}few{Показати # комп’ютерні програми.}many{Показати # комп’ютерних програм.}other{Показати # комп’ютерної програми.}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Повідомлення"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Додаткове повідомлення"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> з додатка <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> і ще <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml
index c42a601..e125248 100644
--- a/quickstep/res/values-ur/strings.xml
+++ b/quickstep/res/values-ur/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{# مزید ایپ دکھائیں۔}other{# مزید ایپس دکھائیں۔}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# ڈیسک ٹاپ ایپ دکھائیں۔}other{# ڈیسک ٹاپ ایپس دکھائیں۔}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"ببل"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"اوورفلو"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> سے <xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> اور <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> مزید"</string>
 </resources>
diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index 810c8f0..3f4f981 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Yana # ta ilovani chiqarish}other{Yana # ta ilovani chiqarish}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{# ta desktop ilovani chiqarish.}other{# ta desktop ilovani chiqarish.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> va <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Pufak"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Kengaytirish"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> (<xliff:g id="APP_NAME">%2$s</xliff:g>)"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> va yana <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> kishi"</string>
 </resources>
diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index 8291ac7..9bc526f 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Hiện thêm # ứng dụng.}other{Hiện thêm # ứng dụng.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Hiện # ứng dụng dành cho máy tính.}other{Hiện # ứng dụng dành cho máy tính.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> và <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Bong bóng"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Bong bóng bổ sung"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> từ <xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> và <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> bong bóng khác"</string>
 </resources>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index e00e932..a89227e 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{显示另外 # 个应用。}other{显示另外 # 个应用。}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{显示 # 款桌面应用。}other{显示 # 款桌面应用。}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"气泡框"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"菜单"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"来自“<xliff:g id="APP_NAME">%2$s</xliff:g>”的<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>以及另外 <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> 个"</string>
 </resources>
diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index 3a77369..b9d8eb7 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{顯示另外 # 個應用程式。}other{顯示另外 # 個應用程式。}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{顯示 # 個桌面應用程式。}other{顯示 # 個桌面應用程式。}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"對話氣泡"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"展開式"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="APP_NAME">%2$s</xliff:g> 的「<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>」通知"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>和其他 <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> 則通知"</string>
 </resources>
diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index 3b59966..90140cb 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{再多顯示 # 個應用程式。}other{再多顯示 # 個應用程式。}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{顯示 # 個電腦版應用程式。}other{顯示 # 個電腦版應用程式。}}"</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>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"泡泡"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"溢位"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"「<xliff:g id="APP_NAME">%2$s</xliff:g>」的「<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g>」通知"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g>和另外 <xliff:g id="BUBBLE_COUNT">%2$d</xliff:g> 則通知"</string>
 </resources>
diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index b3ef1e7..73be445 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -140,12 +140,8 @@
     <string name="quick_switch_overflow" msgid="6935266023013283353">"{count,plural, =1{Bonisa i-app e-# ngaphezulu.}one{Bonisa ama-app angu-# ngaphezulu.}other{Bonisa ama-app angu-# ngaphezulu.}}"</string>
     <string name="quick_switch_desktop" msgid="4834587349322698616">"{count,plural, =1{Bonisa i-app engu-# yedeskithophu.}one{Bonisa ama-app angu-# wedeskithophu.}other{Bonisa ama-app angu-# wedeskithophu.}}"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"I-<xliff:g id="APP_NAME_1">%1$s</xliff:g> ne-<xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
-    <!-- no translation found for bubble_bar_bubble_fallback_description (7811684548953452009) -->
-    <skip />
-    <!-- no translation found for bubble_bar_overflow_description (8617628132733151708) -->
-    <skip />
-    <!-- no translation found for bubble_bar_bubble_description (1882466152448446446) -->
-    <skip />
-    <!-- no translation found for bubble_bar_description_multiple_bubbles (3922207715357143648) -->
-    <skip />
+    <string name="bubble_bar_bubble_fallback_description" msgid="7811684548953452009">"Ibhamuza"</string>
+    <string name="bubble_bar_overflow_description" msgid="8617628132733151708">"Ukugcwala kakhulu"</string>
+    <string name="bubble_bar_bubble_description" msgid="1882466152448446446">"<xliff:g id="NOTIFICATION_TITLE">%1$s</xliff:g> kusuka ku-<xliff:g id="APP_NAME">%2$s</xliff:g>"</string>
+    <string name="bubble_bar_description_multiple_bubbles" msgid="3922207715357143648">"<xliff:g id="BUBBLE_BAR_BUBBLE_DESCRIPTION">%1$s</xliff:g> nokunye okungu-<xliff:g id="BUBBLE_COUNT">%2$d</xliff:g>"</string>
 </resources>
diff --git a/quickstep/res/values/colors.xml b/quickstep/res/values/colors.xml
index 14a916f..0f997f9 100644
--- a/quickstep/res/values/colors.xml
+++ b/quickstep/res/values/colors.xml
@@ -76,7 +76,7 @@
     <color name="mock_webpage_top_bar_item">#80868b</color>
     <color name="mock_webpage_page_text">#bdc1c6</color>
 
-    <color name="all_set_page_background">#FFFFFFFF</color>
+    <color name="all_set_page_background">@android:color/system_neutral1_50</color>
 
     <!-- Recents overview -->
     <color name="recents_filter_icon">#333333</color>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 6b76311..d4f66e2 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -367,6 +367,7 @@
     <dimen name="transient_taskbar_min_width">150dp</dimen>
     <dimen name="transient_taskbar_bottom_margin">24dp</dimen>
     <dimen name="transient_taskbar_shadow_blur">40dp</dimen>
+    <dimen name="transient_taskbar_stroke_width">1dp</dimen>
     <dimen name="transient_taskbar_key_shadow_distance">10dp</dimen>
     <dimen name="transient_taskbar_stashed_height">32dp</dimen>
     <dimen name="transient_taskbar_all_apps_button_translation_x_offset">8dp</dimen>
@@ -384,6 +385,11 @@
     <!-- Taskbar swipe down threshold -->
     <dimen name="taskbar_to_nav_threshold">24dp</dimen>
 
+    <!-- Taskbar variables that help determine when to animate the Taskbar background -->
+    <!-- Velocity defined as dp per s. Negative because the gesture is an upwards motion. -->
+    <dimen name="taskbar_slow_velocity_y_threshold">-288dp</dimen>
+    <integer name="taskbar_background_duration">80</integer>
+
     <!-- Taskbar swipe up threshold multipliers -->
     <item name="taskbar_nav_threshold_mult" format="float" type="dimen">4.5</item>
     <item name="taskbar_app_window_threshold_mult" format="float" type="dimen">10</item>
@@ -439,6 +445,7 @@
     <dimen name="bubblebar_elevation">1dp</dimen>
     <dimen name="bubblebar_drag_elevation">2dp</dimen>
     <dimen name="bubblebar_hotseat_adjustment_threshold">90dp</dimen>
+    <dimen name="bubblebar_bounce_distance">20dp</dimen>
 
     <dimen name="bubblebar_icon_size_small">32dp</dimen>
     <dimen name="bubblebar_icon_size">36dp</dimen>
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 943c08c..7e52ea1 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -43,17 +43,18 @@
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.popup.PopupDataProvider;
-import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.widget.BaseWidgetSheet;
 import com.android.launcher3.widget.WidgetCell;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
-import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.picker.WidgetsFullSheet;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.function.Function;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
@@ -240,14 +241,16 @@
                     );
             bindWidgets(allWidgets);
             if (mUiSurface != null) {
-                Map<PackageUserKey, List<WidgetItem>> allWidgetsMap = allWidgets.stream()
-                        .filter(WidgetsListHeaderEntry.class::isInstance)
+                Map<ComponentKey, WidgetItem> allWidgetItems = allWidgets.stream()
+                        .filter(entry -> entry instanceof WidgetsListContentEntry)
+                        .flatMap(entry -> entry.mWidgets.stream())
+                        .distinct()
                         .collect(Collectors.toMap(
-                                entry -> PackageUserKey.fromPackageItemInfo(entry.mPkgItem),
-                                entry -> entry.mWidgets)
-                        );
+                                widget -> new ComponentKey(widget.componentName, widget.user),
+                                Function.identity()
+                        ));
                 mWidgetPredictionsRequester = new WidgetPredictionsRequester(app.getContext(),
-                        mUiSurface, allWidgetsMap);
+                        mUiSurface, allWidgetItems);
                 mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets);
             }
         });
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
index 0000c0d..9178062 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
@@ -35,7 +35,7 @@
 
 /** Manage recents related operations with desktop tasks */
 class DesktopRecentsTransitionController(
-    private val stateManager: StateManager<*>,
+    private val stateManager: StateManager<*, *>,
     private val systemUiProxy: SystemUiProxy,
     private val appThread: IApplicationThread,
     private val depthController: DepthController?
@@ -64,7 +64,7 @@
 
     private class RemoteDesktopLaunchTransitionRunner(
         private val desktopTaskView: DesktopTaskView,
-        private val stateManager: StateManager<*>,
+        private val stateManager: StateManager<*, *>,
         private val depthController: DepthController?,
         private val successCallback: Consumer<Boolean>?
     ) : RemoteTransitionStub() {
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 8b5ed7c..6af5a30 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -205,6 +205,7 @@
         mActive = true;
     }
 
+    @WorkerThread
     @Override
     public void workspaceLoadComplete() {
         super.workspaceLoadComplete();
@@ -323,6 +324,7 @@
         }
     }
 
+    @WorkerThread
     @Override
     public void destroy() {
         super.destroy();
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index a7c9652..28bc01c 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -111,6 +111,7 @@
         mWorkerHandler.post(this::initializeInBackground);
     }
 
+    @WorkerThread
     private void initializeInBackground() {
         if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
             mContext.registerReceiver(
@@ -134,8 +135,8 @@
     public void close() {
         if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
             mWorkerHandler.post(() -> {
-                mWellbeingAppChangeReceiver.unregisterReceiverSafely(mContext);
-                mAppAddRemoveReceiver.unregisterReceiverSafely(mContext);
+                mWellbeingAppChangeReceiver.unregisterReceiverSafelySync(mContext);
+                mAppAddRemoveReceiver.unregisterReceiverSafelySync(mContext);
                 mContext.getContentResolver().unregisterContentObserver(mContentObserver);
             });
         }
diff --git a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
index 8431396..5730273 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
@@ -40,7 +40,6 @@
 
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 import com.android.launcher3.widget.picker.WidgetRecommendationCategoryProvider;
 
@@ -67,10 +66,10 @@
     @NonNull
     private final String mUiSurface;
     @NonNull
-    private final Map<PackageUserKey, List<WidgetItem>> mAllWidgets;
+    private final Map<ComponentKey, WidgetItem> mAllWidgets;
 
     public WidgetPredictionsRequester(Context context, @NonNull String uiSurface,
-            @NonNull Map<PackageUserKey, List<WidgetItem>> allWidgets) {
+            @NonNull Map<ComponentKey, WidgetItem> allWidgets) {
         mContext = context;
         mUiSurface = uiSurface;
         mAllWidgets = Collections.unmodifiableMap(allWidgets);
@@ -172,33 +171,19 @@
      */
     @VisibleForTesting
     static List<WidgetItem> filterPredictions(List<AppTarget> predictions,
-            Map<PackageUserKey, List<WidgetItem>> allWidgets, Predicate<WidgetItem> filter) {
+            Map<ComponentKey, WidgetItem> allWidgets, Predicate<WidgetItem> filter) {
         List<WidgetItem> servicePredictedItems = new ArrayList<>();
-        List<WidgetItem> localFilteredWidgets = new ArrayList<>();
 
         for (AppTarget prediction : predictions) {
-            List<WidgetItem> widgetsInPackage = allWidgets.get(
-                    new PackageUserKey(prediction.getPackageName(), prediction.getUser()));
-            if (widgetsInPackage == null || widgetsInPackage.isEmpty()) {
-                continue;
-            }
             String className = prediction.getClassName();
             if (!TextUtils.isEmpty(className)) {
-                WidgetItem item = widgetsInPackage.stream()
-                        .filter(w -> className.equals(w.componentName.getClassName()))
-                        .filter(filter)
-                        .findFirst().orElse(null);
-                if (item != null) {
-                    servicePredictedItems.add(item);
-                    continue;
+                WidgetItem widgetItem = allWidgets.get(
+                        new ComponentKey(new ComponentName(prediction.getPackageName(), className),
+                                prediction.getUser()));
+                if (widgetItem != null && filter.test(widgetItem)) {
+                    servicePredictedItems.add(widgetItem);
                 }
             }
-            // No widget was added by the service, try local filtering
-            widgetsInPackage.stream().filter(filter).findFirst()
-                    .ifPresent(localFilteredWidgets::add);
-        }
-        if (servicePredictedItems.isEmpty()) {
-            servicePredictedItems.addAll(localFilteredWidgets);
         }
 
         return servicePredictedItems;
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index 747612d..4c24d95 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -19,6 +19,7 @@
 import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
 import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
 
 import android.animation.Animator;
@@ -74,8 +75,9 @@
             mOnAttachListener = new View.OnAttachStateChangeListener() {
                 @Override
                 public void onViewAttachedToWindow(View view) {
-                    CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
-                            mCrossWindowBlurListener);
+                    UI_HELPER_EXECUTOR.execute(() ->
+                            CrossWindowBlurListeners.getInstance().addListener(
+                                    mLauncher.getMainExecutor(), mCrossWindowBlurListener));
                     mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener);
 
                     // To handle the case where window token is invalid during last setDepth call.
@@ -108,7 +110,9 @@
 
     private void removeSecondaryListeners() {
         if (mCrossWindowBlurListener != null) {
-            CrossWindowBlurListeners.getInstance().removeListener(mCrossWindowBlurListener);
+            UI_HELPER_EXECUTOR.execute(() ->
+                    CrossWindowBlurListeners.getInstance()
+                            .removeListener(mCrossWindowBlurListener));
         }
         if (mOpaquenessListener != null) {
             mLauncher.getScrimView().removeOpaquenessListener(mOpaquenessListener);
diff --git a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt
deleted file mode 100644
index d4bef28..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsController.kt
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2024 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.taskbar
-
-import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration
-import android.util.Log
-import android.util.SparseArray
-import androidx.annotation.VisibleForTesting
-import androidx.core.util.valueIterator
-import com.android.launcher3.model.data.AppInfo
-import com.android.launcher3.model.data.ItemInfo
-import com.android.launcher3.model.data.WorkspaceItemInfo
-import com.android.launcher3.statehandlers.DesktopVisibilityController
-import com.android.quickstep.RecentsModel
-import kotlin.collections.filterNotNull
-
-/**
- * Shows running apps when in Desktop Mode.
- *
- * Users can enter and exit Desktop Mode at run-time, meaning this class falls back to the default
- * recent-apps behaviour when outside of Desktop Mode.
- *
- * This class should only be used if
- * [com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps] is enabled.
- */
-class DesktopTaskbarRunningAppsController(
-    private val recentsModel: RecentsModel,
-    // Pass a provider here instead of the actual DesktopVisibilityController instance since that
-    // instance might not be available when this constructor is called.
-    private val desktopVisibilityControllerProvider: () -> DesktopVisibilityController?,
-) : TaskbarRecentAppsController() {
-
-    private var apps: Array<AppInfo>? = null
-    private var allRunningDesktopAppInfos: List<AppInfo>? = null
-    private var allMinimizedDesktopAppInfos: List<AppInfo>? = null
-
-    private val desktopVisibilityController: DesktopVisibilityController?
-        get() = desktopVisibilityControllerProvider()
-
-    private val isInDesktopMode: Boolean
-        get() = desktopVisibilityController?.areDesktopTasksVisible() ?: false
-
-    override fun onDestroy() {
-        super.onDestroy()
-        apps = null
-    }
-
-    @VisibleForTesting
-    public override fun setApps(apps: Array<AppInfo>?) {
-        this.apps = apps
-    }
-
-    override fun isEnabled() = true
-
-    @VisibleForTesting
-    public override fun updateHotseatItemInfos(hotseatItems: Array<ItemInfo?>): Array<ItemInfo?> {
-        if (!isInDesktopMode) {
-            Log.d(TAG, "updateHotseatItemInfos: not in Desktop Mode")
-            return hotseatItems
-        }
-        val newHotseatItemInfos =
-            hotseatItems
-                .filterNotNull()
-                // Ignore predicted apps - we show running apps instead
-                .filter { itemInfo -> !itemInfo.isPredictedItem }
-                .toMutableList()
-        val runningDesktopAppInfos =
-            allRunningDesktopAppInfos?.let {
-                getRunningDesktopAppInfosExceptHotseatApps(it, newHotseatItemInfos.toList())
-            }
-        if (runningDesktopAppInfos != null) {
-            newHotseatItemInfos.addAll(runningDesktopAppInfos)
-        }
-        return newHotseatItemInfos.toTypedArray()
-    }
-
-    override fun getRunningApps(): Set<String> {
-        if (!isInDesktopMode) {
-            return emptySet()
-        }
-        return allRunningDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet() ?: emptySet()
-    }
-
-    override fun getMinimizedApps(): Set<String> {
-        if (!isInDesktopMode) {
-            return emptySet()
-        }
-        return allMinimizedDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet() ?: emptySet()
-    }
-
-    @VisibleForTesting
-    public override fun updateRunningApps() {
-        if (!isInDesktopMode) {
-            Log.d(TAG, "updateRunningApps: not in Desktop Mode")
-            mControllers.taskbarViewController.commitRunningAppsToUI()
-            return
-        }
-        val runningTasks = getDesktopRunningTasks()
-        val runningAppInfo = getAppInfosFromRunningTasks(runningTasks)
-        allRunningDesktopAppInfos = runningAppInfo
-        updateMinimizedApps(runningTasks, runningAppInfo)
-        mControllers.taskbarViewController.commitRunningAppsToUI()
-    }
-
-    private fun updateMinimizedApps(
-        runningTasks: List<RunningTaskInfo>,
-        runningAppInfo: List<AppInfo>,
-    ) {
-        val allRunningAppTasks =
-            runningAppInfo
-                .mapNotNull { appInfo -> appInfo.targetPackage?.let { appInfo to it } }
-                .associate { (appInfo, targetPackage) ->
-                    appInfo to
-                        runningTasks
-                            .filter { it.realActivity?.packageName == targetPackage }
-                            .map { it.taskId }
-                }
-        val minimizedTaskIds = runningTasks.associate { it.taskId to !it.isVisible }
-        allMinimizedDesktopAppInfos =
-            allRunningAppTasks
-                .filterValues { taskIds -> taskIds.all { minimizedTaskIds[it] ?: false } }
-                .keys
-                .toList()
-    }
-
-    private fun getRunningDesktopAppInfosExceptHotseatApps(
-        allRunningDesktopAppInfos: List<AppInfo>,
-        hotseatItems: List<ItemInfo>
-    ): List<ItemInfo> {
-        val hotseatPackages = hotseatItems.map { it.targetPackage }
-        return allRunningDesktopAppInfos
-            .filter { appInfo -> !hotseatPackages.contains(appInfo.targetPackage) }
-            .map { WorkspaceItemInfo(it) }
-    }
-
-    private fun getDesktopRunningTasks(): List<RunningTaskInfo> =
-        recentsModel.runningTasks.filter { taskInfo: RunningTaskInfo ->
-            taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM
-        }
-
-    // TODO(b/335398876) fetch app icons from Tasks instead of AppInfos
-    private fun getAppInfosFromRunningTasks(tasks: List<RunningTaskInfo>): List<AppInfo> {
-        // Early return if apps is empty, since we then have no AppInfo to compare to
-        if (apps == null) {
-            return emptyList()
-        }
-        val packageNames = tasks.map { it.realActivity?.packageName }.distinct().filterNotNull()
-        return packageNames
-            .map { packageName -> apps?.find { app -> packageName == app.targetPackage } }
-            .filterNotNull()
-    }
-
-    private fun getAppInfosFromRunningTask(task: RunningTaskInfo): AppInfo? =
-        apps?.firstOrNull { it.targetPackage == task.realActivity?.packageName }
-
-    private fun <E> SparseArray<E>.toList(): List<E> = valueIterator().asSequence().toList()
-
-    companion object {
-        private const val TAG = "TabletDesktopTaskbarRunningAppsController"
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index d6ee92f..73819b3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -15,12 +15,7 @@
  */
 package com.android.launcher3.taskbar;
 
-import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
-
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
 import android.animation.Animator;
-import android.app.ActivityOptions;
 import android.view.KeyEvent;
 import android.view.animation.AnimationUtils;
 import android.window.RemoteTransition;
@@ -31,13 +26,10 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
-import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.SlideInRemoteTransition;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 
 import java.io.PrintWriter;
@@ -158,28 +150,8 @@
                 AnimationUtils.loadInterpolator(
                         context, android.R.interpolator.fast_out_extra_slow_in)),
                 "SlideInTransition");
-        if (task instanceof DesktopTask) {
-            UI_HELPER_EXECUTOR.execute(() ->
-                    SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
-                            .showDesktopApps(
-                                    mKeyboardQuickSwitchView.getDisplay().getDisplayId(),
-                                    remoteTransition));
-        } else if (mOnDesktop) {
-            UI_HELPER_EXECUTOR.execute(() ->
-                    SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
-                            .showDesktopApp(task.task1.key.id));
-        } else if (task.task2 == null) {
-            UI_HELPER_EXECUTOR.execute(() -> {
-                ActivityOptions activityOptions = mControllers.taskbarActivityContext
-                        .makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED).options;
-                activityOptions.setRemoteTransition(remoteTransition);
-
-                ActivityManagerWrapper.getInstance().startActivityFromRecents(
-                        task.task1.key, activityOptions);
-            });
-        } else {
-            mControllers.uiController.launchSplitTasks(task, remoteTransition);
-        }
+        mControllers.taskbarActivityContext.handleGroupTaskLaunch(
+                task, remoteTransition, mOnDesktop);
         return -1;
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 81581b8..63e1e01 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -47,6 +47,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SHORTCUT_HELPER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING;
+import static com.android.wm.shell.Flags.enableTaskbarOnPhones;
 
 import android.animation.ArgbEvaluator;
 import android.animation.ObjectAnimator;
@@ -678,14 +679,19 @@
                 mLightIconColorOnHome,
                 mDarkIconColorOnHome);
 
-        // Override the color from framework if nav buttons are over an opaque Taskbar surface.
-        final int iconColor = (int) argbEvaluator.evaluate(
-                mOnBackgroundNavButtonColorOverrideMultiplier.value
-                        * Math.max(
-                                mOnTaskbarBackgroundNavButtonColorOverride.value,
-                                mSlideInViewVisibleNavButtonColorOverride.value),
-                sysUiNavButtonIconColorOnHome,
-                mOnBackgroundIconColor);
+        final int iconColor;
+        if (ENABLE_TASKBAR_NAVBAR_UNIFICATION && enableTaskbarOnPhones()
+                && mContext.isPhoneMode()) {
+            iconColor = sysUiNavButtonIconColorOnHome;
+        } else {
+            // Override the color from framework if nav buttons are over an opaque Taskbar surface.
+            iconColor = (int) argbEvaluator.evaluate(
+                    mOnBackgroundNavButtonColorOverrideMultiplier.value * Math.max(
+                            mOnTaskbarBackgroundNavButtonColorOverride.value,
+                            mSlideInViewVisibleNavButtonColorOverride.value),
+                    sysUiNavButtonIconColorOnHome,
+                    mOnBackgroundIconColor);
+        }
 
         for (ImageView button : mAllButtons) {
             button.setImageTintList(ColorStateList.valueOf(iconColor));
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 0de0550..5020206 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -41,8 +41,6 @@
 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.enableDesktopWindowingMode;
-import static com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps;
 import static com.android.wm.shell.Flags.enableTinyTaskbar;
 
 import android.animation.AnimatorSet;
@@ -64,12 +62,12 @@
 import android.util.Log;
 import android.view.Display;
 import android.view.Gravity;
-import android.view.RoundedCorner;
 import android.view.Surface;
 import android.view.View;
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.widget.Toast;
+import android.window.RemoteTransition;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -134,6 +132,9 @@
 import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.NavHandle;
 import com.android.quickstep.RecentsModel;
+import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.DesktopTask;
+import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
@@ -168,7 +169,6 @@
     private final TaskbarControllers mControllers;
 
     private final WindowManager mWindowManager;
-    private final @Nullable RoundedCorner mLeftCorner, mRightCorner;
     private DeviceProfile mDeviceProfile;
     private WindowManager.LayoutParams mWindowLayoutParams;
     private boolean mIsFullscreen;
@@ -229,16 +229,8 @@
         Context c = getApplicationContext();
         mWindowManager = c.getSystemService(WindowManager.class);
 
-        boolean phoneMode = isPhoneMode();
-        mLeftCorner = phoneMode
-                ? null
-                : display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
-        mRightCorner = phoneMode
-                ? null
-                : display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
-
         // Inflate views.
-        int taskbarLayout = DisplayController.isTransientTaskbar(this) && !phoneMode
+        int taskbarLayout = DisplayController.isTransientTaskbar(this) && !isPhoneMode()
                 ? R.layout.transient_taskbar
                 : R.layout.taskbar;
         mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(taskbarLayout, null, false);
@@ -304,26 +296,18 @@
                 new VoiceInteractionWindowController(this),
                 new TaskbarTranslationController(this),
                 new TaskbarSpringOnStashController(this),
-                createTaskbarRecentAppsController(),
+                new TaskbarRecentAppsController(
+                        RecentsModel.INSTANCE.get(this),
+                        LauncherActivityInterface.INSTANCE::getDesktopVisibilityController),
                 TaskbarEduTooltipController.newInstance(this),
                 new KeyboardQuickSwitchController(),
                 new TaskbarPinningController(this, () ->
-                        DisplayController.INSTANCE.get(this).getInfo().isInDesktopMode()),
+                        DisplayController.isInDesktopMode(this)),
                 bubbleControllersOptional);
 
         mLauncherPrefs = LauncherPrefs.get(this);
     }
 
-    private TaskbarRecentAppsController createTaskbarRecentAppsController() {
-        // TODO(b/335401172): unify DesktopMode checks in Launcher
-        if (enableDesktopWindowingMode() && enableDesktopWindowingTaskbarRunningApps()) {
-            return new DesktopTaskbarRunningAppsController(
-                    RecentsModel.INSTANCE.get(this),
-                    LauncherActivityInterface.INSTANCE::getDesktopVisibilityController);
-        }
-        return TaskbarRecentAppsController.DEFAULT;
-    }
-
     /** Updates {@link DeviceProfile} instances for any Taskbar windows. */
     public void updateDeviceProfile(DeviceProfile launcherDp) {
         applyDeviceProfile(launcherDp);
@@ -625,12 +609,9 @@
         return mImeDrawsImeNavBar;
     }
 
-    public int getLeftCornerRadius() {
-        return mLeftCorner == null ? 0 : mLeftCorner.getRadius();
-    }
-
-    public int getRightCornerRadius() {
-        return mRightCorner == null ? 0 : mRightCorner.getRadius();
+    public int getCornerRadius() {
+        return isPhoneMode() ? 0 : getResources().getDimensionPixelSize(
+                R.dimen.persistent_taskbar_corner_radius);
     }
 
     public WindowManager.LayoutParams getWindowLayoutParams() {
@@ -1023,7 +1004,7 @@
 
 
         return mDeviceProfile.taskbarHeight
-                + Math.max(getLeftCornerRadius(), getRightCornerRadius())
+                + getCornerRadius()
                 + extraHeightForTaskbarTooltips;
     }
 
@@ -1104,10 +1085,9 @@
         RecentsView recents = taskbarUIController.getRecentsView();
         boolean shouldCloseAllOpenViews = true;
         Object tag = view.getTag();
-        if (tag instanceof Task) {
-            Task task = (Task) tag;
-            ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key,
-                    ActivityOptions.makeBasic());
+        if (tag instanceof GroupTask groupTask) {
+            handleGroupTaskLaunch(groupTask, /* remoteTransition = */ null,
+                    DisplayController.isInDesktopMode(this));
             mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
         } else if (tag instanceof FolderInfo) {
             // Tapping an expandable folder icon on Taskbar
@@ -1208,6 +1188,36 @@
     }
 
     /**
+     * Launches the given GroupTask with the following behavior:
+     * - If the GroupTask is a DesktopTask, launch the tasks in that Desktop.
+     * - If {@code onDesktop}, bring the given GroupTask to the front.
+     * - If the GroupTask is a single task, launch it via startActivityFromRecents.
+     * - Otherwise, we assume the GroupTask is a Split pair and launch them together.
+     */
+    public void handleGroupTaskLaunch(GroupTask task, @Nullable RemoteTransition remoteTransition,
+            boolean onDesktop) {
+        if (task instanceof DesktopTask) {
+            UI_HELPER_EXECUTOR.execute(() ->
+                    SystemUiProxy.INSTANCE.get(this).showDesktopApps(getDisplay().getDisplayId(),
+                            remoteTransition));
+        } else if (onDesktop) {
+            UI_HELPER_EXECUTOR.execute(() ->
+                    SystemUiProxy.INSTANCE.get(this).showDesktopApp(task.task1.key.id));
+        } else if (task.task2 == null) {
+            UI_HELPER_EXECUTOR.execute(() -> {
+                ActivityOptions activityOptions =
+                        makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED).options;
+                activityOptions.setRemoteTransition(remoteTransition);
+
+                ActivityManagerWrapper.getInstance().startActivityFromRecents(
+                        task.task1.key, activityOptions);
+            });
+        } else {
+            mControllers.uiController.launchSplitTasks(task, remoteTransition);
+        }
+    }
+
+    /**
      * Runs when the user taps a Taskbar icon in TaskbarActivityContext (Overview or inside an app),
      * and calls the appropriate method to animate and launch.
      */
@@ -1406,6 +1416,13 @@
     }
 
     /**
+     * Plays the taskbar background alpha animation if one is not currently playing.
+     */
+    public void playTaskbarBackgroundAlphaAnimation() {
+        mControllers.taskbarStashController.playTaskbarBackgroundAlphaAnimation();
+    }
+
+    /**
      * Called to start the taskbar translation spring to its settled translation (0).
      */
     public void startTranslationSpring() {
@@ -1605,4 +1622,9 @@
     boolean canToggleHomeAllApps() {
         return mControllers.uiController.canToggleHomeAllApps();
     }
+
+    @VisibleForTesting
+    public TaskbarControllers getControllers() {
+        return mControllers;
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index bafd059..2737cbd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -37,8 +37,6 @@
 class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
 
     private val isInSetup: Boolean = !context.isUserSetupComplete
-    private val DARK_THEME_SHADOW_ALPHA = 51f
-    private val LIGHT_THEME_SHADOW_ALPHA = 25f
 
     private val maxTransientTaskbarHeight =
         context.transientTaskbarDeviceProfile.taskbarHeight.toFloat()
@@ -54,6 +52,7 @@
     var isAnimatingPinning = false
 
     val paint = Paint()
+    private val strokePaint = Paint()
     val lastDrawnTransientRect = RectF()
     var backgroundHeight = context.deviceProfile.taskbarHeight.toFloat()
     var translationYForSwipe = 0f
@@ -62,19 +61,18 @@
     private val transientBackgroundBounds = context.transientTaskbarBounds
 
     private val shadowAlpha: Float
+    private val strokeAlpha: Int
     private var shadowBlur = 0f
     private var keyShadowDistance = 0f
     private var bottomMargin = 0
 
-    private val fullLeftCornerRadius = context.leftCornerRadius.toFloat()
-    private val fullRightCornerRadius = context.rightCornerRadius.toFloat()
-    private var leftCornerRadius = fullLeftCornerRadius
-    private var rightCornerRadius = fullRightCornerRadius
+    private val fullCornerRadius = context.cornerRadius.toFloat()
+    private var cornerRadius = fullCornerRadius
     private var widthInsetPercentage = 0f
-    private val square: Path = Path()
-    private val circle: Path = Path()
-    private val invertedLeftCornerPath: Path = Path()
-    private val invertedRightCornerPath: Path = Path()
+    private val square = Path()
+    private val circle = Path()
+    private val invertedLeftCornerPath = Path()
+    private val invertedRightCornerPath = Path()
 
     private var stashedHandleWidth =
         context.resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width)
@@ -86,10 +84,18 @@
         paint.color = context.getColor(R.color.taskbar_background)
         paint.flags = Paint.ANTI_ALIAS_FLAG
         paint.style = Paint.Style.FILL
-
-        shadowAlpha =
-            if (Utilities.isDarkTheme(context)) DARK_THEME_SHADOW_ALPHA
-            else LIGHT_THEME_SHADOW_ALPHA
+        strokePaint.color = context.getColor(R.color.taskbar_stroke)
+        strokePaint.flags = Paint.ANTI_ALIAS_FLAG
+        strokePaint.style = Paint.Style.STROKE
+        strokePaint.strokeWidth =
+            context.resources.getDimension(R.dimen.transient_taskbar_stroke_width)
+        if (Utilities.isDarkTheme(context)) {
+            strokeAlpha = DARK_THEME_STROKE_ALPHA
+            shadowAlpha = DARK_THEME_SHADOW_ALPHA
+        } else {
+            strokeAlpha = LIGHT_THEME_STROKE_ALPHA
+            shadowAlpha = LIGHT_THEME_SHADOW_ALPHA
+        }
 
         setCornerRoundness(DEFAULT_ROUNDNESS)
     }
@@ -106,7 +112,7 @@
     }
 
     /**
-     * Sets the roundness of the round corner above Taskbar. No effect on transient Taskkbar.
+     * Sets the roundness of the round corner above Taskbar. No effect on transient Taskbar.
      *
      * @param cornerRoundness 0 has no round corner, 1 has complete round corner.
      */
@@ -115,21 +121,18 @@
             return
         }
 
-        leftCornerRadius = fullLeftCornerRadius * cornerRoundness
-        rightCornerRadius = fullRightCornerRadius * cornerRoundness
+        cornerRadius = fullCornerRadius * cornerRoundness
 
         // Create the paths for the inverted rounded corners above the taskbar. Start with a filled
         // square, and then subtract out a circle from the appropriate corner.
         square.reset()
-        square.addRect(0f, 0f, leftCornerRadius, leftCornerRadius, Path.Direction.CW)
+        square.addRect(0f, 0f, cornerRadius, cornerRadius, Path.Direction.CW)
         circle.reset()
-        circle.addCircle(leftCornerRadius, 0f, leftCornerRadius, Path.Direction.CW)
+        circle.addCircle(cornerRadius, 0f, cornerRadius, Path.Direction.CW)
         invertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE)
 
-        square.reset()
-        square.addRect(0f, 0f, rightCornerRadius, rightCornerRadius, Path.Direction.CW)
         circle.reset()
-        circle.addCircle(0f, 0f, rightCornerRadius, Path.Direction.CW)
+        circle.addCircle(0f, 0f, cornerRadius, Path.Direction.CW)
         invertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE)
     }
 
@@ -163,10 +166,10 @@
         }
 
         // Draw the inverted rounded corners above the taskbar.
-        canvas.translate(0f, -leftCornerRadius)
+        canvas.translate(0f, -cornerRadius)
         canvas.drawPath(invertedLeftCornerPath, paint)
-        canvas.translate(0f, leftCornerRadius)
-        canvas.translate(canvas.width - rightCornerRadius, -rightCornerRadius)
+        canvas.translate(0f, cornerRadius)
+        canvas.translate(canvas.width - cornerRadius, -cornerRadius)
         canvas.drawPath(invertedRightCornerPath, paint)
     }
 
@@ -236,6 +239,7 @@
             keyShadowDistance,
             setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
         )
+        strokePaint.alpha = (paint.alpha * strokeAlpha) / 255
 
         lastDrawnTransientRect.set(
             transientBackgroundBounds.left + halfWidthDelta,
@@ -247,6 +251,7 @@
         lastDrawnTransientRect.inset(horizontalInset, 0f)
 
         canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, paint)
+        canvas.drawRoundRect(lastDrawnTransientRect, radius, radius, strokePaint)
     }
 
     /**
@@ -259,5 +264,9 @@
 
     companion object {
         const val DEFAULT_ROUNDNESS = 1f
+        private const val DARK_THEME_STROKE_ALPHA = 51
+        private const val LIGHT_THEME_STROKE_ALPHA = 41
+        private const val DARK_THEME_SHADOW_ALPHA = 51f
+        private const val LIGHT_THEME_SHADOW_ALPHA = 25f
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index f9ddc3d..58c5e83 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -180,8 +180,9 @@
                 taskbarUnfoldAnimationController, taskbarKeyguardController,
                 stashedHandleViewController, taskbarStashController,
                 taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController,
-                voiceInteractionWindowController, taskbarTranslationController,
-                taskbarEduTooltipController, keyboardQuickSwitchController, taskbarPinningController
+                voiceInteractionWindowController, taskbarRecentAppsController,
+                taskbarTranslationController, taskbarEduTooltipController,
+                keyboardQuickSwitchController, taskbarPinningController,
         };
         mBackgroundRendererControllers = new BackgroundRendererController[] {
                 taskbarDragLayerController, taskbarScrimViewController,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 4f5922c..efe42fb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -82,6 +82,7 @@
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.views.BubbleTextHolder;
 import com.android.quickstep.LauncherActivityInterface;
+import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LogUtils;
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.systemui.shared.recents.model.Task;
@@ -181,7 +182,9 @@
 
     private DragView startInternalDrag(
             BubbleTextView btv, @Nullable DragPreviewProvider dragPreviewProvider) {
-        float iconScale = btv.getIcon().getAnimatedScale();
+        // TODO(b/344038728): null check is only necessary because Recents doesn't use
+        //  FastBitmapDrawable
+        float iconScale = btv.getIcon() == null ? 1f : btv.getIcon().getAnimatedScale();
 
         // Clear the pressed state if necessary
         btv.clearFocus();
@@ -248,7 +251,7 @@
                 dragLayerX + dragOffset.x,
                 dragLayerY + dragOffset.y,
                 (View target, DropTarget.DragObject d, boolean success) -> {} /* DragSource */,
-                (ItemInfo) btv.getTag(),
+                btv.getTag() instanceof ItemInfo itemInfo ? itemInfo : null,
                 dragRect,
                 scale * iconScale,
                 scale,
@@ -288,7 +291,9 @@
                 initialDragViewScale,
                 dragViewScaleOnDrop,
                 scalePx);
-        dragView.setItemInfo(dragInfo);
+        if (dragInfo != null) {
+            dragView.setItemInfo(dragInfo);
+        }
         mDragObject.dragComplete = false;
 
         mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft);
@@ -301,7 +306,8 @@
 
         mDragObject.dragSource = source;
         mDragObject.dragInfo = dragInfo;
-        mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();
+        mDragObject.originalDragInfo =
+                mDragObject.dragInfo != null ? mDragObject.dragInfo.makeShallowCopy() : null;
 
         if (mOptions.preDragCondition != null) {
             dragView.setHasDragOffset(mOptions.preDragCondition.getDragOffset().x != 0
@@ -431,8 +437,8 @@
                                 null, item.user));
             }
             intent.putExtra(Intent.EXTRA_USER, item.user);
-        } else if (tag instanceof Task) {
-            Task task = (Task) tag;
+        } else if (tag instanceof GroupTask groupTask && !groupTask.hasMultipleTasks()) {
+            Task task = groupTask.task1;
             clipDescription = new ClipDescription(task.titleDescription,
                     new String[] {
                             ClipDescription.MIMETYPE_APPLICATION_TASK
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 84874a9..a9b34d2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -104,7 +104,6 @@
         mTaskbarBackgroundAlpha = new MultiPropertyFactory<>(this, BG_ALPHA, INDEX_COUNT,
                 (a, b) -> a * b, 1f);
         mTaskbarBackgroundAlpha.get(INDEX_ALL_OTHER_STATES).setValue(0);
-        mTaskbarBackgroundAlpha.get(INDEX_STASH_ANIM).setValue(1);
     }
 
     public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 0b60e45..2103ebb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -53,6 +53,7 @@
 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
 import com.android.launcher3.testing.shared.ResourceUtils
 import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.Executors
 import java.io.PrintWriter
 import kotlin.jvm.optionals.getOrNull
 import kotlin.math.max
@@ -76,6 +77,7 @@
     private val gestureNavSettingsObserver =
         GestureNavigationSettingsObserver(
             context.mainThreadHandler,
+            Executors.UI_HELPER_EXECUTOR.handler,
             context,
             this::onTaskbarOrBubblebarWindowHeightOrInsetsChanged
         )
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
index eac4eaa..911140a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarKeyguardController.java
@@ -5,9 +5,6 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK;
@@ -16,6 +13,7 @@
 import android.app.KeyguardManager;
 
 import com.android.launcher3.AbstractFloatingView;
+import com.android.quickstep.util.SystemUiFlagUtils;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 
@@ -26,19 +24,6 @@
  */
 public class TaskbarKeyguardController implements TaskbarControllers.LoggableTaskbarController {
 
-    private static final long KEYGUARD_SYSUI_FLAGS = SYSUI_STATE_BOUNCER_SHOWING
-            | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING | SYSUI_STATE_DEVICE_DOZING
-            | SYSUI_STATE_OVERVIEW_DISABLED | SYSUI_STATE_HOME_DISABLED
-            | SYSUI_STATE_BACK_DISABLED | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
-            | SYSUI_STATE_WAKEFULNESS_MASK;
-
-    // If any of these SysUi flags (via QuickstepContract) is set, the device to be considered
-    // locked.
-    public static final long MASK_ANY_SYSUI_LOCKED = SYSUI_STATE_BOUNCER_SHOWING
-            | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
-            | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
-            | SYSUI_STATE_DEVICE_DREAMING;
-
     private final TaskbarActivityContext mContext;
     private long mKeyguardSysuiFlags;
     private boolean mBouncerShowing;
@@ -55,7 +40,7 @@
     }
 
     public void updateStateForSysuiFlags(@SystemUiStateFlags long systemUiStateFlags) {
-        long interestingKeyguardFlags = systemUiStateFlags & KEYGUARD_SYSUI_FLAGS;
+        long interestingKeyguardFlags = systemUiStateFlags & SystemUiFlagUtils.KEYGUARD_SYSUI_FLAGS;
         if (interestingKeyguardFlags == mKeyguardSysuiFlags) {
             // No change in keyguard relevant flags
             return;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index ead1a8a..cb9f24a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.taskbar;
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
-import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_OVERVIEW;
 import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
@@ -51,6 +50,7 @@
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.RecentsAnimationController;
+import com.android.quickstep.util.SystemUiFlagUtils;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.animation.ViewRootSync;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -343,8 +343,7 @@
                     prevIsAwake && hasAnyFlag(FLAGS_LAUNCHER_ACTIVE));
         }
 
-        boolean isDeviceLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED);
-        updateStateForFlag(FLAG_DEVICE_LOCKED, isDeviceLocked);
+        updateStateForFlag(FLAG_DEVICE_LOCKED, SystemUiFlagUtils.isLocked(systemUiStateFlags));
 
         // Taskbar is hidden whenever the device is dreaming. The dreaming state includes the
         // interactive dreams, AoD, screen off. Since the SYSUI_STATE_DEVICE_DREAMING only kicks in
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index ec2cee2..051bdc8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -304,7 +304,7 @@
                 .register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
         Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
         mContext.registerComponentCallbacks(mComponentCallbacks);
-        mShutdownReceiver.register(mContext, Intent.ACTION_SHUTDOWN);
+        mShutdownReceiver.registerAsync(mContext, Intent.ACTION_SHUTDOWN);
         UI_HELPER_EXECUTOR.execute(() -> {
             mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast(
                     mContext,
@@ -582,8 +582,7 @@
     public void destroy() {
         debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
         removeActivityCallbacksAndListeners();
-        UI_HELPER_EXECUTOR.execute(
-                () -> mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext));
+        mTaskbarBroadcastReceiver.unregisterReceiverSafelyAsync(mContext);
         destroyExistingTaskbar();
         removeTaskbarRootViewFromWindow();
         if (mUserUnlocked) {
@@ -595,7 +594,7 @@
                 .unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
         Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
         mContext.unregisterComponentCallbacks(mComponentCallbacks);
-        mContext.unregisterReceiver(mShutdownReceiver);
+        mShutdownReceiver.unregisterReceiverSafelyAsync(mContext);
     }
 
     public @Nullable TaskbarActivityContext getCurrentActivityContext() {
@@ -611,7 +610,8 @@
         }
     }
 
-    private void addTaskbarRootViewToWindow() {
+    @VisibleForTesting
+    void addTaskbarRootViewToWindow() {
         if (enableTaskbarNoRecreate() && !mAddedWindow && mTaskbarActivityContext != null) {
             mWindowManager.addView(mTaskbarRootLayout,
                     mTaskbarActivityContext.getWindowLayoutParams());
@@ -619,7 +619,8 @@
         }
     }
 
-    private void removeTaskbarRootViewFromWindow() {
+    @VisibleForTesting
+    void removeTaskbarRootViewFromWindow() {
         if (enableTaskbarNoRecreate() && mAddedWindow) {
             mWindowManager.removeViewImmediate(mTaskbarRootLayout);
             mAddedWindow = false;
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 35e1c7b..0b7ae39 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -15,9 +15,6 @@
  */
 package com.android.launcher3.taskbar;
 
-import static com.android.window.flags.Flags.enableDesktopWindowingMode;
-import static com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps;
-
 import android.util.SparseArray;
 import android.view.View;
 
@@ -29,7 +26,6 @@
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
@@ -37,8 +33,7 @@
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.LauncherActivityInterface;
-import com.android.quickstep.RecentsModel;
+import com.android.quickstep.util.GroupTask;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -54,7 +49,7 @@
  * Launcher model Callbacks for rendering taskbar.
  */
 public class TaskbarModelCallbacks implements
-        BgDataModel.Callbacks, LauncherBindableItemsContainer, RecentsModel.RunningTasksListener {
+        BgDataModel.Callbacks, LauncherBindableItemsContainer {
 
     private final SparseArray<ItemInfo> mHotseatItems = new SparseArray<>();
     private List<ItemInfo> mPredictedItems = Collections.emptyList();
@@ -68,8 +63,6 @@
     // Used to defer any UI updates during the SUW unstash animation.
     private boolean mDeferUpdatesForSUW;
     private Runnable mDeferredUpdates;
-    private final DesktopVisibilityController.DesktopVisibilityListener mDesktopVisibilityListener =
-            visible -> updateRunningApps();
 
     public TaskbarModelCallbacks(
             TaskbarActivityContext context, TaskbarView container) {
@@ -79,39 +72,6 @@
 
     public void init(TaskbarControllers controllers) {
         mControllers = controllers;
-        if (mControllers.taskbarRecentAppsController.isEnabled()) {
-            RecentsModel.INSTANCE.get(mContext).registerRunningTasksListener(this);
-
-            if (shouldShowRunningAppsInDesktopMode()) {
-                DesktopVisibilityController desktopVisibilityController =
-                        LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
-                if (desktopVisibilityController != null) {
-                    desktopVisibilityController.registerDesktopVisibilityListener(
-                            mDesktopVisibilityListener);
-                }
-            }
-        }
-    }
-
-    /**
-     * Unregisters listeners in this class.
-     */
-    public void unregisterListeners() {
-        RecentsModel.INSTANCE.get(mContext).unregisterRunningTasksListener();
-
-        if (shouldShowRunningAppsInDesktopMode()) {
-            DesktopVisibilityController desktopVisibilityController =
-                    LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
-            if (desktopVisibilityController != null) {
-                desktopVisibilityController.unregisterDesktopVisibilityListener(
-                        mDesktopVisibilityListener);
-            }
-        }
-    }
-
-    private boolean shouldShowRunningAppsInDesktopMode() {
-        // TODO(b/335401172): unify DesktopMode checks in Launcher
-        return enableDesktopWindowingMode() && enableDesktopWindowingTaskbarRunningApps();
     }
 
     @Override
@@ -171,7 +131,7 @@
         final int itemCount = mContainer.getChildCount();
         for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
             View item = mContainer.getChildAt(itemIdx);
-            if (op.evaluate((ItemInfo) item.getTag(), item)) {
+            if (item.getTag() instanceof ItemInfo itemInfo && op.evaluate(itemInfo, item)) {
                 return;
             }
         }
@@ -232,26 +192,30 @@
                 predictionNextIndex++;
             }
         }
-        hotseatItemInfos = mControllers.taskbarRecentAppsController
-                .updateHotseatItemInfos(hotseatItemInfos);
-        Set<String> runningPackages = mControllers.taskbarRecentAppsController.getRunningApps();
-        Set<String> minimizedPackages = mControllers.taskbarRecentAppsController.getMinimizedApps();
+
+        final TaskbarRecentAppsController recentAppsController =
+                mControllers.taskbarRecentAppsController;
+        hotseatItemInfos = recentAppsController.updateHotseatItemInfos(hotseatItemInfos);
+        Set<String> runningPackages = recentAppsController.getRunningAppPackages();
+        Set<String> minimizedPackages = recentAppsController.getMinimizedAppPackages();
 
         if (mDeferUpdatesForSUW) {
             ItemInfo[] finalHotseatItemInfos = hotseatItemInfos;
             mDeferredUpdates = () ->
-                    commitHotseatItemUpdates(finalHotseatItemInfos, runningPackages,
+                    commitHotseatItemUpdates(finalHotseatItemInfos,
+                            recentAppsController.getShownTasks(), runningPackages,
                             minimizedPackages);
         } else {
-            commitHotseatItemUpdates(hotseatItemInfos, runningPackages, minimizedPackages);
+            commitHotseatItemUpdates(hotseatItemInfos,
+                    recentAppsController.getShownTasks(), runningPackages, minimizedPackages);
         }
     }
 
-    private void commitHotseatItemUpdates(ItemInfo[] hotseatItemInfos, Set<String> runningPackages,
-            Set<String> minimizedPackages) {
-        mContainer.updateHotseatItems(hotseatItemInfos);
-        mControllers.taskbarViewController.updateIconViewsRunningStates(runningPackages,
-                minimizedPackages);
+    private void commitHotseatItemUpdates(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks,
+            Set<String> runningPackages, Set<String> minimizedPackages) {
+        mContainer.updateHotseatItems(hotseatItemInfos, recentTasks);
+        mControllers.taskbarViewController.updateIconViewsRunningStates(
+                runningPackages, minimizedPackages);
     }
 
     /**
@@ -270,21 +234,11 @@
         }
     }
 
-    @Override
-    public void onRunningTasksChanged() {
-        updateRunningApps();
-    }
-
     /** Called when there's a change in running apps to update the UI. */
     public void commitRunningAppsToUI() {
         commitItemsToUI();
     }
 
-    /** Call TaskbarRecentAppsController to update running apps with mHotseatItems. */
-    public void updateRunningApps() {
-        mControllers.taskbarRecentAppsController.updateRunningApps();
-    }
-
     @Override
     public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
         mControllers.taskbarPopupController.setDeepShortcutMap(deepShortcutMapCopy);
@@ -296,7 +250,6 @@
             Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
         Preconditions.assertUIThread();
         mControllers.taskbarAllAppsController.setApps(apps, flags, packageUserKeytoUidMap);
-        mControllers.taskbarRecentAppsController.setApps(apps);
     }
 
     protected void dumpLogs(String prefix, PrintWriter pw) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 2730be1..b697590 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -148,8 +148,8 @@
             icon.clearFocus();
             return null;
         }
-        ItemInfo item = (ItemInfo) icon.getTag();
-        if (!ShortcutUtil.supportsShortcuts(item)) {
+        // TODO(b/344657629) support GroupTask as well, for Taskbar Recent apps
+        if (!(icon.getTag() instanceof ItemInfo item) || !ShortcutUtil.supportsShortcuts(item)) {
             return null;
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java
deleted file mode 100644
index 606ba5b..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.taskbar;
-
-import static java.util.Collections.emptySet;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
-
-import java.util.Set;
-
-/**
- * Base class for providing recent apps functionality
- */
-public class TaskbarRecentAppsController {
-
-    public static final TaskbarRecentAppsController DEFAULT = new TaskbarRecentAppsController();
-
-    // Initialized in init.
-    protected TaskbarControllers mControllers;
-
-    @CallSuper
-    protected void init(TaskbarControllers taskbarControllers) {
-        mControllers = taskbarControllers;
-    }
-
-    @CallSuper
-    protected void onDestroy() {
-        mControllers = null;
-    }
-
-    /** Stores the current {@link AppInfo} instances, no-op except in desktop environment. */
-    protected void setApps(AppInfo[] apps) {
-    }
-
-    /**
-     * Indicates whether recent apps functionality is enabled, should return false except in
-     * desktop environment.
-     */
-    protected boolean isEnabled() {
-        return false;
-    }
-
-    /** Called to update hotseatItems, no-op except in desktop environment. */
-    protected ItemInfo[] updateHotseatItemInfos(@NonNull ItemInfo[] hotseatItems) {
-        return hotseatItems;
-    }
-
-    /** Called to update the list of currently running apps, no-op except in desktop environment. */
-    protected void updateRunningApps() {}
-
-    /** Returns the currently running apps, or an empty Set if outside of Desktop environment. */
-    public Set<String> getRunningApps() {
-        return emptySet();
-    }
-
-    /** Returns the set of apps whose tasks are all minimized. */
-    public Set<String> getMinimizedApps() {
-        return emptySet();
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
new file mode 100644
index 0000000..fc3b4c7
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2024 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.taskbar
+
+import androidx.annotation.VisibleForTesting
+import com.android.launcher3.Flags.enableRecentsInTaskbar
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.statehandlers.DesktopVisibilityController
+import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
+import com.android.launcher3.util.CancellableTask
+import com.android.quickstep.RecentsModel
+import com.android.quickstep.util.DesktopTask
+import com.android.quickstep.util.GroupTask
+import com.android.systemui.shared.recents.model.Task
+import com.android.window.flags.Flags.enableDesktopWindowingMode
+import com.android.window.flags.Flags.enableDesktopWindowingTaskbarRunningApps
+import java.io.PrintWriter
+import java.util.function.Consumer
+
+/**
+ * Provides recent apps functionality, when the Taskbar Recent Apps section is enabled. Behavior:
+ * - When in Fullscreen mode: show the N most recent Tasks
+ * - When in Desktop Mode: show the currently running (open) Tasks
+ */
+class TaskbarRecentAppsController(
+    private val recentsModel: RecentsModel,
+    // Pass a provider here instead of the actual DesktopVisibilityController instance since that
+    // instance might not be available when this constructor is called.
+    private val desktopVisibilityControllerProvider: () -> DesktopVisibilityController?,
+) : LoggableTaskbarController {
+
+    // TODO(b/335401172): unify DesktopMode checks in Launcher.
+    var canShowRunningApps =
+        enableDesktopWindowingMode() && enableDesktopWindowingTaskbarRunningApps()
+        @VisibleForTesting
+        set(isEnabledFromTest) {
+            field = isEnabledFromTest
+        }
+
+    // TODO(b/343532825): Add a setting to disable Recents even when the flag is on.
+    var canShowRecentApps = enableRecentsInTaskbar()
+        @VisibleForTesting
+        set(isEnabledFromTest) {
+            field = isEnabledFromTest
+        }
+
+    // Initialized in init.
+    private lateinit var controllers: TaskbarControllers
+
+    private var shownHotseatItems: List<ItemInfo> = emptyList()
+    private var allRecentTasks: List<GroupTask> = emptyList()
+    private var desktopTask: DesktopTask? = null
+    var shownTasks: List<GroupTask> = emptyList()
+        private set
+
+    private val desktopVisibilityController: DesktopVisibilityController?
+        get() = desktopVisibilityControllerProvider()
+
+    private val isInDesktopMode: Boolean
+        get() = desktopVisibilityController?.areDesktopTasksVisible() ?: false
+
+    val runningAppPackages: Set<String>
+        /**
+         * Returns the package names of apps that should be indicated as "running" to the user.
+         * Specifically, we return all the open tasks if we are in Desktop mode, else emptySet().
+         */
+        get() {
+            if (!canShowRunningApps || !isInDesktopMode) {
+                return emptySet()
+            }
+            val tasks = desktopTask?.tasks ?: return emptySet()
+            return tasks.map { task -> task.key.packageName }.toSet()
+        }
+
+    val minimizedAppPackages: Set<String>
+        /**
+         * Returns the package names of apps that should be indicated as "minimized" to the user.
+         * Specifically, we return all the running packages where all the tasks in that package are
+         * minimized (not visible).
+         */
+        get() {
+            if (!canShowRunningApps || !isInDesktopMode) {
+                return emptySet()
+            }
+            val desktopTasks = desktopTask?.tasks ?: return emptySet()
+            val packageToTasks = desktopTasks.groupBy { it.key.packageName }
+            return packageToTasks.filterValues { tasks -> tasks.all { !it.isVisible } }.keys
+        }
+
+    private val recentTasksChangedListener =
+        RecentsModel.RecentTasksChangedListener { reloadRecentTasksIfNeeded() }
+
+    private val iconLoadRequests: MutableSet<CancellableTask<*>> = HashSet()
+
+    // TODO(b/343291428): add TaskVisualsChangListener as well (for calendar/clock?)
+
+    // Used to keep track of the last requested task list ID, so that we do not request to load the
+    // tasks again if we have already requested it and the task list has not changed
+    private var taskListChangeId = -1
+
+    fun init(taskbarControllers: TaskbarControllers) {
+        controllers = taskbarControllers
+        recentsModel.registerRecentTasksChangedListener(recentTasksChangedListener)
+        reloadRecentTasksIfNeeded()
+    }
+
+    fun onDestroy() {
+        recentsModel.unregisterRecentTasksChangedListener()
+        iconLoadRequests.forEach { it.cancel() }
+        iconLoadRequests.clear()
+    }
+
+    /** 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 removePredictions =
+            (isInDesktopMode && canShowRunningApps) || (!isInDesktopMode && canShowRecentApps)
+        if (!removePredictions) {
+            shownHotseatItems = hotseatItems.filterNotNull()
+            onRecentsOrHotseatChanged()
+            return hotseatItems
+        }
+        shownHotseatItems =
+            hotseatItems
+                .filterNotNull()
+                .filter { itemInfo -> !itemInfo.isPredictedItem }
+                .toMutableList()
+
+        onRecentsOrHotseatChanged()
+
+        return shownHotseatItems.toTypedArray()
+    }
+
+    private fun reloadRecentTasksIfNeeded() {
+        if (!recentsModel.isTaskListValid(taskListChangeId)) {
+            taskListChangeId =
+                recentsModel.getTasks { tasks ->
+                    allRecentTasks = tasks
+                    desktopTask = allRecentTasks.filterIsInstance<DesktopTask>().firstOrNull()
+                    onRecentsOrHotseatChanged()
+                    controllers.taskbarViewController.commitRunningAppsToUI()
+                }
+        }
+    }
+
+    private fun onRecentsOrHotseatChanged() {
+        shownTasks =
+            if (isInDesktopMode) {
+                computeShownRunningTasks()
+            } else {
+                computeShownRecentTasks()
+            }
+
+        for (groupTask in shownTasks) {
+            for (task in groupTask.tasks) {
+                val callback =
+                    Consumer<Task> { controllers.taskbarViewController.onTaskUpdated(it) }
+                val cancellableTask = recentsModel.iconCache.updateIconInBackground(task, callback)
+                if (cancellableTask != null) {
+                    iconLoadRequests.add(cancellableTask)
+                }
+            }
+        }
+    }
+
+    private fun computeShownRunningTasks(): List<GroupTask> {
+        if (!canShowRunningApps) {
+            return emptyList()
+        }
+        val tasks = desktopTask?.tasks ?: emptyList()
+        // Kind of hacky, we wrap each single task in the Desktop as a GroupTask.
+        var desktopTaskAsList = tasks.map { GroupTask(it) }
+        // TODO(b/315344726 Multi-instance support): dedupe Tasks of the same package too.
+        desktopTaskAsList = dedupeHotseatTasks(desktopTaskAsList, shownHotseatItems)
+        val desktopPackages = desktopTaskAsList.map { it.packageNames }
+        // Remove any missing Tasks.
+        val newShownTasks = shownTasks.filter { it.packageNames in desktopPackages }.toMutableList()
+        val newShownPackages = newShownTasks.map { it.packageNames }
+        // Add any new Tasks, maintaining the order from previous shownTasks.
+        newShownTasks.addAll(desktopTaskAsList.filter { it.packageNames !in newShownPackages })
+        return newShownTasks.toList()
+    }
+
+    private fun computeShownRecentTasks(): List<GroupTask> {
+        if (!canShowRecentApps || allRecentTasks.isEmpty()) {
+            return emptyList()
+        }
+        // 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.
+            shownTasks = shownTasks.subList(shownTasks.size - MAX_RECENT_TASKS, shownTasks.size)
+        }
+        return shownTasks
+    }
+
+    private fun dedupeHotseatTasks(
+        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)
+        }
+    }
+
+    override fun dumpLogs(prefix: String, pw: PrintWriter) {
+        pw.println("$prefix TaskbarRecentAppsController:")
+        pw.println("$prefix\tcanShowRunningApps=$canShowRunningApps")
+        pw.println("$prefix\tcanShowRecentApps=$canShowRecentApps")
+        pw.println("$prefix\tshownHotseatItems=${shownHotseatItems.map{item->item.targetPackage}}")
+        pw.println("$prefix\tallRecentTasks=${allRecentTasks.map { it.packageNames }}")
+        pw.println("$prefix\tdesktopTask=${desktopTask?.packageNames}")
+        pw.println("$prefix\tshownTasks=${shownTasks.map { it.packageNames }}")
+        pw.println("$prefix\trunningTasks=$runningAppPackages")
+        pw.println("$prefix\tminimizedTasks=$minimizedAppPackages")
+    }
+
+    private val GroupTask.packageNames: List<String>
+        get() = tasks.map { task -> task.key.packageName }
+
+    private companion object {
+        const val MAX_RECENT_TASKS = 2
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 8fdb460..fa2d907 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -22,10 +22,10 @@
 import static com.android.app.animation.Interpolators.INSTANT;
 import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.internal.jank.InteractionJankMonitor.Configuration;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_SHOW;
-import static com.android.launcher3.taskbar.TaskbarKeyguardController.MASK_ANY_SYSUI_LOCKED;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
@@ -35,11 +35,11 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
 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;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
 import android.app.RemoteAction;
 import android.graphics.drawable.Icon;
 import android.os.SystemClock;
@@ -66,6 +66,7 @@
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
 import com.android.quickstep.LauncherActivityInterface;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.SystemUiFlagUtils;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -242,6 +243,10 @@
     private final Alarm mTimeoutAlarm = new Alarm();
     private boolean mEnableBlockingTimeoutDuringTests = false;
 
+    private Animator mTaskbarBackgroundAlphaAnimator;
+    private long mTaskbarBackgroundDuration;
+    private boolean mUserIsNotGoingHome = false;
+
     // Evaluate whether the handle should be stashed
     private final LongPredicate mIsStashedPredicate = flags -> {
         boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
@@ -266,6 +271,8 @@
         mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
         mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class);
 
+        mTaskbarBackgroundDuration =
+                activity.getResources().getInteger(R.integer.taskbar_background_duration);
         if (mActivity.isPhoneMode()) {
             mUnstashedHeight = mActivity.getResources().getDimensionPixelSize(
                     R.dimen.taskbar_phone_size);
@@ -331,7 +338,16 @@
         // For now, assume we're in an app, since LauncherTaskbarUIController won't be able to tell
         // us that we're paused until a bit later. This avoids flickering upon recreating taskbar.
         updateStateForFlag(FLAG_IN_APP, true);
+
         applyState(/* duration = */ 0);
+
+        // Hide the background while stashed so it doesn't show on fast swipes home
+        boolean shouldHideTaskbarBackground = enableScalingRevealHomeAnimation()
+                && DisplayController.isTransientTaskbar(mActivity)
+                && isStashed();
+
+        mTaskbarBackgroundAlphaForStash.setValue(shouldHideTaskbarBackground ? 0 : 1);
+
         if (mTaskbarSharedState.getTaskbarWasPinned()
                 || !mTaskbarSharedState.taskbarWasStashedAuto) {
             tryStartTaskbarTimeout();
@@ -760,9 +776,16 @@
                 backgroundAndHandleAlphaStartDelay,
                 backgroundAndHandleAlphaDuration, LINEAR);
 
-        play(as, mTaskbarBackgroundAlphaForStash.animateToValue(backgroundAlphaTarget),
-                backgroundAndHandleAlphaStartDelay,
-                backgroundAndHandleAlphaDuration, LINEAR);
+        if (enableScalingRevealHomeAnimation() && !isStashed) {
+            play(as, getTaskbarBackgroundAnimatorWhenNotGoingHome(duration),
+                    0, 0, LINEAR);
+            as.addListener(AnimatorListeners.forEndCallback(
+                    () -> mTaskbarBackgroundAlphaForStash.setValue(backgroundAlphaTarget)));
+        } else {
+            play(as, mTaskbarBackgroundAlphaForStash.animateToValue(backgroundAlphaTarget),
+                    backgroundAndHandleAlphaStartDelay,
+                    backgroundAndHandleAlphaDuration, LINEAR);
+        }
 
         // The rest of the animations might be "skipped" in TRANSITION_HANDLE_FADE transitions.
         AnimatorSet skippable = as;
@@ -805,6 +828,58 @@
                 .setDuration(isStashed ? duration / 2 : duration));
     }
 
+    private Animator getTaskbarBackgroundAnimatorWhenNotGoingHome(long duration) {
+        ValueAnimator a = ValueAnimator.ofFloat(0, 1);
+        a.setDuration(duration);
+        a.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            // This value is arbitrary.
+            private static final float ANIMATED_FRACTION_THRESHOLD = 0.25f;
+            private boolean mTaskbarBgAlphaAnimationStarted = false;
+            @Override
+            public void onAnimationUpdate(ValueAnimator valueAnimator) {
+                if (mTaskbarBgAlphaAnimationStarted) {
+                    return;
+                }
+
+                if (valueAnimator.getAnimatedFraction() >= ANIMATED_FRACTION_THRESHOLD) {
+                    if (mUserIsNotGoingHome) {
+                        playTaskbarBackgroundAlphaAnimation();
+                        mTaskbarBgAlphaAnimationStarted = true;
+                    }
+                }
+            }
+        });
+        return a;
+    }
+
+    /**
+     * Sets whether the user is going home based on the current gesture.
+     */
+    public void setUserIsNotGoingHome(boolean userIsNotGoingHome) {
+        mUserIsNotGoingHome = userIsNotGoingHome;
+    }
+
+    /**
+     * Plays the taskbar background alpha animation if one is not currently playing.
+     */
+    public void playTaskbarBackgroundAlphaAnimation() {
+        if (mTaskbarBackgroundAlphaAnimator != null
+                && mTaskbarBackgroundAlphaAnimator.isRunning()) {
+            return;
+        }
+        mTaskbarBackgroundAlphaAnimator = mTaskbarBackgroundAlphaForStash
+                .animateToValue(1f)
+                .setDuration(mTaskbarBackgroundDuration);
+        mTaskbarBackgroundAlphaAnimator.setInterpolator(LINEAR);
+        mTaskbarBackgroundAlphaAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mTaskbarBackgroundAlphaAnimator = null;
+            }
+        });
+        mTaskbarBackgroundAlphaAnimator.start();
+    }
+
     private static void play(AnimatorSet as, @Nullable Animator a, long startDelay, long duration,
             Interpolator interpolator) {
         if (a == null) {
@@ -950,9 +1025,8 @@
                 && DisplayController.isTransientTaskbar(mActivity);
         updateStateForFlag(FLAG_STASHED_SYSUI,
                 hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING) || stashForBubbles);
-        boolean isLocked = hasAnyFlag(systemUiStateFlags, MASK_ANY_SYSUI_LOCKED)
-                && !hasAnyFlag(systemUiStateFlags, SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY);
-        updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, isLocked);
+        updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED,
+                SystemUiFlagUtils.isLocked(systemUiStateFlags));
 
         mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
         mIsImeSwitcherShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_SHOWING);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 2b68b52..ce281c3 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -411,4 +411,11 @@
     public void setSkipNextRecentsAnimEnd() {
         // Overridden
     }
+
+    /**
+     * Sets whether the user is going home based on the current gesture.
+     */
+    public void setUserIsNotGoingHome(boolean isNotGoingHome) {
+        mControllers.taskbarStashController.setUserIsNotGoingHome(isNotGoingHome);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 570221c..c42d6c6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -19,6 +19,7 @@
 
 import static com.android.launcher3.BubbleTextView.DISPLAY_TASKBAR;
 import static com.android.launcher3.Flags.enableCursorHoverStates;
+import static com.android.launcher3.Flags.enableRecentsInTaskbar;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR;
@@ -30,6 +31,7 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.util.AttributeSet;
 import android.view.DisplayCutout;
@@ -67,7 +69,11 @@
 import com.android.launcher3.views.IconButtonView;
 import com.android.quickstep.DeviceConfigWrapper;
 import com.android.quickstep.util.AssistStateManager;
+import com.android.quickstep.util.DesktopTask;
+import com.android.quickstep.util.GroupTask;
+import com.android.systemui.shared.recents.model.Task;
 
+import java.util.List;
 import java.util.function.Predicate;
 
 /**
@@ -168,7 +174,7 @@
         mAllAppsButton.setForegroundTint(
                 mActivityContext.getColor(R.color.all_apps_button_color));
 
-        if (enableTaskbarPinning()) {
+        if (enableTaskbarPinning() || enableRecentsInTaskbar()) {
             mTaskbarDivider = (IconButtonView) LayoutInflater.from(context).inflate(
                     R.layout.taskbar_divider,
                     this, false);
@@ -308,9 +314,10 @@
     /**
      * Inflates/binds the Hotseat views to show in the Taskbar given their ItemInfos.
      */
-    protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) {
+    protected void updateHotseatItems(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
         int nextViewIndex = 0;
         int numViewsAnimated = 0;
+        boolean addedDividerForRecents = false;
 
         if (mAllAppsButton != null) {
             removeView(mAllAppsButton);
@@ -321,8 +328,8 @@
         }
         removeView(mQsb);
 
-        for (int i = 0; i < hotseatItemInfos.length; i++) {
-            ItemInfo hotseatItemInfo = hotseatItemInfos[i];
+        // Add Hotseat icons.
+        for (ItemInfo hotseatItemInfo : hotseatItemInfos) {
             if (hotseatItemInfo == null) {
                 continue;
             }
@@ -388,11 +395,8 @@
             }
 
             // Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
-            if (hotseatView instanceof BubbleTextView
-                    && hotseatItemInfo instanceof WorkspaceItemInfo) {
-                BubbleTextView btv = (BubbleTextView) hotseatView;
-                WorkspaceItemInfo workspaceInfo = (WorkspaceItemInfo) hotseatItemInfo;
-
+            if (hotseatView instanceof BubbleTextView btv
+                    && hotseatItemInfo instanceof WorkspaceItemInfo workspaceInfo) {
                 boolean animate = btv.shouldAnimateIconChange((WorkspaceItemInfo) hotseatItemInfo);
                 btv.applyFromWorkspaceItem(workspaceInfo, animate, numViewsAnimated);
                 if (animate) {
@@ -405,6 +409,67 @@
             }
             nextViewIndex++;
         }
+
+        if (mTaskbarDivider != null && !recentTasks.isEmpty()) {
+            addView(mTaskbarDivider, nextViewIndex++);
+            addedDividerForRecents = true;
+        }
+
+        // Add Recent/Running icons.
+        for (GroupTask task : recentTasks) {
+            // Replace any Recent views with the appropriate type if it's not already that type.
+            final int expectedLayoutResId;
+            boolean isCollection = false;
+            if (task.hasMultipleTasks()) {
+                if (task instanceof DesktopTask) {
+                    // TODO(b/316004172): use Desktop tile layout.
+                    expectedLayoutResId = -1;
+                } else {
+                    // TODO(b/343289567): use R.layout.app_pair_icon
+                    expectedLayoutResId = -1;
+                }
+                isCollection = true;
+            } else {
+                expectedLayoutResId = R.layout.taskbar_app_icon;
+            }
+
+            View recentIcon = null;
+            while (nextViewIndex < getChildCount()) {
+                recentIcon = getChildAt(nextViewIndex);
+
+                // see if the view can be reused
+                if ((recentIcon.getSourceLayoutResId() != expectedLayoutResId)
+                        || (isCollection && (recentIcon.getTag() != task))) {
+                    removeAndRecycle(recentIcon);
+                    recentIcon = null;
+                } else {
+                    // View found
+                    break;
+                }
+            }
+
+            if (recentIcon == null) {
+                if (isCollection) {
+                    // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
+                    continue;
+                }
+
+                recentIcon = inflate(expectedLayoutResId);
+                LayoutParams lp = new LayoutParams(mIconTouchSize, mIconTouchSize);
+                recentIcon.setPadding(mItemPadding, mItemPadding, mItemPadding, mItemPadding);
+                addView(recentIcon, nextViewIndex, lp);
+            }
+
+            if (recentIcon instanceof BubbleTextView btv) {
+                applyGroupTaskToBubbleTextView(btv, task);
+            }
+            setClickAndLongClickListenersForIcon(recentIcon);
+            if (enableCursorHoverStates()) {
+                setHoverListenerForIcon(recentIcon);
+            }
+            nextViewIndex++;
+        }
+
         // Remove remaining views
         while (nextViewIndex < getChildCount()) {
             removeAndRecycle(getChildAt(nextViewIndex));
@@ -413,8 +478,8 @@
         if (mAllAppsButton != null) {
             addView(mAllAppsButton, mIsRtl ? getChildCount() : 0);
 
-            // if only all apps button present, don't include divider view.
-            if (mTaskbarDivider != null && getChildCount() > 1) {
+            // If there are no recent tasks, add divider after All Apps (unless it's the only view).
+            if (!addedDividerForRecents && mTaskbarDivider != null && getChildCount() > 1) {
                 addView(mTaskbarDivider, mIsRtl ? (getChildCount() - 1) : 1);
             }
         }
@@ -425,6 +490,20 @@
         }
     }
 
+    /** Binds the GroupTask to the BubbleTextView to be ready to present to the user. */
+    public void applyGroupTaskToBubbleTextView(BubbleTextView btv, GroupTask groupTask) {
+        // TODO(b/343289567): support app pairs.
+        Task task1 = groupTask.task1;
+        // TODO(b/344038728): use FastBitmapDrawable instead of Drawable, to get disabled state
+        //  while dragging.
+        Drawable taskIcon = groupTask.task1.icon;
+        if (taskIcon != null) {
+            taskIcon = taskIcon.getConstantState().newDrawable().mutate();
+        }
+        btv.applyIconAndLabel(taskIcon, task1.titleDescription);
+        btv.setTag(groupTask);
+    }
+
     /**
      * Sets OnClickListener and OnLongClickListener for the given view.
      */
@@ -677,7 +756,8 @@
         // map over all the shortcuts on the taskbar
         for (int i = 0; i < getChildCount(); i++) {
             View item = getChildAt(i);
-            if (op.evaluate((ItemInfo) item.getTag(), item)) {
+            // TODO(b/344657629): Support GroupTask as well for notification dots/popup
+            if (item.getTag() instanceof ItemInfo itemInfo && op.evaluate(itemInfo, item)) {
                 return;
             }
         }
@@ -694,6 +774,7 @@
                 View item = getChildAt(i);
                 if (!(item.getTag() instanceof ItemInfo)) {
                     // Should only happen for All Apps button.
+                    // Will also happen for Recent/Running app icons. (Which have GroupTask as tags)
                     continue;
                 }
                 ItemInfo info = (ItemInfo) item.getTag();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 23495ad..e59a016 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -70,6 +70,8 @@
 import com.android.launcher3.util.MultiTranslateDelegate;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.views.IconButtonView;
+import com.android.quickstep.util.GroupTask;
+import com.android.systemui.shared.recents.model.Task;
 
 import java.io.PrintWriter;
 import java.util.Set;
@@ -224,7 +226,6 @@
         }
         LauncherAppState.getInstance(mActivity).getModel().removeCallbacks(mModelCallbacks);
         mActivity.removeOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
-        mModelCallbacks.unregisterListeners();
     }
 
     public boolean areIconsVisible() {
@@ -347,6 +348,11 @@
         float allAppIconTranslateRange = mapRange(scale, transientTaskbarAllAppsOffset,
                 persistentTaskbarAllAppsOffset);
 
+        // no x translation required when all apps button is the only icon in taskbar.
+        if (iconViews.length <= 1) {
+            allAppIconTranslateRange = 0f;
+        }
+
         if (mIsRtl) {
             allAppIconTranslateRange *= -1;
         }
@@ -377,7 +383,7 @@
                         -finalMarginScale * (iconIndex - halfIconCount));
             }
 
-            if (iconView.equals(mTaskbarView.getAllAppsButtonView()) && iconViews.length > 1) {
+            if (iconView.equals(mTaskbarView.getAllAppsButtonView())) {
                 ((IconButtonView) iconView).setTranslationXForTaskbarAllAppsIcon(
                         allAppIconTranslateRange);
             }
@@ -515,21 +521,31 @@
         for (View iconView : getIconViews()) {
             if (iconView instanceof BubbleTextView btv) {
                 btv.updateRunningState(
-                        getRunningAppState(btv.getTargetPackageName(), runningPackages,
-                                minimizedPackages));
+                        getRunningAppState(btv, runningPackages, minimizedPackages));
             }
         }
     }
 
     private BubbleTextView.RunningAppState getRunningAppState(
-            String packageName,
+            BubbleTextView btv,
             Set<String> runningPackages,
             Set<String> minimizedPackages) {
-        if (minimizedPackages.contains(packageName)) {
-            return BubbleTextView.RunningAppState.MINIMIZED;
+        Object tag = btv.getTag();
+        if (tag instanceof ItemInfo itemInfo) {
+            if (minimizedPackages.contains(itemInfo.getTargetPackage())) {
+                return BubbleTextView.RunningAppState.MINIMIZED;
+            }
+            if (runningPackages.contains(itemInfo.getTargetPackage())) {
+                return BubbleTextView.RunningAppState.RUNNING;
+            }
         }
-        if (runningPackages.contains(packageName)) {
-            return BubbleTextView.RunningAppState.RUNNING;
+        if (tag instanceof GroupTask groupTask && !groupTask.hasMultipleTasks()) {
+            if (minimizedPackages.contains(groupTask.task1.key.getPackageName())) {
+                return BubbleTextView.RunningAppState.MINIMIZED;
+            }
+            if (runningPackages.contains(groupTask.task1.key.getPackageName())) {
+                return BubbleTextView.RunningAppState.RUNNING;
+            }
         }
         return BubbleTextView.RunningAppState.NOT_RUNNING;
     }
@@ -864,6 +880,27 @@
         return mTaskbarView.isEventOverAnyItem(ev);
     }
 
+    /** Called when there's a change in running apps to update the UI. */
+    public void commitRunningAppsToUI() {
+        mModelCallbacks.commitRunningAppsToUI();
+    }
+
+    /**
+     * To be called when the given Task is updated, so that we can tell TaskbarView to also update.
+     * @param task The Task whose e.g. icon changed.
+     */
+    public void onTaskUpdated(Task task) {
+        // Find the icon view(s) that changed.
+        for (View view : mTaskbarView.getIconViews()) {
+            if (view instanceof BubbleTextView btv
+                    && view.getTag() instanceof GroupTask groupTask) {
+                if (groupTask.containsTask(task.key.id)) {
+                    mTaskbarView.applyGroupTaskToBubbleTextView(btv, groupTask);
+                }
+            }
+        }
+    }
+
     @Override
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarViewController:");
@@ -883,15 +920,4 @@
 
         mModelCallbacks.dumpLogs(prefix + "\t", pw);
     }
-
-    /** Called when there's a change in running apps to update the UI. */
-    public void commitRunningAppsToUI() {
-        mModelCallbacks.commitRunningAppsToUI();
-    }
-
-    /** Call TaskbarModelCallbacks to update running apps. */
-    public void updateRunningApps() {
-        mModelCallbacks.updateRunningApps();
-    }
-
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
index a59e81b..25939e1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -19,7 +19,9 @@
 import android.graphics.Canvas
 import android.graphics.Color
 import android.graphics.ColorFilter
+import android.graphics.Matrix
 import android.graphics.Paint
+import android.graphics.Path
 import android.graphics.PixelFormat
 import android.graphics.drawable.Drawable
 import com.android.app.animation.Interpolators
@@ -28,28 +30,28 @@
 import com.android.launcher3.Utilities.mapToRange
 import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
 import com.android.launcher3.popup.RoundedArrowDrawable
+import kotlin.math.max
+import kotlin.math.min
 
 /** Drawable for the background of the bubble bar. */
 class BubbleBarBackground(context: Context, private var backgroundHeight: Float) : Drawable() {
 
-    private val DARK_THEME_SHADOW_ALPHA = 51f
-    private val LIGHT_THEME_SHADOW_ALPHA = 25f
-
-    private val paint: Paint = Paint()
-    private val pointerWidth: Float
-    private val pointerHeight: Float
-    private val pointerTipRadius: Float
-    private val pointerVisibleHeight: Float
+    private val fillPaint: Paint = Paint()
+    private val strokePaint: Paint = Paint()
+    private val arrowWidth: Float
+    private val arrowHeight: Float
+    private val arrowTipRadius: Float
+    private val arrowVisibleHeight: Float
 
     private val shadowAlpha: Float
     private var shadowBlur = 0f
     private var keyShadowDistance = 0f
+    private var arrowHeightFraction = 1f
 
     var arrowPositionX: Float = 0f
         private set
 
     private var showingArrow: Boolean = false
-    private var arrowDrawable: RoundedArrowDrawable
 
     var width: Float = 0f
 
@@ -70,34 +72,31 @@
         }
 
     init {
-        paint.color = context.getColor(R.color.taskbar_background)
-        paint.flags = Paint.ANTI_ALIAS_FLAG
-        paint.style = Paint.Style.FILL
-
         val res = context.resources
+        // configure fill paint
+        fillPaint.color = context.getColor(R.color.taskbar_background)
+        fillPaint.flags = Paint.ANTI_ALIAS_FLAG
+        fillPaint.style = Paint.Style.FILL
+        // configure stroke paint
+        strokePaint.color = context.getColor(R.color.taskbar_stroke)
+        strokePaint.flags = Paint.ANTI_ALIAS_FLAG
+        strokePaint.style = Paint.Style.STROKE
+        strokePaint.strokeWidth = res.getDimension(R.dimen.transient_taskbar_stroke_width)
+        // apply theme alpha attributes
+        if (Utilities.isDarkTheme(context)) {
+            strokePaint.alpha = DARK_THEME_STROKE_ALPHA
+            shadowAlpha = DARK_THEME_SHADOW_ALPHA
+        } else {
+            strokePaint.alpha = LIGHT_THEME_STROKE_ALPHA
+            shadowAlpha = LIGHT_THEME_SHADOW_ALPHA
+        }
+
         shadowBlur = res.getDimension(R.dimen.transient_taskbar_shadow_blur)
         keyShadowDistance = res.getDimension(R.dimen.transient_taskbar_key_shadow_distance)
-        pointerWidth = res.getDimension(R.dimen.bubblebar_pointer_width)
-        pointerHeight = res.getDimension(R.dimen.bubblebar_pointer_height)
-        pointerVisibleHeight = res.getDimension(R.dimen.bubblebar_pointer_visible_size)
-        pointerTipRadius = res.getDimension(R.dimen.bubblebar_pointer_radius)
-
-        shadowAlpha =
-            if (Utilities.isDarkTheme(context)) {
-                DARK_THEME_SHADOW_ALPHA
-            } else {
-                LIGHT_THEME_SHADOW_ALPHA
-            }
-
-        arrowDrawable =
-            RoundedArrowDrawable.createVerticalRoundedArrow(
-                pointerWidth,
-                pointerHeight,
-                pointerTipRadius,
-                /* isPointingUp= */ true,
-                context.getColor(R.color.taskbar_background)
-            )
-        arrowDrawable.setBounds(0, 0, pointerWidth.toInt(), pointerHeight.toInt())
+        arrowWidth = res.getDimension(R.dimen.bubblebar_pointer_width)
+        arrowHeight = res.getDimension(R.dimen.bubblebar_pointer_height)
+        arrowVisibleHeight = res.getDimension(R.dimen.bubblebar_pointer_visible_size)
+        arrowTipRadius = res.getDimension(R.dimen.bubblebar_pointer_radius)
     }
 
     fun showArrow(show: Boolean) {
@@ -115,40 +114,54 @@
         // TODO (b/277359345): Should animate the alpha similar to taskbar (see TaskbarDragLayer)
         // Draw shadows.
         val newShadowAlpha =
-            mapToRange(paint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
-        paint.setShadowLayer(
+            mapToRange(fillPaint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
+        fillPaint.setShadowLayer(
             shadowBlur,
             0f,
             keyShadowDistance,
             setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
         )
-        arrowDrawable.setShadowLayer(
-            shadowBlur,
-            0f,
-            keyShadowDistance,
-            setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
-        )
-
-        // Draw background.
+        // Create background path
+        val backgroundPath = Path()
+        val topOffset = backgroundHeight - bounds.height().toFloat()
         val radius = backgroundHeight / 2f
         val left = bounds.left + (if (anchorLeft) 0f else bounds.width().toFloat() - width)
         val right = bounds.left + (if (anchorLeft) width else bounds.width().toFloat())
-        val top = bounds.top + pointerVisibleHeight
+        val top = bounds.top - topOffset + arrowVisibleHeight
+
         val bottom = bounds.top + bounds.height().toFloat()
-        canvas.drawRoundRect(left, top, right, bottom, radius, radius, paint)
+        backgroundPath.addRoundRect(left, top, right, bottom, radius, radius, Path.Direction.CW)
+        addArrowPathIfNeeded(backgroundPath, topOffset)
 
-        if (showingArrow) {
-            // Draw arrow.
-            val transX = bounds.left + arrowPositionX - pointerWidth / 2f
-            canvas.translate(transX, 0f)
-            arrowDrawable.draw(canvas)
-        }
-
+        // Draw background.
+        canvas.drawPath(backgroundPath, fillPaint)
+        canvas.drawPath(backgroundPath, strokePaint)
         canvas.restore()
     }
 
+    private fun addArrowPathIfNeeded(sourcePath: Path, topOffset: Float) {
+        if (!showingArrow || arrowHeightFraction <= 0) return
+        val arrowPath = Path()
+        RoundedArrowDrawable.addDownPointingRoundedTriangleToPath(
+            arrowWidth,
+            arrowHeight,
+            arrowTipRadius,
+            arrowPath
+        )
+        // flip it horizontally
+        val pathTransform = Matrix()
+        pathTransform.setRotate(180f, arrowWidth * 0.5f, arrowHeight * 0.5f)
+        arrowPath.transform(pathTransform)
+        // shift to arrow position
+        val arrowStart = bounds.left + arrowPositionX - (arrowWidth / 2f)
+        val arrowTop = (1 - arrowHeightFraction) * arrowVisibleHeight - topOffset
+        arrowPath.offset(arrowStart, arrowTop)
+        // union with rectangle
+        sourcePath.op(arrowPath, Path.Op.UNION)
+    }
+
     override fun getOpacity(): Int {
-        return when (paint.alpha) {
+        return when (fillPaint.alpha) {
             255 -> PixelFormat.OPAQUE
             0 -> PixelFormat.TRANSPARENT
             else -> PixelFormat.TRANSLUCENT
@@ -156,24 +169,40 @@
     }
 
     override fun setAlpha(alpha: Int) {
-        paint.alpha = alpha
-        arrowDrawable.alpha = alpha
+        fillPaint.alpha = alpha
         invalidateSelf()
     }
 
     override fun getAlpha(): Int {
-        return paint.alpha
+        return fillPaint.alpha
     }
 
     override fun setColorFilter(colorFilter: ColorFilter?) {
-        paint.colorFilter = colorFilter
+        fillPaint.colorFilter = colorFilter
     }
 
-    fun setArrowAlpha(alpha: Int) {
-        arrowDrawable.alpha = alpha
-    }
-
-    fun setHeight(newHeight: Float) {
+    fun setBackgroundHeight(newHeight: Float) {
         backgroundHeight = newHeight
     }
+
+    /**
+     * Set fraction of the arrow height that should be displayed. Allowed values range are [0..1].
+     * If value passed is out of range it will be converted to the closest value in tha allowed
+     * range.
+     */
+    fun setArrowHeightFraction(arrowHeightFraction: Float) {
+        var newHeightFraction = arrowHeightFraction
+        if (newHeightFraction !in 0f..1f) {
+            newHeightFraction = min(max(newHeightFraction, 0f), 1f)
+        }
+        this.arrowHeightFraction = newHeightFraction
+        invalidateSelf()
+    }
+
+    companion object {
+        private const val DARK_THEME_STROKE_ALPHA = 51
+        private const val LIGHT_THEME_STROKE_ALPHA = 41
+        private const val DARK_THEME_SHADOW_ALPHA = 51f
+        private const val LIGHT_THEME_SHADOW_ALPHA = 25f
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 046f5b6..15e4578 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -135,7 +135,6 @@
 
     private static final Executor BUBBLE_STATE_EXECUTOR = Executors.newSingleThreadExecutor(
             new SimpleThreadFactory("BubbleStateUpdates-", THREAD_PRIORITY_BACKGROUND));
-    private final Executor mMainExecutor;
     private final LauncherApps mLauncherApps;
     private final BubbleIconFactory mIconFactory;
     private final SystemUiProxy mSystemUiProxy;
@@ -198,7 +197,6 @@
         if (sBubbleBarEnabled) {
             mSystemUiProxy.setBubblesListener(this);
         }
-        mMainExecutor = MAIN_EXECUTOR;
         mLauncherApps = context.getSystemService(LauncherApps.class);
         mIconFactory = new BubbleIconFactory(context,
                 context.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size),
@@ -241,7 +239,7 @@
     private void createAndAddOverflowIfNeeded() {
         if (mOverflowBubble == null) {
             BubbleBarOverflow overflow = createOverflow(mContext);
-            mMainExecutor.execute(() -> {
+            MAIN_EXECUTOR.execute(() -> {
                 // we're on the main executor now, so check that the overflow hasn't been created
                 // again to avoid races.
                 if (mOverflowBubble == null) {
@@ -303,12 +301,12 @@
                     }
                     viewUpdate.currentBubbles = currentBubbles;
                 }
-                mMainExecutor.execute(() -> applyViewChanges(viewUpdate));
+                MAIN_EXECUTOR.execute(() -> applyViewChanges(viewUpdate));
             });
         } else {
             // No bubbles to load, immediately apply the changes.
             BUBBLE_STATE_EXECUTOR.execute(
-                    () -> mMainExecutor.execute(() -> applyViewChanges(viewUpdate)));
+                    () -> MAIN_EXECUTOR.execute(() -> applyViewChanges(viewUpdate)));
         }
     }
 
@@ -496,7 +494,7 @@
 
     @Override
     public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
-        mMainExecutor.execute(
+        MAIN_EXECUTOR.execute(
                 () -> mBubbleBarViewController.animateBubbleBarLocation(bubbleBarLocation));
     }
 
@@ -602,7 +600,7 @@
         Bitmap bitmap = createOverflowBitmap(context);
         LayoutInflater inflater = LayoutInflater.from(context);
         BubbleView bubbleView = (BubbleView) inflater.inflate(
-                R.layout.bubblebar_item_view, mBarView, false /* attachToRoot */);
+                R.layout.bubble_bar_overflow_button, mBarView, false /* attachToRoot */);
         BubbleBarOverflow overflow = new BubbleBarOverflow(bubbleView);
         bubbleView.setOverflow(overflow, bitmap);
         return overflow;
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
index 43e21f4..39d1ed7 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarItem.kt
@@ -34,4 +34,8 @@
 ) : BubbleBarItem(info.key, view)
 
 /** Represents the overflow bubble in the bubble bar. */
-data class BubbleBarOverflow(override var view: BubbleView) : BubbleBarItem("Overflow", view)
+data class BubbleBarOverflow(override var view: BubbleView) : BubbleBarItem(KEY, view) {
+    companion object {
+        const val KEY = "Overflow"
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 85ea5fd..0ea5031 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -24,6 +24,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.content.Context;
@@ -43,7 +44,9 @@
 
 import com.android.launcher3.R;
 import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.taskbar.bubbles.animation.BubbleAnimator;
 import com.android.launcher3.util.DisplayController;
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.common.bubbles.BubbleBarLocation;
 
 import java.util.List;
@@ -85,6 +88,7 @@
     private static final int MAX_VISIBLE_BUBBLES_COLLAPSED = 2;
     private static final int ARROW_POSITION_ANIMATION_DURATION_MS = 200;
     private static final int WIDTH_ANIMATION_DURATION_MS = 200;
+    private static final int SCALE_ANIMATION_DURATION_MS = 200;
 
     private static final long FADE_OUT_ANIM_ALPHA_DURATION_MS = 50L;
     private static final long FADE_OUT_ANIM_ALPHA_DELAY_MS = 50L;
@@ -133,13 +137,12 @@
     private float mBubbleBarPadding;
     // The size of a bubble in the bar
     private float mIconSize;
+    // The scale of bubble icons
+    private float mIconScale = 1f;
     // The elevation of the bubbles within the bar
     private final float mBubbleElevation;
     private final float mDragElevation;
     private final int mPointerSize;
-
-    private final Rect mTempBackgroundBounds = new Rect();
-
     // Whether the bar is expanded (i.e. the bubble activity is being displayed).
     private boolean mIsBarExpanded = false;
     // The currently selected bubble view.
@@ -157,6 +160,12 @@
     // collapsed state and 1 to the fully expanded state.
     private final ValueAnimator mWidthAnimator = ValueAnimator.ofFloat(0, 1);
 
+    /** An animator used for animating individual bubbles in the bubble bar while expanded. */
+    @Nullable
+    private BubbleAnimator mBubbleAnimator = null;
+    @Nullable
+    private ValueAnimator mScalePaddingAnimator;
+
     @Nullable
     private Animator mBubbleBarLocationAnimator = null;
 
@@ -211,42 +220,75 @@
         setBackgroundDrawable(mBubbleBarBackground);
 
         mWidthAnimator.setDuration(WIDTH_ANIMATION_DURATION_MS);
-        mWidthAnimator.addUpdateListener(animation -> {
-            updateChildrenRenderNodeProperties(mBubbleBarLocation);
-            invalidate();
-        });
-        mWidthAnimator.addListener(new Animator.AnimatorListener() {
-            @Override
-            public void onAnimationCancel(Animator animation) {
-            }
 
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                mBubbleBarBackground.showArrow(mIsBarExpanded);
-                if (!mIsBarExpanded && mReorderRunnable != null) {
-                    mReorderRunnable.run();
-                    mReorderRunnable = null;
-                }
-                // If the bar was just collapsed and the overflow was the last bubble that was
-                // selected, set the first bubble as selected.
-                if (!mIsBarExpanded && mUpdateSelectedBubbleAfterCollapse != null
-                        && mSelectedBubbleView != null
-                        && mSelectedBubbleView.getBubble() instanceof BubbleBarOverflow) {
-                    BubbleView firstBubble = (BubbleView) getChildAt(0);
-                    mUpdateSelectedBubbleAfterCollapse.accept(firstBubble.getBubble().getKey());
-                }
-                updateWidth();
-            }
+        addAnimationCallBacks(mWidthAnimator,
+                /* onStart= */ () -> mBubbleBarBackground.showArrow(true),
+                /* onEnd= */ () -> {
+                    mBubbleBarBackground.showArrow(mIsBarExpanded);
+                    if (!mIsBarExpanded && mReorderRunnable != null) {
+                        mReorderRunnable.run();
+                        mReorderRunnable = null;
+                    }
+                    // If the bar was just collapsed and the overflow was the last bubble that was
+                    // selected, set the first bubble as selected.
+                    if (!mIsBarExpanded && mUpdateSelectedBubbleAfterCollapse != null
+                            && mSelectedBubbleView != null
+                            && mSelectedBubbleView.getBubble() instanceof BubbleBarOverflow) {
+                        BubbleView firstBubble = (BubbleView) getChildAt(0);
+                        mUpdateSelectedBubbleAfterCollapse.accept(firstBubble.getBubble().getKey());
+                    }
+                    updateWidth();
+                },
+                /* onUpdate= */ animator -> {
+                    updateBubblesLayoutProperties(mBubbleBarLocation);
+                    invalidate();
+                });
+    }
 
-            @Override
-            public void onAnimationRepeat(Animator animation) {
-            }
 
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mBubbleBarBackground.showArrow(true);
-            }
-        });
+    /**
+     * Animates icon sizes and spacing between icons and bubble bar borders.
+     *
+     * @param newIconSize         new icon size
+     * @param newBubbleBarPadding spacing between icons and bubble bar borders.
+     */
+    public void animateBubbleBarIconSize(float newIconSize, float newBubbleBarPadding) {
+        if (!isIconSizeOrPaddingUpdated(newIconSize, newBubbleBarPadding)) {
+            return;
+        }
+        if (!Flags.animateBubbleSizeChange()) {
+            setIconSizeAndPadding(newIconSize, newBubbleBarPadding);
+            return;
+        }
+        if (mScalePaddingAnimator != null && mScalePaddingAnimator.isRunning()) {
+            mScalePaddingAnimator.cancel();
+        }
+        ValueAnimator scalePaddingAnimator = ValueAnimator.ofFloat(0f, 1f);
+        scalePaddingAnimator.setDuration(SCALE_ANIMATION_DURATION_MS);
+        boolean isPaddingUpdated = isPaddingUpdated(newBubbleBarPadding);
+        boolean isIconSizeUpdated = isIconSizeUpdated(newIconSize);
+        float initialScale = mIconScale;
+        float initialPadding = mBubbleBarPadding;
+        float targetScale = newIconSize / getScaledIconSize();
+
+        addAnimationCallBacks(scalePaddingAnimator,
+                /* onStart= */ null,
+                /* onEnd= */ () -> setIconSizeAndPadding(newIconSize, newBubbleBarPadding),
+                /* onUpdate= */ animator -> {
+                    float transitionProgress = (float) animator.getAnimatedValue();
+                    if (isIconSizeUpdated) {
+                        mIconScale =
+                                initialScale + (targetScale - initialScale) * transitionProgress;
+                    }
+                    if (isPaddingUpdated) {
+                        mBubbleBarPadding = initialPadding
+                                + (newBubbleBarPadding - initialPadding) * transitionProgress;
+                    }
+                    updateBubblesLayoutProperties(mBubbleBarLocation);
+                    invalidate();
+                });
+        scalePaddingAnimator.start();
+        mScalePaddingAnimator = scalePaddingAnimator;
     }
 
     @Override
@@ -261,28 +303,37 @@
     }
 
     /**
-     * Sets new icon size and spacing between icons and bubble bar borders.
+     * Sets new icon sizes and newBubbleBarPadding between icons and bubble bar borders.
      *
-     * @param newIconSize new icon size
-     * @param spacing     spacing between icons and bubble bar borders.
+     * @param newIconSize         new icon size
+     * @param newBubbleBarPadding newBubbleBarPadding between icons and bubble bar borders.
      */
-    // TODO(b/335575529): animate bubble bar icons size change
-    public void setIconSizeAndPadding(float newIconSize, float spacing) {
+    public void setIconSizeAndPadding(float newIconSize, float newBubbleBarPadding) {
         // TODO(b/335457839): handle new bubble animation during the size change
-        mBubbleBarPadding = spacing;
+        if (!isIconSizeOrPaddingUpdated(newIconSize, newBubbleBarPadding)) {
+            return;
+        }
+        mIconScale = 1f;
+        mBubbleBarPadding = newBubbleBarPadding;
         mIconSize = newIconSize;
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View childView = getChildAt(i);
+            childView.setScaleY(mIconScale);
+            childView.setScaleY(mIconScale);
             FrameLayout.LayoutParams params = (LayoutParams) childView.getLayoutParams();
             params.height = (int) mIconSize;
             params.width = (int) mIconSize;
             childView.setLayoutParams(params);
         }
-        mBubbleBarBackground.setHeight(getBubbleBarExpandedHeight());
+        mBubbleBarBackground.setBackgroundHeight(getBubbleBarHeight());
         updateLayoutParams();
     }
 
+    private float getScaledIconSize() {
+        return mIconSize * mIconScale;
+    }
+
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
@@ -298,7 +349,7 @@
 
         if (!mDragging) {
             // Position the views when not dragging
-            updateChildrenRenderNodeProperties(mBubbleBarLocation);
+            updateBubblesLayoutProperties(mBubbleBarLocation);
         }
     }
 
@@ -444,7 +495,7 @@
         mBubbleBarLocationAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                updateChildrenRenderNodeProperties(bubbleBarLocation);
+                updateBubblesLayoutProperties(bubbleBarLocation);
                 mBubbleBarBackground.setAnchorLeft(bubbleBarLocation.isOnLeft(isLayoutRtl()));
 
                 // Animate it in
@@ -613,6 +664,44 @@
         mIsAnimatingNewBubble = false;
     }
 
+    /** Add a new bubble to the bubble bar. */
+    public void addBubble(View bubble, FrameLayout.LayoutParams lp) {
+        if (isExpanded()) {
+            // if we're expanded scale the new bubble in
+            bubble.setScaleX(0f);
+            bubble.setScaleY(0f);
+            addView(bubble, 0, lp);
+
+            mBubbleAnimator = new BubbleAnimator(mIconSize, mExpandedBarIconsSpacing,
+                    getChildCount(), mBubbleBarLocation.isOnLeft(isLayoutRtl()));
+            BubbleAnimator.Listener listener = new BubbleAnimator.Listener() {
+
+                @Override
+                public void onAnimationEnd() {
+                    updateWidth();
+                    mBubbleAnimator = null;
+                }
+
+                @Override
+                public void onAnimationCancel() {
+                    bubble.setScaleX(1);
+                    bubble.setScaleY(1);
+                }
+
+                @Override
+                public void onAnimationUpdate(float animatedFraction) {
+                    bubble.setScaleX(animatedFraction);
+                    bubble.setScaleY(animatedFraction);
+                    updateBubblesLayoutProperties(mBubbleBarLocation);
+                    invalidate();
+                }
+            };
+            mBubbleAnimator.animateNewBubble(indexOfChild(mSelectedBubbleView), listener);
+        } else {
+            addView(bubble, 0, lp);
+        }
+    }
+
     // TODO: (b/280605790) animate it
     @Override
     public void addView(View child, int index, ViewGroup.LayoutParams params) {
@@ -627,6 +716,50 @@
         updateContentDescription();
     }
 
+    /** Removes the given bubble from the bubble bar. */
+    public void removeBubble(View bubble) {
+        if (isExpanded()) {
+            // TODO b/347062801 - animate the bubble bar if the last bubble is removed
+            int bubbleCount = getChildCount();
+            mBubbleAnimator = new BubbleAnimator(mIconSize, mExpandedBarIconsSpacing,
+                    bubbleCount, mBubbleBarLocation.isOnLeft(isLayoutRtl()));
+            BubbleAnimator.Listener listener = new BubbleAnimator.Listener() {
+
+                @Override
+                public void onAnimationEnd() {
+                    removeView(bubble);
+                    mBubbleAnimator = null;
+                }
+
+                @Override
+                public void onAnimationCancel() {
+                    bubble.setScaleX(0);
+                    bubble.setScaleY(0);
+                }
+
+                @Override
+                public void onAnimationUpdate(float animatedFraction) {
+                    bubble.setScaleX(1 - animatedFraction);
+                    bubble.setScaleY(1 - animatedFraction);
+                    updateBubblesLayoutProperties(mBubbleBarLocation);
+                    invalidate();
+                }
+            };
+            int bubbleIndex = indexOfChild(bubble);
+            BubbleView lastBubble = (BubbleView) getChildAt(bubbleCount - 1);
+            String lastBubbleKey = lastBubble.getBubble().getKey();
+            boolean removingLastBubble =
+                    BubbleBarOverflow.KEY.equals(lastBubbleKey)
+                            ? bubbleIndex == bubbleCount - 2
+                            : bubbleIndex == bubbleCount - 1;
+            mBubbleAnimator.animateRemovedBubble(
+                    indexOfChild(bubble), indexOfChild(mSelectedBubbleView), removingLastBubble,
+                    listener);
+        } else {
+            removeView(bubble);
+        }
+    }
+
     // TODO: (b/283309949) animate it
     @Override
     public void removeView(View view) {
@@ -653,6 +786,11 @@
         setLayoutParams(lp);
     }
 
+    private float getBubbleBarHeight() {
+        return mIsBarExpanded ? getBubbleBarExpandedHeight()
+                : getBubbleBarCollapsedHeight();
+    }
+
     /** @return the horizontal margin between the bubble bar and the edge of the screen. */
     int getHorizontalMargin() {
         LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
@@ -663,13 +801,16 @@
      * Updates the z order, positions, and badge visibility of the bubble views in the bar based
      * on the expanded state.
      */
-    private void updateChildrenRenderNodeProperties(BubbleBarLocation bubbleBarLocation) {
+    private void updateBubblesLayoutProperties(BubbleBarLocation bubbleBarLocation) {
         final float widthState = (float) mWidthAnimator.getAnimatedValue();
         final float currentWidth = getWidth();
         final float expandedWidth = expandedWidth();
         final float collapsedWidth = collapsedWidth();
         int bubbleCount = getChildCount();
-        final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
+        float viewBottom = mBubbleBarBounds.height() + (isExpanded() ? mPointerSize : 0);
+        float bubbleBarAnimatedTop = viewBottom - getBubbleBarHeight();
+        // When translating X & Y the scale is ignored, so need to deduct it from the translations
+        final float ty = bubbleBarAnimatedTop + mBubbleBarPadding - getScaleIconShift();
         final boolean animate = getVisibility() == VISIBLE;
         final boolean onLeft = bubbleBarLocation.isOnLeft(isLayoutRtl());
         // elevation state is opposite to widthState - when expanded all icons are flat
@@ -684,6 +825,12 @@
             bv.setDragTranslationX(0f);
             bv.setOffsetX(0f);
 
+            if (mBubbleAnimator == null || !mBubbleAnimator.isRunning()) {
+                // if the bubble animator is running don't set scale here, it will be set by the
+                // animator
+                bv.setScaleX(mIconScale);
+                bv.setScaleY(mIconScale);
+            }
             bv.setTranslationY(ty);
 
             // the position of the bubble when the bar is fully expanded
@@ -748,22 +895,29 @@
             }
         }
         mBubbleBarBackground.setArrowPosition(arrowPosition);
-        mBubbleBarBackground.setArrowAlpha((int) (255 * widthState));
+        mBubbleBarBackground.setArrowHeightFraction(widthState);
         mBubbleBarBackground.setWidth(interpolatedWidth);
+        mBubbleBarBackground.setBackgroundHeight(getBubbleBarExpandedHeight());
     }
 
-    private float getExpandedBubbleTranslationX(int bubbleIndex, int bubbleCount,
-            boolean onLeft) {
+    private float getScaleIconShift() {
+        return (mIconSize - getScaledIconSize()) / 2;
+    }
+
+    private float getExpandedBubbleTranslationX(int bubbleIndex, int bubbleCount, boolean onLeft) {
         if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) {
             return 0;
         }
-        if (onLeft) {
-            // If bar is on the left, bubbles are ordered right to left
-            return (bubbleCount - bubbleIndex - 1) * (mIconSize + mExpandedBarIconsSpacing);
+        final float iconAndSpacing = getScaledIconSize() + mExpandedBarIconsSpacing;
+        float translationX;
+        if (mBubbleAnimator != null && mBubbleAnimator.isRunning()) {
+            return mBubbleAnimator.getExpandedBubbleTranslationX(bubbleIndex) + mBubbleBarPadding;
+        } else if (onLeft) {
+            translationX = mBubbleBarPadding + (bubbleCount - bubbleIndex - 1) * iconAndSpacing;
         } else {
-            // Bubbles ordered left to right, don't move the first bubble
-            return bubbleIndex * (mIconSize + mExpandedBarIconsSpacing);
+            translationX = mBubbleBarPadding + bubbleIndex * iconAndSpacing;
         }
+        return translationX - getScaleIconShift();
     }
 
     private float getCollapsedBubbleTranslationX(int bubbleIndex, int bubbleCount,
@@ -771,13 +925,16 @@
         if (bubbleIndex < 0 || bubbleIndex >= bubbleCount) {
             return 0;
         }
+        float translationX;
         if (onLeft) {
             // Shift the first bubble only if there are more bubbles in addition to overflow
-            return bubbleIndex == 0 && bubbleCount > MAX_VISIBLE_BUBBLES_COLLAPSED
-                    ? mIconOverlapAmount : 0;
+            translationX = mBubbleBarPadding + (
+                    bubbleIndex == 0 && bubbleCount > MAX_VISIBLE_BUBBLES_COLLAPSED
+                            ? mIconOverlapAmount : 0);
         } else {
-            return bubbleIndex == 0 ? 0 : mIconOverlapAmount;
+            translationX = mBubbleBarPadding + (bubbleIndex == 0 ? 0 : mIconOverlapAmount);
         }
+        return translationX - getScaleIconShift();
     }
 
     /**
@@ -804,7 +961,7 @@
                     addViewInLayout(child, i, child.getLayoutParams());
                 }
             }
-            updateChildrenRenderNodeProperties(mBubbleBarLocation);
+            updateBubblesLayoutProperties(mBubbleBarLocation);
             updateContentDescription();
         }
     }
@@ -825,9 +982,11 @@
         BubbleView previouslySelectedBubble = mSelectedBubbleView;
         mSelectedBubbleView = view;
         mBubbleBarBackground.showArrow(view != null);
-        // TODO: (b/283309949) remove animation should be implemented first, so than arrow
-        //  animation is adjusted, skip animation for now
-        updateArrowForSelected(previouslySelectedBubble != null);
+
+        // if bubbles are being animated, the arrow position will be set as part of the animation
+        if (mBubbleAnimator == null) {
+            updateArrowForSelected(previouslySelectedBubble != null);
+        }
     }
 
     /**
@@ -882,16 +1041,13 @@
     }
 
     private float arrowPositionForSelectedWhenExpanded(BubbleBarLocation bubbleBarLocation) {
-        final int index = indexOfChild(mSelectedBubbleView);
-        final int bubblePosition;
-        if (bubbleBarLocation.isOnLeft(isLayoutRtl())) {
-            // Bubble positions are reversed. First bubble is on the right.
-            bubblePosition = getChildCount() - index - 1;
-        } else {
-            bubblePosition = index;
+        if (mBubbleAnimator != null && mBubbleAnimator.isRunning()) {
+            return mBubbleAnimator.getArrowPosition() + mBubbleBarPadding;
         }
-        return getPaddingStart() + bubblePosition * (mIconSize + mExpandedBarIconsSpacing)
-                + mIconSize / 2f;
+        final int index = indexOfChild(mSelectedBubbleView);
+        final float selectedBubbleTranslationX = getExpandedBubbleTranslationX(
+                index, getChildCount(), bubbleBarLocation.isOnLeft(isLayoutRtl()));
+        return selectedBubbleTranslationX + mIconSize / 2f;
     }
 
     private float arrowPositionForSelectedWhenCollapsed(BubbleBarLocation bubbleBarLocation) {
@@ -902,9 +1058,10 @@
             // bubbles than the current bubble and overflow.
             bubblePosition = index == 0 && getChildCount() > MAX_VISIBLE_BUBBLES_COLLAPSED ? 1 : 0;
         } else {
-            bubblePosition = index;
+            bubblePosition = index >= MAX_VISIBLE_BUBBLES_COLLAPSED
+                    ? MAX_VISIBLE_BUBBLES_COLLAPSED - 1 : index;
         }
-        return getPaddingStart() + bubblePosition * (mIconOverlapAmount) + mIconSize / 2f;
+        return mBubbleBarPadding + bubblePosition * (mIconOverlapAmount) + getScaledIconSize() / 2f;
     }
 
     @Override
@@ -952,20 +1109,24 @@
      */
     public float expandedWidth() {
         final int childCount = getChildCount();
-        final int horizontalPadding = getPaddingStart() + getPaddingEnd();
+        final float horizontalPadding = 2 * mBubbleBarPadding;
+        if (mBubbleAnimator != null && mBubbleAnimator.isRunning()) {
+            return mBubbleAnimator.getExpandedWidth() + horizontalPadding;
+        }
         // spaces amount is less than child count by 1, or 0 if no child views
-        int spacesCount = Math.max(childCount - 1, 0);
-        return childCount * mIconSize + spacesCount * mExpandedBarIconsSpacing + horizontalPadding;
+        final float totalSpace = Math.max(childCount - 1, 0) * mExpandedBarIconsSpacing;
+        final float totalIconSize = childCount * getScaledIconSize();
+        return totalIconSize + totalSpace + horizontalPadding;
     }
 
     private float collapsedWidth() {
         final int childCount = getChildCount();
-        final int horizontalPadding = getPaddingStart() + getPaddingEnd();
+        final float horizontalPadding = 2 * mBubbleBarPadding;
         // If there are more than 2 bubbles, the first 2 should be visible when collapsed.
         // Otherwise just the first bubble should be visible because we don't show the overflow.
         return childCount > MAX_VISIBLE_BUBBLES_COLLAPSED
-                ? mIconSize + mIconOverlapAmount + horizontalPadding
-                : mIconSize + horizontalPadding;
+                ? getScaledIconSize() + mIconOverlapAmount + horizontalPadding
+                : getScaledIconSize() + horizontalPadding;
     }
 
     private float getBubbleBarExpandedHeight() {
@@ -974,7 +1135,7 @@
 
     float getBubbleBarCollapsedHeight() {
         // the pointer is invisible when collapsed
-        return mIconSize + mBubbleBarPadding * 2;
+        return getScaledIconSize() + mBubbleBarPadding * 2;
     }
 
     /**
@@ -1047,6 +1208,46 @@
         setContentDescription(contentDesc);
     }
 
+    private boolean isIconSizeOrPaddingUpdated(float newIconSize, float newBubbleBarPadding) {
+        return isIconSizeUpdated(newIconSize) || isPaddingUpdated(newBubbleBarPadding);
+    }
+
+    private boolean isIconSizeUpdated(float newIconSize) {
+        return Float.compare(mIconSize, newIconSize) != 0;
+    }
+
+    private boolean isPaddingUpdated(float newBubbleBarPadding) {
+        return Float.compare(mBubbleBarPadding, newBubbleBarPadding) != 0;
+    }
+
+    private void addAnimationCallBacks(@NonNull ValueAnimator animator,
+            @Nullable Runnable onStart,
+            @Nullable Runnable onEnd,
+            @Nullable ValueAnimator.AnimatorUpdateListener onUpdate) {
+        if (onUpdate != null) animator.addUpdateListener(onUpdate);
+        animator.addListener(new Animator.AnimatorListener() {
+            @Override
+            public void onAnimationCancel(Animator animator) {
+
+            }
+
+            @Override
+            public void onAnimationStart(Animator animator) {
+                if (onStart != null) onStart.run();
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animator) {
+                if (onEnd != null) onEnd.run();
+            }
+
+            @Override
+            public void onAnimationRepeat(Animator animator) {
+
+            }
+        });
+    }
+
     /** Interface for BubbleBarView to communicate with its controller. */
     interface Controller {
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 45a9fa1..da0826b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -108,9 +108,11 @@
         mBubbleDragController = bubbleControllers.bubbleDragController;
         mTaskbarStashController = controllers.taskbarStashController;
         mTaskbarInsetsController = controllers.taskbarInsetsController;
+        mBubbleBarViewAnimator = new BubbleBarViewAnimator(mBarView, mBubbleStashController);
 
-        mActivity.addOnDeviceProfileChangeListener(dp -> setBubbleBarIconSize(dp.taskbarIconSize));
-        setBubbleBarIconSize(mActivity.getDeviceProfile().taskbarIconSize);
+        mActivity.addOnDeviceProfileChangeListener(
+                dp -> updateBubbleBarIconSize(dp.taskbarIconSize, /* animate= */ true));
+        updateBubbleBarIconSize(mActivity.getDeviceProfile().taskbarIconSize, /* animate= */ false);
         mBubbleBarScale.updateValue(1f);
         mBubbleClickListener = v -> onBubbleClicked(v);
         mBubbleBarClickListener = v -> onBubbleBarClicked();
@@ -123,8 +125,6 @@
                         mBoundsChangeListener.onBoundsChanged();
                     }
                 });
-
-        mBubbleBarViewAnimator = new BubbleBarViewAnimator(mBarView, mBubbleStashController);
         mBarView.setController(new BubbleBarView.Controller() {
             @Override
             public float getBubbleBarTranslationY() {
@@ -299,33 +299,6 @@
         }
     }
 
-    private void setBubbleBarIconSize(int newIconSize) {
-        if (newIconSize == mIconSize) {
-            return;
-        }
-        Resources res = mActivity.getResources();
-        DisplayMetrics dm = res.getDisplayMetrics();
-        float smallIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                APP_ICON_SMALL_DP, dm);
-        float mediumIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                APP_ICON_MEDIUM_DP, dm);
-        float largeIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
-                APP_ICON_LARGE_DP, dm);
-        float smallMediumThreshold = (smallIconSize + mediumIconSize) / 2f;
-        float mediumLargeThreshold = (mediumIconSize + largeIconSize) / 2f;
-        mIconSize = newIconSize <= smallMediumThreshold
-                ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_size_small) :
-                res.getDimensionPixelSize(R.dimen.bubblebar_icon_size);
-        float bubbleBarPadding = newIconSize >= mediumLargeThreshold
-                ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing_large) :
-                res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
-
-        mBarView.setIconSizeAndPadding(mIconSize, bubbleBarPadding);
-        mBarView.setPadding((int) bubbleBarPadding, mBarView.getPaddingTop(),
-                (int) bubbleBarPadding,
-                mBarView.getPaddingBottom());
-    }
-
     /** Sets a callback that updates the selected bubble after the bubble bar collapses. */
     public void setUpdateSelectedBubbleAfterCollapse(
             Consumer<String> updateSelectedBubbleAfterCollapse) {
@@ -360,6 +333,30 @@
     // Modifying view related properties.
     //
 
+    private void updateBubbleBarIconSize(int newIconSize, boolean animate) {
+        Resources res = mActivity.getResources();
+        DisplayMetrics dm = res.getDisplayMetrics();
+        float smallIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                APP_ICON_SMALL_DP, dm);
+        float mediumIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                APP_ICON_MEDIUM_DP, dm);
+        float largeIconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+                APP_ICON_LARGE_DP, dm);
+        float smallMediumThreshold = (smallIconSize + mediumIconSize) / 2f;
+        float mediumLargeThreshold = (mediumIconSize + largeIconSize) / 2f;
+        mIconSize = newIconSize <= smallMediumThreshold
+                ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_size_small) :
+                res.getDimensionPixelSize(R.dimen.bubblebar_icon_size);
+        float bubbleBarPadding = newIconSize >= mediumLargeThreshold
+                ? res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing_large) :
+                res.getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
+        if (animate) {
+            mBarView.animateBubbleBarIconSize(mIconSize, bubbleBarPadding);
+        } else {
+            mBarView.setIconSizeAndPadding(mIconSize, bubbleBarPadding);
+        }
+    }
+
     /**
      * Sets the translation of the bubble bar during the swipe up gesture.
      */
@@ -391,7 +388,7 @@
      */
     public void removeBubble(BubbleBarItem b) {
         if (b != null) {
-            mBarView.removeView(b.getView());
+            mBarView.removeBubble(b.getView());
         } else {
             Log.w(TAG, "removeBubble, bubble was null!");
         }
@@ -402,8 +399,8 @@
      */
     public void addBubble(BubbleBarItem b, boolean isExpanding, boolean suppressAnimation) {
         if (b != null) {
-            mBarView.addView(b.getView(), 0,
-                    new FrameLayout.LayoutParams(mIconSize, mIconSize, Gravity.LEFT));
+            mBarView.addBubble(
+                    b.getView(), new FrameLayout.LayoutParams(mIconSize, mIconSize, Gravity.LEFT));
             b.getView().setOnClickListener(mBubbleClickListener);
             mBubbleDragController.setupBubbleView(b.getView());
 
@@ -438,6 +435,11 @@
             return;
         }
 
+        if (mBubbleStashController.isBubblesShowingOnHome() && !isExpanding && !isExpanded()) {
+            mBubbleBarViewAnimator.animateBubbleBarForCollapsed(bubble);
+            return;
+        }
+
         // only animate the new bubble if we're in an app and not auto expanding
         if (isInApp && !isExpanding && !isExpanded()) {
             mBubbleBarViewAnimator.animateBubbleInForStashed(bubble);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
index fbd1b88..efc747c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
@@ -153,6 +153,7 @@
             @Override
             protected void onDragDismiss() {
                 mBubblePinController.onDragEnd();
+                mBubbleBarViewController.onBubbleDragEnd();
             }
 
             @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
index 74ddf90..185f85f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashController.java
@@ -184,7 +184,8 @@
 
     /** Whether bubbles are showing on the launcher home page. */
     public boolean isBubblesShowingOnHome() {
-        return mBubblesShowingOnHome;
+        boolean hasBubbles = mBarViewController != null && mBarViewController.hasBubbles();
+        return mBubblesShowingOnHome && hasBubbles;
     }
 
     // TODO: when tapping on an app in overview, this is a bit delayed compared to taskbar stashing
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt
new file mode 100644
index 0000000..7672743
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2024 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.taskbar.bubbles.animation
+
+import androidx.core.animation.Animator
+import androidx.core.animation.ValueAnimator
+
+/**
+ * Animates individual bubbles within the bubble bar while the bubble bar is expanded.
+ *
+ * This class should only be kept for the duration of the animation and a new instance should be
+ * created for each animation.
+ */
+class BubbleAnimator(
+    private val iconSize: Float,
+    private val expandedBarIconSpacing: Float,
+    private val bubbleCount: Int,
+    private val onLeft: Boolean,
+) {
+
+    companion object {
+        const val ANIMATION_DURATION_MS = 250L
+    }
+
+    private var state: State = State.Idle
+    private lateinit var animator: ValueAnimator
+
+    fun animateNewBubble(selectedBubbleIndex: Int, listener: Listener) {
+        animator = createAnimator(listener)
+        state = State.AddingBubble(selectedBubbleIndex)
+        animator.start()
+    }
+
+    fun animateRemovedBubble(
+        bubbleIndex: Int,
+        selectedBubbleIndex: Int,
+        removingLastBubble: Boolean,
+        listener: Listener
+    ) {
+        animator = createAnimator(listener)
+        state = State.RemovingBubble(bubbleIndex, selectedBubbleIndex, removingLastBubble)
+        animator.start()
+    }
+
+    private fun createAnimator(listener: Listener): ValueAnimator {
+        val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(ANIMATION_DURATION_MS)
+        animator.addUpdateListener { animation ->
+            val animatedFraction = (animation as ValueAnimator).animatedFraction
+            listener.onAnimationUpdate(animatedFraction)
+        }
+        animator.addListener(
+            object : Animator.AnimatorListener {
+
+                override fun onAnimationCancel(animation: Animator) {
+                    listener.onAnimationCancel()
+                }
+
+                override fun onAnimationEnd(animation: Animator) {
+                    state = State.Idle
+                    listener.onAnimationEnd()
+                }
+
+                override fun onAnimationRepeat(animation: Animator) {}
+
+                override fun onAnimationStart(animation: Animator) {}
+            }
+        )
+        return animator
+    }
+
+    /**
+     * The translation X of the bubble at index [bubbleIndex] according to the progress of the
+     * animation.
+     *
+     * Callers should verify that the animation is running before calling this.
+     *
+     * @see isRunning
+     */
+    fun getExpandedBubbleTranslationX(bubbleIndex: Int): Float {
+        return when (val state = state) {
+            State.Idle -> 0f
+            is State.AddingBubble ->
+                getExpandedBubbleTranslationXWhileScalingBubble(
+                    bubbleIndex = bubbleIndex,
+                    scalingBubbleIndex = 0,
+                    bubbleScale = animator.animatedFraction
+                )
+            is State.RemovingBubble ->
+                getExpandedBubbleTranslationXWhileScalingBubble(
+                    bubbleIndex = bubbleIndex,
+                    scalingBubbleIndex = state.bubbleIndex,
+                    bubbleScale = 1 - animator.animatedFraction
+                )
+        }
+    }
+
+    /**
+     * The expanded width of the bubble bar according to the progress of the animation.
+     *
+     * Callers should verify that the animation is running before calling this.
+     *
+     * @see isRunning
+     */
+    fun getExpandedWidth(): Float {
+        val bubbleScale =
+            when (state) {
+                State.Idle -> 0f
+                is State.AddingBubble -> animator.animatedFraction
+                is State.RemovingBubble -> 1 - animator.animatedFraction
+            }
+        // When this animator is running the bubble bar is expanded so it's safe to assume that we
+        // have at least 2 bubbles, but should update the logic to support optional overflow.
+        // If we're removing the last bubble, the entire bar should animate and we shouldn't get
+        // here.
+        val totalSpace = (bubbleCount - 2 + bubbleScale) * expandedBarIconSpacing
+        val totalIconSize = (bubbleCount - 1 + bubbleScale) * iconSize
+        return totalIconSize + totalSpace
+    }
+
+    /**
+     * Returns the arrow position according to the progress of the animation and, if the selected
+     * bubble is being removed, accounting to the newly selected bubble.
+     *
+     * Callers should verify that the animation is running before calling this.
+     *
+     * @see isRunning
+     */
+    fun getArrowPosition(): Float {
+        return when (val state = state) {
+            State.Idle -> 0f
+            is State.AddingBubble -> {
+                val tx =
+                    getExpandedBubbleTranslationXWhileScalingBubble(
+                        bubbleIndex = state.selectedBubbleIndex,
+                        scalingBubbleIndex = 0,
+                        bubbleScale = animator.animatedFraction
+                    )
+                tx + iconSize / 2f
+            }
+            is State.RemovingBubble -> getArrowPositionWhenRemovingBubble(state)
+        }
+    }
+
+    private fun getArrowPositionWhenRemovingBubble(state: State.RemovingBubble): Float {
+        return if (state.selectedBubbleIndex != state.bubbleIndex) {
+            // if we're not removing the selected bubble, the selected bubble doesn't change so just
+            // return the translation X of the selected bubble and add half icon
+            val tx =
+                getExpandedBubbleTranslationXWhileScalingBubble(
+                    bubbleIndex = state.selectedBubbleIndex,
+                    scalingBubbleIndex = state.bubbleIndex,
+                    bubbleScale = 1 - animator.animatedFraction
+                )
+            tx + iconSize / 2f
+        } else {
+            // we're removing the selected bubble so the arrow needs to point to a different bubble.
+            // if we're removing the last bubble the newly selected bubble will be the second to
+            // last. otherwise, it'll be the next bubble (closer to the overflow)
+            val iconAndSpacing = iconSize + expandedBarIconSpacing
+            if (state.removingLastBubble) {
+                if (onLeft) {
+                    // the newly selected bubble is the bubble to the right. at the end of the
+                    // animation all the bubbles will have shifted left, so the arrow stays at the
+                    // same distance from the left edge of bar
+                    (bubbleCount - state.bubbleIndex - 1) * iconAndSpacing + iconSize / 2f
+                } else {
+                    // the newly selected bubble is the bubble to the left. at the end of the
+                    // animation all the bubbles will have shifted right, and the arrow would
+                    // eventually be closer to the left edge of the bar by iconAndSpacing
+                    val initialTx = state.bubbleIndex * iconAndSpacing + iconSize / 2f
+                    initialTx - animator.animatedFraction * iconAndSpacing
+                }
+            } else {
+                if (onLeft) {
+                    // the newly selected bubble is to the left, and bubbles are shifting left, so
+                    // move the arrow closer to the left edge of the bar by iconAndSpacing
+                    val initialTx =
+                        (bubbleCount - state.bubbleIndex - 1) * iconAndSpacing + iconSize / 2f
+                    initialTx - animator.animatedFraction * iconAndSpacing
+                } else {
+                    // the newly selected bubble is to the right, and bubbles are shifting right, so
+                    // the arrow stays at the same distance from the left edge of the bar
+                    state.bubbleIndex * iconAndSpacing + iconSize / 2f
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the translation X for the bubble at index {@code bubbleIndex} when the bubble bar is
+     * expanded and a bubble is animating in or out.
+     *
+     * @param bubbleIndex the index of the bubble for which the translation is requested
+     * @param scalingBubbleIndex the index of the bubble that is animating
+     * @param bubbleScale the current scale of the animating bubble
+     */
+    private fun getExpandedBubbleTranslationXWhileScalingBubble(
+        bubbleIndex: Int,
+        scalingBubbleIndex: Int,
+        bubbleScale: Float
+    ): Float {
+        val iconAndSpacing = iconSize + expandedBarIconSpacing
+        // the bubble is scaling from the center, so we need to adjust its translation so
+        // that the distance to the adjacent bubble scales at the same rate.
+        val pivotAdjustment = -(1 - bubbleScale) * iconSize / 2f
+
+        return if (onLeft) {
+            when {
+                bubbleIndex < scalingBubbleIndex ->
+                    // the bar is on the left and the current bubble is to the right of the scaling
+                    // bubble so account for its scale
+                    (bubbleCount - bubbleIndex - 2 + bubbleScale) * iconAndSpacing
+                bubbleIndex == scalingBubbleIndex -> {
+                    // the bar is on the left and this is the scaling bubble
+                    val totalIconSize = (bubbleCount - bubbleIndex - 1) * iconSize
+                    // don't count the spacing between the scaling bubble and the bubble on the left
+                    // because we need to scale that space
+                    val totalSpacing = (bubbleCount - bubbleIndex - 2) * expandedBarIconSpacing
+                    val scaledSpace = bubbleScale * expandedBarIconSpacing
+                    totalIconSize + totalSpacing + scaledSpace + pivotAdjustment
+                }
+                else ->
+                    // the bar is on the left and the scaling bubble is on the right. the current
+                    // bubble is unaffected by the scaling bubble
+                    (bubbleCount - bubbleIndex - 1) * iconAndSpacing
+            }
+        } else {
+            when {
+                bubbleIndex < scalingBubbleIndex ->
+                    // the bar is on the right and the scaling bubble is on the right. the current
+                    // bubble is unaffected by the scaling bubble
+                    iconAndSpacing * bubbleIndex
+                bubbleIndex == scalingBubbleIndex ->
+                    // the bar is on the right, and this is the animating bubble. it only needs to
+                    // be adjusted for the scaling pivot.
+                    iconAndSpacing * bubbleIndex + pivotAdjustment
+                else ->
+                    // the bar is on the right and the scaling bubble is on the left so account for
+                    // its scale
+                    iconAndSpacing * (bubbleIndex - 1 + bubbleScale)
+            }
+        }
+    }
+
+    val isRunning: Boolean
+        get() = state != State.Idle
+
+    /** The state of the animation. */
+    sealed interface State {
+
+        /** The animation is not running. */
+        data object Idle : State
+
+        /** A new bubble is being added to the bubble bar. */
+        data class AddingBubble(val selectedBubbleIndex: Int) : State
+
+        /** A bubble is being removed from the bubble bar. */
+        data class RemovingBubble(
+            /** The index of the bubble being removed. */
+            val bubbleIndex: Int,
+            /** The index of the selected bubble. */
+            val selectedBubbleIndex: Int,
+            /** Whether the bubble being removed is also the last bubble. */
+            val removingLastBubble: Boolean
+        ) : State
+    }
+
+    /** Callbacks for the animation. */
+    interface Listener {
+
+        /**
+         * Notifies the listener of an animation update event, where `animatedFraction` represents
+         * the progress of the animation starting from 0 and ending at 1.
+         */
+        fun onAnimationUpdate(animatedFraction: Float)
+
+        /** Notifies the listener that the animation was canceled. */
+        fun onAnimationCancel()
+
+        /** Notifies that listener that the animation ended. */
+        fun onAnimationEnd()
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
index d88e272..feff9fd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
@@ -18,8 +18,12 @@
 
 import android.view.View
 import android.view.View.VISIBLE
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorListenerAdapter
+import androidx.core.animation.ObjectAnimator
 import androidx.dynamicanimation.animation.DynamicAnimation
 import androidx.dynamicanimation.animation.SpringForce
+import com.android.launcher3.R
 import com.android.launcher3.taskbar.bubbles.BubbleBarBubble
 import com.android.launcher3.taskbar.bubbles.BubbleBarView
 import com.android.launcher3.taskbar.bubbles.BubbleStashController
@@ -36,6 +40,8 @@
 ) {
 
     private var animatingBubble: AnimatingBubble? = null
+    private val bubbleBarBounceDistanceInPx =
+        bubbleBarView.resources.getDimensionPixelSize(R.dimen.bubblebar_bounce_distance)
 
     private companion object {
         /** The time to show the flyout. */
@@ -44,6 +50,8 @@
         const val BUBBLE_ANIMATION_INITIAL_SCALE_Y = 0.3f
         /** The minimum alpha value to make the bubble bar touchable. */
         const val MIN_ALPHA_FOR_TOUCHABLE = 0.5f
+        /** The duration of the bounce animation. */
+        const val BUBBLE_BAR_BOUNCE_ANIMATION_DURATION_MS = 250L
     }
 
     /** Wrapper around the animating bubble with its show and hide animations. */
@@ -277,7 +285,7 @@
         if (animator.isRunning()) animator.cancel()
         // the animation of a new bubble is divided into 2 parts. The first part shows the bubble
         // and the second part hides it after a delay if we are in an app.
-        val showAnimation = buildBubbleBarBounceAnimation()
+        val showAnimation = buildBubbleBarSpringInAnimation()
         val hideAnimation =
             if (isInApp && !isExpanding) {
                 buildBubbleBarToHandleAnimation()
@@ -296,7 +304,7 @@
         scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
     }
 
-    private fun buildBubbleBarBounceAnimation() = Runnable {
+    private fun buildBubbleBarSpringInAnimation() = Runnable {
         // prepare the bubble bar for the animation
         bubbleBarView.onAnimatingBubbleStarted()
         bubbleBarView.translationY = bubbleBarView.height.toFloat()
@@ -316,6 +324,42 @@
         animator.start()
     }
 
+    fun animateBubbleBarForCollapsed(b: BubbleBarBubble) {
+        val bubbleView = b.view
+        val animator = PhysicsAnimator.getInstance(bubbleView)
+        if (animator.isRunning()) animator.cancel()
+        val showAnimation = buildBubbleBarBounceAnimation()
+        val hideAnimation = Runnable {
+            animatingBubble = null
+            bubbleStashController.showBubbleBarImmediate()
+            bubbleBarView.onAnimatingBubbleCompleted()
+            bubbleStashController.updateTaskbarTouchRegion()
+        }
+        animatingBubble = AnimatingBubble(bubbleView, showAnimation, hideAnimation)
+        scheduler.post(showAnimation)
+        scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
+    }
+
+    /**
+     * The bubble bar animation when it is collapsed is divided into 2 chained animations. The first
+     * animation is a regular accelerate animation that moves the bubble bar upwards. When it ends
+     * the bubble bar moves back to its initial position with a spring animation.
+     */
+    private fun buildBubbleBarBounceAnimation() = Runnable {
+        bubbleBarView.onAnimatingBubbleStarted()
+        val ty = bubbleStashController.bubbleBarTranslationY
+
+        val springBackAnimation = PhysicsAnimator.getInstance(bubbleBarView)
+        springBackAnimation.setDefaultSpringConfig(springConfig)
+        springBackAnimation.spring(DynamicAnimation.TRANSLATION_Y, ty)
+
+        // animate the bubble bar up and start the spring back down animation when it ends.
+        ObjectAnimator.ofFloat(bubbleBarView, View.TRANSLATION_Y, ty - bubbleBarBounceDistanceInPx)
+            .withDuration(BUBBLE_BAR_BOUNCE_ANIMATION_DURATION_MS)
+            .withEndAction { springBackAnimation.start() }
+            .start()
+    }
+
     /** Handles touching the animating bubble bar. */
     fun onBubbleBarTouchedWhileAnimating() {
         PhysicsAnimator.getInstance(bubbleBarView).cancelIfRunning()
@@ -344,4 +388,20 @@
     private fun <T> PhysicsAnimator<T>.cancelIfRunning() {
         if (isRunning()) cancel()
     }
+
+    private fun ObjectAnimator.withDuration(duration: Long): ObjectAnimator {
+        setDuration(duration)
+        return this
+    }
+
+    private fun ObjectAnimator.withEndAction(endAction: () -> Unit): ObjectAnimator {
+        addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator) {
+                    endAction()
+                }
+            }
+        )
+        return this
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt
new file mode 100644
index 0000000..3c4b63a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarContainer.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 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.taskbar.customization
+
+/** Enums for all feature container that taskbar supports. */
+enum class TaskbarContainer {
+    ALL_APPS,
+    DIVIDER,
+    APP_ICONS,
+    RECENTS,
+    NAV_BUTTONS,
+    BUBBLES,
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
new file mode 100644
index 0000000..ac7dd06
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarFeatureEvaluator.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2024 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.taskbar.customization
+
+import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
+import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarControllers
+import com.android.launcher3.util.DisplayController
+
+/** Evaluates all the features taskbar can have. */
+class TaskbarFeatureEvaluator(
+    private val taskbarActivityContext: TaskbarActivityContext,
+    private val taskbarControllers: TaskbarControllers,
+) {
+
+    val hasAllApps = true
+    val hasAppIcons = true
+    val hasBubbles = false
+    val hasNavButtons = taskbarActivityContext.isThreeButtonNav
+
+    val hasRecents: Boolean
+        get() = taskbarControllers.taskbarRecentAppsController.shownTasks.isNotEmpty()
+
+    val hasDivider: Boolean
+        get() = enableTaskbarPinning() || hasRecents
+
+    val isTransient: Boolean
+        get() = DisplayController.isTransientTaskbar(taskbarActivityContext)
+
+    val isLandscape: Boolean
+        get() = taskbarActivityContext.deviceProfile.isLandscape
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
new file mode 100644
index 0000000..67bbcce
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarIconSpecs.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 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.taskbar.customization
+
+/** Taskbar Icon Specs */
+object TaskbarIconSpecs {
+
+    val iconSize40dp = TaskbarIconSize(40)
+    val iconSize44dp = TaskbarIconSize(44)
+    val iconSize48dp = TaskbarIconSize(48)
+    val iconSize52dp = TaskbarIconSize(52)
+
+    val transientTaskbarIconSizes = arrayOf(iconSize44dp, iconSize48dp, iconSize52dp)
+
+    val defaultPersistentIconSize = iconSize40dp
+    val defaultTransientIconSize = iconSize44dp
+
+    val transientTaskbarIconSizeByGridSize =
+        mapOf(
+            TransientTaskbarIconSizeKey(6, 5, false) to iconSize52dp,
+            TransientTaskbarIconSizeKey(6, 5, true) to iconSize52dp,
+            TransientTaskbarIconSizeKey(4, 4, false) to iconSize48dp,
+            TransientTaskbarIconSizeKey(4, 4, true) to iconSize52dp,
+            TransientTaskbarIconSizeKey(4, 5, false) to iconSize48dp,
+            TransientTaskbarIconSizeKey(4, 5, true) to iconSize48dp,
+            TransientTaskbarIconSizeKey(5, 5, false) to iconSize44dp,
+            TransientTaskbarIconSizeKey(5, 5, true) to iconSize44dp,
+        )
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
new file mode 100644
index 0000000..0b7be40
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2024 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.taskbar.customization
+
+/** Evaluates the taskbar specs based on the taskbar grid size and the taskbar icon size. */
+class TaskbarSpecsEvaluator(private val taskbarFeatureEvaluator: TaskbarFeatureEvaluator) {
+
+    fun getIconSizeByGrid(row: Int, column: Int): TaskbarIconSize {
+        return if (taskbarFeatureEvaluator.isTransient) {
+            TaskbarIconSpecs.transientTaskbarIconSizeByGridSize.getOrDefault(
+                TransientTaskbarIconSizeKey(row, column, taskbarFeatureEvaluator.isLandscape),
+                TaskbarIconSpecs.defaultTransientIconSize,
+            )
+        } else {
+            TaskbarIconSpecs.defaultPersistentIconSize
+        }
+    }
+
+    fun getIconSizeStepDown(iconSize: TaskbarIconSize): TaskbarIconSize {
+        if (!taskbarFeatureEvaluator.isTransient) return TaskbarIconSpecs.defaultPersistentIconSize
+
+        val currentIconSizeIndex = TaskbarIconSpecs.transientTaskbarIconSizes.indexOf(iconSize)
+        // return the current icon size if supplied icon size is unknown or we have reached the
+        // min icon size.
+        return if (currentIconSizeIndex == -1 || currentIconSizeIndex == 0) iconSize
+        else TaskbarIconSpecs.transientTaskbarIconSizes[currentIconSizeIndex - 1]
+    }
+
+    fun getIconSizeStepUp(iconSize: TaskbarIconSize): TaskbarIconSize {
+        if (!taskbarFeatureEvaluator.isTransient) return TaskbarIconSpecs.defaultPersistentIconSize
+
+        val currentIconSizeIndex = TaskbarIconSpecs.transientTaskbarIconSizes.indexOf(iconSize)
+        // return the current icon size if supplied icon size is unknown or we have reached the
+        // max icon size.
+        return if (
+            currentIconSizeIndex == -1 ||
+                currentIconSizeIndex == TaskbarIconSpecs.transientTaskbarIconSizes.size - 1
+        ) {
+            iconSize
+        } else {
+            TaskbarIconSpecs.transientTaskbarIconSizes.get(currentIconSizeIndex + 1)
+        }
+    }
+}
+
+data class TaskbarIconSize(val size: Int)
+
+data class TransientTaskbarIconSizeKey(val row: Int, val column: Int, val isLandscape: Boolean)
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
index adbec65..7eb34a5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayController.java
@@ -133,16 +133,19 @@
      * <p>
      * This method should be called after an exit animation finishes, if applicable.
      */
-    @SuppressLint("WrongConstant")
     void maybeCloseWindow() {
-        if (mOverlayContext != null && (AbstractFloatingView.hasOpenView(mOverlayContext, TYPE_ALL)
-                || mOverlayContext.getDragController().isSystemDragInProgress())) {
-            return;
-        }
+        if (!canCloseWindow()) return;
         mProxyView.close(false);
         onDestroy();
     }
 
+    @SuppressLint("WrongConstant")
+    private boolean canCloseWindow() {
+        if (mOverlayContext == null) return true;
+        if (AbstractFloatingView.hasOpenView(mOverlayContext, TYPE_ALL)) return false;
+        return !mOverlayContext.getDragController().isSystemDragInProgress();
+    }
+
     /** Destroys the controller and any overlay window if present. */
     public void onDestroy() {
         TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
@@ -212,10 +215,17 @@
 
         @Override
         protected void handleClose(boolean animate) {
-            if (mIsOpen) {
-                mTaskbarContext.getDragLayer().removeView(this);
-                Optional.ofNullable(mOverlayContext).ifPresent(c -> closeAllOpenViews(c, animate));
-            }
+            if (!mIsOpen) return;
+            mTaskbarContext.getDragLayer().removeView(this);
+            Optional.ofNullable(mOverlayContext).ifPresent(c -> {
+                if (canCloseWindow()) {
+                    onDestroy(); // Window is already ready to be destroyed.
+                } else {
+                    // Close window's AFVs before destroying it. Its drag layer will attempt to
+                    // close the proxy view again once its children are removed.
+                    closeAllOpenViews(c, animate);
+                }
+            });
         }
 
         @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index e3a2bab..037f2f6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -60,6 +60,8 @@
 import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.QUICK_SWITCH_FROM_HOME_FAILED;
+import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.QUICK_SWITCH_FROM_HOME_FALLBACK;
 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;
@@ -172,6 +174,7 @@
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.TouchInteractionService.TISBinder;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.AsyncClockEventDelegate;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LauncherUnfoldAnimationController;
@@ -198,8 +201,6 @@
 import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver;
 import com.android.systemui.unfold.updates.RotationChangeProvider;
 
-import kotlin.Unit;
-
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -212,6 +213,8 @@
 import java.util.function.Predicate;
 import java.util.stream.Stream;
 
+import kotlin.Unit;
+
 public class QuickstepLauncher extends Launcher implements RecentsViewContainer {
     private static final boolean TRACE_LAYOUTS =
             SystemProperties.getBoolean("persist.debug.trace_layouts", false);
@@ -581,9 +584,19 @@
             }
             case QUICK_SWITCH_STATE_ORDINAL: {
                 RecentsView rv = getOverviewPanel();
-                TaskView tasktolaunch = rv.getCurrentPageTaskView();
-                if (tasktolaunch != null) {
-                    tasktolaunch.launchTask(success -> {
+                TaskView currentPageTask = rv.getCurrentPageTaskView();
+                TaskView fallbackTask = rv.getTaskViewAt(0);
+                if (currentPageTask != null || fallbackTask != null) {
+                    TaskView taskToLaunch = currentPageTask;
+                    if (currentPageTask == null) {
+                        taskToLaunch = fallbackTask;
+                        ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                                "Quick switch from home fallback case: The TaskView at index ")
+                                        .append(rv.getCurrentPage())
+                                        .append(" is missing."),
+                                QUICK_SWITCH_FROM_HOME_FALLBACK);
+                    }
+                    taskToLaunch.launchTask(success -> {
                         if (!success) {
                             getStateManager().goToState(OVERVIEW);
                         } else {
@@ -592,6 +605,11 @@
                         return Unit.INSTANCE;
                     });
                 } else {
+                    ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                            "Quick switch from home failed: TaskViews at indices ")
+                                    .append(rv.getCurrentPage())
+                                    .append(" and 0 are missing."),
+                            QUICK_SWITCH_FROM_HOME_FAILED);
                     getStateManager().goToState(NORMAL);
                 }
                 break;
@@ -1118,7 +1136,7 @@
     }
 
     @Override
-    protected void collectStateHandlers(List<StateHandler> out) {
+    public void collectStateHandlers(List<StateHandler<LauncherState>> out) {
         super.collectStateHandlers(out);
         out.add(getDepthController());
         out.add(new RecentsViewStateController(this));
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
index 01d5ff0..56fc4d1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetHostView;
@@ -100,7 +99,7 @@
                                     // for concurrent modification.
                                     new ArrayList<>(h.mProviderChangedListeners).forEach(
                                     ProviderChangedListener::notifyWidgetProvidersChanged))),
-                    UI_HELPER_EXECUTOR.getLooper());
+                    getWidgetHolderExecutor().getLooper());
             if (WIDGETS_ENABLED) {
                 sWidgetHost.startListening();
             }
@@ -199,8 +198,10 @@
             return;
         }
 
-        sWidgetHost.setAppWidgetHidden();
-        setListeningFlag(false);
+        getWidgetHolderExecutor().execute(() -> {
+            sWidgetHost.setAppWidgetHidden();
+            setListeningFlag(false);
+        });
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
index 146ff3d..0469636 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
@@ -17,24 +17,32 @@
 
 import android.app.ActivityOptions
 import android.app.PendingIntent
+import android.app.role.RoleManager
 import android.content.Context
+import android.content.IIntentReceiver
+import android.content.IIntentSender
 import android.content.Intent
 import android.content.pm.ActivityInfo
 import android.content.pm.LauncherActivityInfo
 import android.content.pm.LauncherApps
 import android.content.pm.ShortcutInfo
+import android.os.Bundle
 import android.os.Flags.allowPrivateProfile
+import android.os.IBinder
 import android.os.UserHandle
 import android.os.UserManager
 import android.util.ArrayMap
+import android.widget.Toast
 import android.window.RemoteTransition
 import com.android.launcher3.Flags.enablePrivateSpace
 import com.android.launcher3.Flags.enablePrivateSpaceInstallShortcut
 import com.android.launcher3.Flags.privateSpaceAppInstallerButton
 import com.android.launcher3.Flags.privateSpaceSysAppsSeparation
+import com.android.launcher3.R
 import com.android.launcher3.Utilities
 import com.android.launcher3.proxy.ProxyActivityStarter
 import com.android.launcher3.util.ApiWrapper
+import com.android.launcher3.util.Executors
 import com.android.launcher3.util.StartActivityParams
 import com.android.launcher3.util.UserIconInfo
 import com.android.quickstep.util.FadeOutRemoteTransition
@@ -115,8 +123,7 @@
                     intentSender =
                         mContext
                             .getSystemService(LauncherApps::class.java)
-                            ?.privateSpaceSettingsIntent
-                            ?: return null
+                            ?.privateSpaceSettingsIntent ?: return null
                     options =
                         ActivityOptions.makeBasic()
                             .setPendingIntentBackgroundActivityStartMode(
@@ -130,4 +137,50 @@
 
     override fun isNonResizeableActivity(lai: LauncherActivityInfo) =
         lai.activityInfo.resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE
+
+    /**
+     * Starts an Activity which can be used to set this Launcher as the HOME app, via a consent
+     * screen. In case the consent screen cannot be shown, or the user does not set current Launcher
+     * as HOME app, a toast asking the user to do the latter is shown.
+     */
+    override fun assignDefaultHomeRole(context: Context) {
+        val roleManager = context.getSystemService(RoleManager::class.java)
+        if (
+            (roleManager!!.isRoleAvailable(RoleManager.ROLE_HOME) &&
+                !roleManager.isRoleHeld(RoleManager.ROLE_HOME))
+        ) {
+            val roleRequestIntent = roleManager.createRequestRoleIntent(RoleManager.ROLE_HOME)
+            val pendingIntent =
+                PendingIntent(
+                    object : IIntentSender.Stub() {
+                        override fun send(
+                            code: Int,
+                            intent: Intent,
+                            resolvedType: String?,
+                            allowlistToken: IBinder?,
+                            finishedReceiver: IIntentReceiver?,
+                            requiredPermission: String?,
+                            options: Bundle?
+                        ) {
+                            if (code != -1) {
+                                Executors.MAIN_EXECUTOR.execute {
+                                    Toast.makeText(
+                                            context,
+                                            context.getString(
+                                                R.string.set_default_home_app,
+                                                context.getString(R.string.derived_app_name)
+                                            ),
+                                            Toast.LENGTH_LONG
+                                        )
+                                        .show()
+                                }
+                            }
+                        }
+                    }
+                )
+            val params = StartActivityParams(pendingIntent, 0)
+            params.intent = roleRequestIntent
+            context.startActivity(ProxyActivityStarter.getLaunchIntent(context, params))
+        }
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 463222d..fb2a982 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -32,6 +32,7 @@
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
 import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
 import static com.android.launcher3.Flags.enableGridOnlyOverview;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.PagedView.INVALID_PAGE;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
@@ -1193,6 +1194,10 @@
                 setDividerShown(true);
                 break;
         }
+        if (mContainerInterface.getTaskbarController() != null) {
+            // Resets this value as the gesture is now complete.
+            mContainerInterface.getTaskbarController().setUserIsNotGoingHome(false);
+        }
         ActiveGestureLog.INSTANCE.addLog(
                 new ActiveGestureLog.CompoundString("onSettledOnEndTarget ")
                         .append(endTarget.name()),
@@ -1200,17 +1205,28 @@
     }
 
     /** @return Whether this was the task we were waiting to appear, and thus handled it. */
-    protected boolean handleTaskAppeared(RemoteAnimationTarget[] appearedTaskTarget) {
+    protected boolean handleTaskAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets,
+            @NonNull ActiveGestureLog.CompoundString failureReason) {
         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
+            failureReason.append("State handler was invalidated");
             return false;
         }
-        boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTarget).anyMatch(
-                mGestureState.mLastStartedTaskIdPredicate);
-        if (mStateCallback.hasStates(STATE_START_NEW_TASK) && hasStartedTaskBefore) {
-            reset();
-            return true;
+        boolean stateStartNewTaskSet = mStateCallback.hasStates(STATE_START_NEW_TASK);
+        if (!stateStartNewTaskSet || !hasStartedTaskBefore(appearedTaskTargets)) {
+            if (!stateStartNewTaskSet) {
+                failureReason.append("STATE_START_NEW_TASK was never set");
+            } else {
+                TaskInfo taskInfo = appearedTaskTargets[0].taskInfo;
+                failureReason.append("Unexpected task appeared")
+                                .append(" id=")
+                                .append(taskInfo.taskId)
+                                .append(" pkg=")
+                                .append(taskInfo.baseIntent.getComponent().getPackageName());
+            }
+            return false;
         }
-        return false;
+        reset();
+        return true;
     }
 
     private float dpiFromPx(float pixels) {
@@ -1341,6 +1357,13 @@
         mGestureState.setEndTarget(endTarget, false /* isAtomic */);
         mAnimationFactory.setEndTarget(endTarget);
 
+        if (enableScalingRevealHomeAnimation()
+                && mIsTransientTaskbar
+                && mContainerInterface.getTaskbarController() != null) {
+            mContainerInterface.getTaskbarController()
+                    .setUserIsNotGoingHome(endTarget != GestureState.GestureEndTarget.HOME);
+        }
+
         float endShift = endTarget == ALL_APPS ? mDragLengthFactor
                 : endTarget.isLauncher ? 1 : 0;
         final float startShift;
@@ -1614,7 +1637,8 @@
                         mSwipePipToHomeAnimator.getComponentName(),
                         mSwipePipToHomeAnimator.getDestinationBounds(),
                         mSwipePipToHomeAnimator.getContentOverlay(),
-                        mSwipePipToHomeAnimator.getAppBounds());
+                        mSwipePipToHomeAnimator.getAppBounds(),
+                        mSwipePipToHomeAnimator.getSourceRectHint());
 
                 windowAnim = mSwipePipToHomeAnimators;
             } else {
@@ -1783,6 +1807,8 @@
                 && (windowRotation == ROTATION_90 || windowRotation == ROTATION_270)) {
             builder.setFromRotation(mRemoteTargetHandles[0].getTaskViewSimulator(), windowRotation,
                     taskInfo.displayCutoutInsets);
+        } else if (taskInfo.displayCutoutInsets != null) {
+            builder.setDisplayCutoutInsets(taskInfo.displayCutoutInsets);
         }
         final SwipePipToHomeAnimator swipePipToHomeAnimator = builder.build();
         AnimatorPlaybackController activityAnimationToHome =
@@ -2387,14 +2413,18 @@
         }
     }
 
+    private boolean hasStartedTaskBefore(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
+        return Arrays.stream(appearedTaskTargets)
+                .anyMatch(mGestureState.mLastStartedTaskIdPredicate);
+    }
+
     @Override
     public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
         if (mRecentsAnimationController == null) {
             return;
         }
-        boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTargets).anyMatch(
-                mGestureState.mLastStartedTaskIdPredicate);
-        if (!mStateCallback.hasStates(STATE_GESTURE_COMPLETED) && !hasStartedTaskBefore) {
+        if (!mStateCallback.hasStates(STATE_GESTURE_COMPLETED)
+                && !hasStartedTaskBefore(appearedTaskTargets)) {
             // This is a special case, if a task is started mid-gesture that wasn't a part of a
             // previous quickswitch task launch, then cancel the animation back to the app
             RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
@@ -2408,7 +2438,11 @@
             finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
             return;
         }
-        if (!handleTaskAppeared(appearedTaskTargets)) {
+        ActiveGestureLog.CompoundString handleTaskFailureReason =
+                new ActiveGestureLog.CompoundString("handleTaskAppeared check failed: ");
+        if (!handleTaskAppeared(appearedTaskTargets, handleTaskFailureReason)) {
+            ActiveGestureLog.INSTANCE.addLog(handleTaskFailureReason);
+            finishRecentsAnimationOnTasksAppeared(null /* onFinishComplete */);
             return;
         }
         Optional<RemoteAnimationTarget> taskTargetOptional =
diff --git a/quickstep/src/com/android/quickstep/BinderTracker.java b/quickstep/src/com/android/quickstep/BinderTracker.java
index a876cd8..2a42861 100644
--- a/quickstep/src/com/android/quickstep/BinderTracker.java
+++ b/quickstep/src/com/android/quickstep/BinderTracker.java
@@ -26,11 +26,14 @@
 import android.os.Trace;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.TraceHelper;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.LinkedList;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -43,6 +46,9 @@
 public class BinderTracker {
 
     private static final String TAG = "BinderTracker";
+    private static final Boolean DEBUG_STACKTRACE = false;
+
+    private static final String[] sActionablePackageKeywords = {"launcher3", "systemui"};
 
     // Common IPCs that are ok to block the main thread.
     private static final Set<String> sAllowedFrameworkClasses = Set.of(
@@ -145,13 +151,32 @@
 
             if (ipcBypass == null) {
                 mUnexpectedTransactionCallback.accept(new BinderCallSite(
-                        mMainThreadTraceStack.peekLast(), descriptor, transactionCode));
+                        mMainThreadTraceStack.peekLast(), descriptor, transactionCode,
+                        getActionableStacktrace()));
             } else {
                 Log.d(TAG, "MainThread-IPC " + descriptor + " ignored due to " + ipcBypass);
             }
             return null;
         }
 
+        @NonNull
+        private static String getActionableStacktrace() {
+            if (!DEBUG_STACKTRACE) {
+                return "DEBUG_STACKTRACE not turned on.";
+            }
+            final StringWriter sw = new StringWriter();
+            new Throwable().printStackTrace(new PrintWriter(sw));
+            final String stackTrace = sw.toString();
+
+            for (String actionablePackageKeyword : sActionablePackageKeywords) {
+                if (stackTrace.contains(actionablePackageKeyword)) {
+                    return stackTrace;
+                }
+            }
+
+            return "Not actionable to launcher";
+        }
+
         @Override
         public Object onTransactStarted(IBinder binder, int transactionCode) {
             // Do nothing
@@ -177,11 +202,14 @@
         public final String activeTrace;
         public final String descriptor;
         public final int transactionCode;
+        public final String stackTrace;
 
-        BinderCallSite(String activeTrace, String descriptor, int transactionCode) {
+        BinderCallSite(
+                String activeTrace, String descriptor, int transactionCode, String stackTrace) {
             this.activeTrace = activeTrace;
             this.descriptor = descriptor;
             this.transactionCode = transactionCode;
+            this.stackTrace = stackTrace;
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index 9e896fd..89fbf4a 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -154,7 +154,8 @@
 
     @Override
     public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
-        final StateManager<RecentsState> stateManager = getCreatedContainer().getStateManager();
+        final StateManager<RecentsState, RecentsActivity> stateManager =
+                getCreatedContainer().getStateManager();
         if (stateManager.getState() == HOME) {
             exitRunnable.run();
             notifyRecentsOfOrientation(deviceState);
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 625b6c6..9b66154 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -64,6 +64,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.RectFSpringAnim;
 import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
 import com.android.quickstep.util.TransformParams;
@@ -170,14 +171,16 @@
     }
 
     @Override
-    protected boolean handleTaskAppeared(RemoteAnimationTarget[] appearedTaskTarget) {
+    protected boolean handleTaskAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTarget,
+            @NonNull ActiveGestureLog.CompoundString failureReason) {
         if (mActiveAnimationFactory != null
                 && mActiveAnimationFactory.handleHomeTaskAppeared(appearedTaskTarget)) {
             mActiveAnimationFactory = null;
+            failureReason.append("(FallbackSwipeHandler) should be handled as home task appeared");
             return false;
         }
 
-        return super.handleTaskAppeared(appearedTaskTarget);
+        return super.handleTaskAppeared(appearedTaskTarget, failureReason);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 08c2e1c..1048ea1 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -35,7 +35,7 @@
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Flags;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherInitListener;
 import com.android.launcher3.LauncherState;
@@ -212,10 +212,7 @@
         if (launcher.isStarted() && (isInLiveTileMode() || launcher.hasBeenResumed())) {
             return launcher;
         }
-        if (Flags.useActivityOverlay()
-                && SystemUiProxy.INSTANCE.get(launcher).getHomeVisibilityState().isHomeVisible()) {
-            return launcher;
-        }
+
         return null;
     }
 
@@ -242,7 +239,8 @@
 
     @Override
     public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
-        final StateManager<LauncherState> stateManager = getCreatedContainer().getStateManager();
+        final StateManager<LauncherState, Launcher> stateManager =
+                getCreatedContainer().getStateManager();
         stateManager.addStateListener(
                 new StateManager.StateListener<LauncherState>() {
                     @Override
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 3c66590..e17cdcd 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -144,8 +144,6 @@
         return new FloatingViewHomeAnimationFactory(floatingIconView) {
             @Nullable
             private RectF mTargetRect;
-            @Nullable
-            private RectFSpringAnim mSiblingAnimation;
 
             @Nullable
             @Override
@@ -173,14 +171,6 @@
             }
 
             @Override
-            protected void playScalingRevealAnimation() {
-                if (mContainer != null) {
-                    new ScalingWorkspaceRevealAnim(mContainer, mSiblingAnimation,
-                            getWindowTargetRect()).start();
-                }
-            }
-
-            @Override
             public void setAnimation(RectFSpringAnim anim) {
                 super.setAnimation(anim);
                 mSiblingAnimation = anim;
@@ -245,6 +235,8 @@
                 isTargetTranslucent, fallbackBackgroundColor);
 
         return new FloatingViewHomeAnimationFactory(floatingWidgetView) {
+            @Nullable
+            private RectF mTargetRect;
 
             @Override
             @Nullable
@@ -254,8 +246,14 @@
 
             @Override
             public RectF getWindowTargetRect() {
-                super.getWindowTargetRect();
-                return backgroundLocation;
+                if (enableScalingRevealHomeAnimation()) {
+                    if (mTargetRect == null) {
+                        mTargetRect = new RectF(backgroundLocation);
+                    }
+                    return mTargetRect;
+                } else {
+                    return backgroundLocation;
+                }
             }
 
             @Override
@@ -266,10 +264,11 @@
             @Override
             public void setAnimation(RectFSpringAnim anim) {
                 super.setAnimation(anim);
-
-                anim.addAnimatorListener(floatingWidgetView);
-                floatingWidgetView.setOnTargetChangeListener(anim::onTargetPositionChanged);
-                floatingWidgetView.setFastFinishRunnable(anim::end);
+                mSiblingAnimation = anim;
+                mSiblingAnimation.addAnimatorListener(floatingWidgetView);
+                floatingWidgetView.setOnTargetChangeListener(
+                        mSiblingAnimation::onTargetPositionChanged);
+                floatingWidgetView.setFastFinishRunnable(mSiblingAnimation::end);
             }
 
             @Override
@@ -330,14 +329,23 @@
     }
 
     private class FloatingViewHomeAnimationFactory extends LauncherHomeAnimationFactory {
-
         private final FloatingView mFloatingView;
+        @Nullable
+        protected RectFSpringAnim mSiblingAnimation;
 
         FloatingViewHomeAnimationFactory(FloatingView floatingView) {
             mFloatingView = floatingView;
         }
 
         @Override
+        protected void playScalingRevealAnimation() {
+            if (mContainer != null) {
+                new ScalingWorkspaceRevealAnim(mContainer, mSiblingAnimation,
+                        getWindowTargetRect()).start();
+            }
+        }
+
+        @Override
         public void onCancel() {
             mFloatingView.fastFinish();
         }
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 26a7c2f..8f533a3 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -228,8 +228,9 @@
         }
         BaseActivityInterface<?, T> activityInterface =
                 mOverviewComponentObserver.getActivityInterface();
-        RecentsView visibleRecentsView = activityInterface.getVisibleRecentsView();
-        RecentsView createdRecentsView;
+
+        RecentsView<?, ?> visibleRecentsView = activityInterface.getVisibleRecentsView();
+        RecentsView<?, ?> createdRecentsView;
 
         Log.d(TAG, "executeCommand: " + cmd
                 + " - visibleRecentsView: " + visibleRecentsView);
@@ -264,6 +265,10 @@
                 case TYPE_HOME:
                     ActiveGestureLog.INSTANCE.addLog(
                             "OverviewCommandHelper.executeCommand(TYPE_HOME)");
+                    // Although IActivityTaskManager$Stub$Proxy.startActivity is a slow binder call,
+                    // we should still call it on main thread because launcher is waiting for
+                    // ActivityTaskManager to resume it. Also calling startActivity() on bg thread
+                    // could potentially delay resuming launcher. See b/348668521 for more details.
                     mService.startActivity(mOverviewComponentObserver.getHomeIntent());
                     return true;
                 case TYPE_SHOW:
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index a71e314..9c64576 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -36,6 +36,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
 
 import com.android.launcher3.R;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -101,7 +102,7 @@
             mConfigChangesMap.append(fallbackComponent.hashCode(), fallbackInfo.configChanges);
         } catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ }
 
-        mUserPreferenceChangeReceiver.register(mContext, ACTION_PREFERRED_ACTIVITY_CHANGED);
+        mUserPreferenceChangeReceiver.registerAsync(mContext, ACTION_PREFERRED_ACTIVITY_CHANGED);
         updateOverviewTargets();
     }
 
@@ -114,6 +115,8 @@
         mOverviewChangeListener = overviewChangeListener;
     }
 
+    /** Called on {@link TouchInteractionService#onSystemUiFlagsChanged} */
+    @UiThread
     public void onSystemUiStateChanged() {
         if (mDeviceState.isHomeDisabled() != mIsHomeDisabled) {
             updateOverviewTargets();
@@ -128,6 +131,7 @@
      * Update overview intent and {@link BaseActivityInterface} based off the current launcher home
      * component.
      */
+    @UiThread
     private void updateOverviewTargets() {
         ComponentName defaultHome = PackageManagerWrapper.getInstance()
                 .getHomeActivities(new ArrayList<>());
@@ -187,8 +191,9 @@
                 unregisterOtherHomeAppUpdateReceiver();
 
                 mUpdateRegisteredPackage = defaultHome.getPackageName();
-                mOtherHomeAppUpdateReceiver.registerPkgActions(mContext, mUpdateRegisteredPackage,
-                        ACTION_PACKAGE_ADDED, ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REMOVED);
+                mOtherHomeAppUpdateReceiver.registerPkgActionsAsync(
+                        mContext, mUpdateRegisteredPackage, ACTION_PACKAGE_ADDED,
+                        ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REMOVED);
             }
         }
         mOverviewChangeListener.accept(mIsHomeAndOverviewSame);
@@ -198,13 +203,13 @@
      * Clean up any registered receivers.
      */
     public void onDestroy() {
-        mContext.unregisterReceiver(mUserPreferenceChangeReceiver);
+        mUserPreferenceChangeReceiver.unregisterReceiverSafelyAsync(mContext);
         unregisterOtherHomeAppUpdateReceiver();
     }
 
     private void unregisterOtherHomeAppUpdateReceiver() {
         if (mUpdateRegisteredPackage != null) {
-            mContext.unregisterReceiver(mOtherHomeAppUpdateReceiver);
+            mOtherHomeAppUpdateReceiver.unregisterReceiverSafelyAsync(mContext);
             mUpdateRegisteredPackage = null;
         }
     }
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index 3c902e6..f4e68dc 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -66,7 +66,8 @@
 
         if (BuildConfig.IS_STUDIO_BUILD) {
             BinderTracker.startTracking(call ->  Log.e("BinderCall",
-                    call.descriptor + " called on mainthread under " + call.activeTrace));
+                    call.descriptor + " called on main thread under " + call.activeTrace
+                            + " stackTrace: " + call.stackTrace));
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 37b4dca..66091d4 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -70,12 +70,13 @@
     private TaskLoadResult mResultsBg = INVALID_RESULT;
     private TaskLoadResult mResultsUi = INVALID_RESULT;
 
-    private RecentsModel.RunningTasksListener mRunningTasksListener;
+    private @Nullable RecentsModel.RunningTasksListener mRunningTasksListener;
+    private @Nullable RecentsModel.RecentTasksChangedListener mRecentTasksChangedListener;
     // Tasks are stored in order of least recently launched to most recently launched.
     private ArrayList<ActivityManager.RunningTaskInfo> mRunningTasks;
 
     public RecentTasksList(LooperExecutor mainThreadExecutor, KeyguardManager keyguardManager,
-            SystemUiProxy sysUiProxy) {
+            SystemUiProxy sysUiProxy, TopTaskTracker topTaskTracker) {
         mMainThreadExecutor = mainThreadExecutor;
         mKeyguardManager = keyguardManager;
         mChangeId = 1;
@@ -106,6 +107,13 @@
                     RecentTasksList.this.onRunningTaskChanged(taskInfo);
                 });
             }
+
+            @Override
+            public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
+                mMainThreadExecutor.execute(() -> {
+                    topTaskTracker.onTaskMovedToFront(taskInfo);
+                });
+            }
         });
         // We may receive onRunningTaskAppeared events later for tasks which have already been
         // included in the list returned by mSysUiProxy.getRunningTasks(), or may receive
@@ -135,7 +143,7 @@
      * Asynchronously fetches the list of recent tasks, reusing cached list if available.
      *
      * @param loadKeysOnly Whether to load other associated task data, or just the key
-     * @param callback The callback to receive the list of recent tasks
+     * @param callback     The callback to receive the list of recent tasks
      * @return The change id of the current task list
      */
     public synchronized int getTasks(boolean loadKeysOnly,
@@ -192,6 +200,9 @@
 
     public void onRecentTasksChanged() {
         invalidateLoadedTasks();
+        if (mRecentTasksChangedListener != null) {
+            mRecentTasksChangedListener.onRecentTasksChanged();
+        }
     }
 
     private synchronized void invalidateLoadedTasks() {
@@ -200,7 +211,7 @@
         mChangeId++;
     }
 
-     /**
+    /**
      * Registers a listener for running tasks
      */
     public void registerRunningTasksListener(RecentsModel.RunningTasksListener listener) {
@@ -214,6 +225,21 @@
         mRunningTasksListener = null;
     }
 
+    /**
+     * Registers a listener for running tasks
+     */
+    public void registerRecentTasksChangedListener(
+            RecentsModel.RecentTasksChangedListener listener) {
+        mRecentTasksChangedListener = listener;
+    }
+
+    /**
+     * Removes the previously registered running tasks listener
+     */
+    public void unregisterRecentTasksChangedListener() {
+        mRecentTasksChangedListener = null;
+    }
+
     private void initRunningTasks(ArrayList<ActivityManager.RunningTaskInfo> runningTasks) {
         // Tasks are retrieved in order of most recently launched/used to least recently launched.
         mRunningTasks = new ArrayList<>(runningTasks);
@@ -350,6 +376,7 @@
             task.setLastSnapshotData(taskInfo);
             task.positionInParent = taskInfo.positionInParent;
             task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds();
+            task.isVisible = taskInfo.isVisible;
             tasks.add(task);
         }
         return new DesktopTask(tasks);
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 3df62da..13e9844 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -115,7 +115,7 @@
     private TISBindHelper mTISBindHelper;
     private @Nullable FallbackTaskbarUIController mTaskbarUIController;
 
-    private StateManager<RecentsState> mStateManager;
+    private StateManager<RecentsState, RecentsActivity> mStateManager;
 
     // Strong refs to runners which are cleared when the activity is destroyed
     private RemoteAnimationFactory mActivityLaunchAnimationRunner;
@@ -386,6 +386,11 @@
         }
     }
 
+    @Override
+    public boolean shouldAnimateStateChange() {
+        return false;
+    }
+
     /**
      * Initialize/update the device profile.
      */
@@ -462,12 +467,12 @@
             };
 
     @Override
-    protected void collectStateHandlers(List<StateHandler> out) {
+    public void collectStateHandlers(List<StateHandler<RecentsState>> out) {
         out.add(new FallbackRecentsStateController(this));
     }
 
     @Override
-    public StateManager<RecentsState> getStateManager() {
+    public StateManager<RecentsState, RecentsActivity> getStateManager() {
         return mStateManager;
     }
 
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 8bcdaa3..7adce74 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -45,6 +45,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED;
 
 import android.app.ActivityTaskManager;
 import android.content.Context;
@@ -386,7 +387,7 @@
         boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
                 || (mSystemUiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0
                 || mRotationTouchHelper.isTaskListFrozen();
-        return canStartWithNavHidden && canStartTrackpadGesture();
+        return canStartWithNavHidden && canStartAnyGesture();
     }
 
     /**
@@ -395,14 +396,24 @@
      * mode.
      */
     public boolean canStartTrackpadGesture() {
-        return (mSystemUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) == 0
-                && (mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING) == 0
-                && (mSystemUiStateFlags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) == 0
-                && (mSystemUiStateFlags & SYSUI_STATE_MAGNIFICATION_OVERLAP) == 0
-                && ((mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
-                        || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0)
-                && (mSystemUiStateFlags & SYSUI_STATE_DEVICE_DREAMING) == 0
-                && (mSystemUiStateFlags & SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION) == 0;
+        boolean trackpadGesturesEnabled =
+                (mSystemUiStateFlags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) == 0;
+        return trackpadGesturesEnabled && canStartAnyGesture();
+    }
+
+    /**
+     * Common logic to determine if either trackpad or finger gesture can be started
+     */
+    private boolean canStartAnyGesture() {
+        boolean homeOrOverviewEnabled = (mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) == 0
+                || (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
+        long gestureDisablingStates = 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;
+        return (gestureDisablingStates & mSystemUiStateFlags) == 0 && homeOrOverviewEnabled;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 98c1eb4..b796951 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -42,6 +42,7 @@
 import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.quickstep.recents.data.RecentTasksDataSource;
+import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.TaskVisualsChangeListener;
 import com.android.systemui.shared.recents.model.Task;
@@ -90,7 +91,8 @@
         this(context,
                 new RecentTasksList(MAIN_EXECUTOR,
                         context.getSystemService(KeyguardManager.class),
-                        SystemUiProxy.INSTANCE.get(context)),
+                        SystemUiProxy.INSTANCE.get(context),
+                        TopTaskTracker.INSTANCE.get(context)),
                 new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider),
                 new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR),
                 iconProvider,
@@ -300,6 +302,8 @@
 
     /**
      * Registers a listener for running tasks
+     * TODO(b/343292503): Should we remove RunningTasksListener entirely if it's not needed?
+     *  (Note that Desktop mode gets the running tasks by checking {@link DesktopTask#tasks}
      */
     public void registerRunningTasksListener(RunningTasksListener listener) {
         mTaskList.registerRunningTasksListener(listener);
@@ -313,6 +317,20 @@
     }
 
     /**
+     * Registers a listener for recent tasks
+     */
+    public void registerRecentTasksChangedListener(RecentTasksChangedListener listener) {
+        mTaskList.registerRecentTasksChangedListener(listener);
+    }
+
+    /**
+     * Removes the previously registered running tasks listener
+     */
+    public void unregisterRecentTasksChangedListener() {
+        mTaskList.unregisterRecentTasksChangedListener();
+    }
+
+    /**
      * Gets the set of running tasks.
      */
     public ArrayList<ActivityManager.RunningTaskInfo> getRunningTasks() {
@@ -378,4 +396,14 @@
          */
         void onRunningTasksChanged();
     }
+
+    /**
+     * Listener for receiving recent tasks changes
+     */
+    public interface RecentTasksChangedListener {
+        /**
+         * Called when there's a change to recent tasks
+         */
+        void onRecentTasksChanged();
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index fb54241..ba33c62 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -17,6 +17,7 @@
 
 import static com.android.app.animation.Interpolators.ACCELERATE_1_5;
 import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
 import static com.android.launcher3.PagedView.INVALID_PAGE;
 
 import android.animation.Animator;
@@ -449,7 +450,7 @@
             float alpha = mAnimationFactory.getWindowAlpha(progress);
 
             mHomeAnim.setPlayFraction(progress);
-            if (mTargetTaskView == null) {
+            if (!enableAdditionalHomeAnimations() || mTargetTaskView == null) {
                 mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect);
                 mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
                 mLocalTransformParams
@@ -464,10 +465,15 @@
 
             mLocalTransformParams.applySurfaceParams(
                     mLocalTransformParams.createSurfaceParams(this));
-            mAnimationFactory.update(
-                    currentRect, progress, mMatrix.mapRadius(cornerRadius), (int) (alpha * 255));
 
-            if (mTargetTaskView == null) {
+            mAnimationFactory.update(
+                    currentRect,
+                    progress,
+                    mMatrix.mapRadius(cornerRadius),
+                    !enableAdditionalHomeAnimations() || mTargetTaskView == null
+                            ? 0 : (int) (alpha * 255));
+
+            if (!enableAdditionalHomeAnimations() || mTargetTaskView == null) {
                 return;
             }
             if (mAnimationFactory.isAnimatingIntoIcon() && mAnimationFactory.isAnimationReady()) {
@@ -506,7 +512,7 @@
         public void onAnimationStart(Animator animation) {
             setUp();
             mHomeAnim.dispatchOnStart();
-            if (mTargetTaskView == null) {
+            if (!enableAdditionalHomeAnimations() || mTargetTaskView == null) {
                 return;
             }
             Rect thumbnailBounds = new Rect();
@@ -521,7 +527,7 @@
         }
 
         private void setUp() {
-            if (mTargetTaskView == null) {
+            if (!enableAdditionalHomeAnimations() || mTargetTaskView == null) {
                 return;
             }
             RecentsView recentsView = mTargetTaskView.getRecentsView();
@@ -542,7 +548,7 @@
         }
 
         private void cleanUp() {
-            if (mTargetTaskView == null) {
+            if (!enableAdditionalHomeAnimations() || mTargetTaskView == null) {
                 return;
             }
             RecentsView recentsView = mTargetTaskView.getRecentsView();
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 9c3fbaa..433baa9 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -684,11 +684,11 @@
      * should be responsible for cleaning up the overlay.
      */
     public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
-            SurfaceControl overlay, Rect appBounds) {
+            SurfaceControl overlay, Rect appBounds, Rect sourceRectHint) {
         if (mPip != null) {
             try {
                 mPip.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay,
-                        appBounds);
+                        appBounds, sourceRectHint);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call stopSwipePipToHome");
             }
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 2348f28..08bb6cd 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -26,6 +26,8 @@
 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_INITIALIZED;
 import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION;
+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 android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -44,9 +46,11 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.util.ActiveGestureLog;
+import com.android.quickstep.util.SystemUiFlagUtils;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
@@ -96,6 +100,11 @@
     TaskAnimationManager(Context ctx) {
         mCtx = ctx;
     }
+
+    SystemUiProxy getSystemUiProxy() {
+        return SystemUiProxy.INSTANCE.get(mCtx);
+    }
+
     /**
      * Preloads the recents animation.
      */
@@ -149,7 +158,7 @@
         final BaseContainerInterface containerInterface = gestureState.getContainerInterface();
         mLastGestureState = gestureState;
         RecentsAnimationCallbacks newCallbacks = new RecentsAnimationCallbacks(
-                SystemUiProxy.INSTANCE.get(mCtx), containerInterface.allowMinimizeSplitScreen());
+                getSystemUiProxy(), containerInterface.allowMinimizeSplitScreen());
         mCallbacks = newCallbacks;
         mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
             @Override
@@ -256,7 +265,7 @@
                 }
 
                 RemoteAnimationTarget[] nonAppTargets = ENABLE_SHELL_TRANSITIONS
-                        ? null : SystemUiProxy.INSTANCE.get(mCtx).onStartingSplitLegacy(
+                        ? null : getSystemUiProxy().onStartingSplitLegacy(
                                 appearedTaskTargets);
                 if (nonAppTargets == null) {
                     nonAppTargets = new RemoteAnimationTarget[0];
@@ -323,12 +332,13 @@
 
         if (ENABLE_SHELL_TRANSITIONS) {
             final ActivityOptions options = ActivityOptions.makeBasic();
+            options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true);
             // Use regular (non-transient) launch for all apps page to control IME.
             if (!containerInterface.allowAllAppsFromOverview()) {
                 options.setTransientLaunch();
             }
             options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
-            mRecentsAnimationStartPending = SystemUiProxy.INSTANCE.get(mCtx)
+            mRecentsAnimationStartPending = getSystemUiProxy()
                     .startRecentsActivity(intent, options, mCallbacks);
             if (enableHandleDelayedGestureCallbacks()) {
                 ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
@@ -373,20 +383,54 @@
         return mCallbacks;
     }
 
-    public void endLiveTile() {
-        if (mLastGestureState == null) {
-            return;
-        }
-        BaseContainerInterface containerInterface = mLastGestureState.getContainerInterface();
-        if (containerInterface.isInLiveTileMode()
-                && containerInterface.getCreatedContainer() != null) {
-            RecentsView recentsView = containerInterface.getCreatedContainer().getOverviewPanel();
-            if (recentsView != null) {
-                recentsView.switchToScreenshot(null,
-                        () -> recentsView.finishRecentsAnimation(true /* toRecents */,
-                                false /* shouldPip */, null));
+    public void onSystemUiFlagsChanged(@QuickStepContract.SystemUiStateFlags long lastSysUIFlags,
+            @QuickStepContract.SystemUiStateFlags long newSysUIFlags) {
+        long isShadeExpandedFlagMask =
+                SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
+        boolean wasExpanded = hasAnyFlag(lastSysUIFlags, isShadeExpandedFlagMask);
+        boolean isExpanded = hasAnyFlag(newSysUIFlags, isShadeExpandedFlagMask);
+        if (wasExpanded != isExpanded && isExpanded) {
+            // End live tile when expanding the notification panel for the first time from
+            // overview.
+            if (endLiveTile()) {
+                return;
             }
         }
+
+        boolean wasLocked = SystemUiFlagUtils.isLocked(lastSysUIFlags);
+        boolean isLocked = SystemUiFlagUtils.isLocked(newSysUIFlags);
+        if (wasLocked != isLocked && isLocked) {
+            // Finish the running recents animation when locking the device.
+            finishRunningRecentsAnimation(
+                    mController != null && mController.getFinishTargetIsLauncher());
+        }
+    }
+
+    private boolean hasAnyFlag(long flags, long flagMask) {
+        return (flags & flagMask) != 0;
+    }
+
+    /**
+     * Switches the {@link RecentsView} to screenshot if in live tile mode.
+     *
+     * @return true iff the {@link RecentsView} was in live tile mode and was switched to screenshot
+     */
+    public boolean endLiveTile() {
+        if (mLastGestureState == null) {
+            return false;
+        }
+        BaseContainerInterface containerInterface = mLastGestureState.getContainerInterface();
+        if (!containerInterface.isInLiveTileMode()
+                || containerInterface.getCreatedContainer() == null) {
+            return false;
+        }
+        RecentsView recentsView = containerInterface.getCreatedContainer().getOverviewPanel();
+        if (recentsView == null) {
+            return false;
+        }
+        recentsView.switchToScreenshot(null, () -> recentsView.finishRecentsAnimation(
+                true /* toRecents */, false /* shouldPip */, null));
+        return true;
     }
 
     public void setLiveTileCleanUpHandler(Runnable cleanUpHandler) {
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index fd141c3..4691ea9 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -228,6 +228,7 @@
 
                 // Take the thumbnail of the task without a scrim and apply it back after
                 float alpha = mThumbnailView.getDimAlpha();
+                // TODO(b/348643341) add ability to get override the scrim for this Bitmap retrieval
                 mThumbnailView.setDimAlpha(0);
                 Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
                         taskBounds.width(), taskBounds.height(), mThumbnailView, 1f,
@@ -492,18 +493,8 @@
                 TaskContainer taskContainer) {
             boolean isTablet = container.getDeviceProfile().isTablet;
             boolean isGridOnlyOverview = isTablet && Flags.enableGridOnlyOverview();
-            // Extra conditions if it's not grid-only overview
             if (!isGridOnlyOverview) {
-                RecentsOrientedState orientedState = taskContainer.getTaskView().getOrientedState();
-                boolean isFakeLandscape = !orientedState.isRecentsActivityRotationAllowed()
-                        && orientedState.getTouchRotation() != ROTATION_0;
-                if (!isFakeLandscape) {
-                    return null;
-                }
-                // Disallow "Select" when swiping up from landscape due to rotated thumbnail.
-                if (orientedState.getDisplayRotation() != ROTATION_0) {
-                    return null;
-                }
+                return null;
             }
 
             SystemShortcut modalStateSystemShortcut =
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index 29c80fd..6ed05c8 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -64,7 +64,7 @@
         implements TaskStackChangeListener, SafeCloseable {
 
     private static final String TAG = "TopTaskTracker";
-    
+
     private static final boolean DEBUG = true;
 
     public static MainThreadInitializedObject<TopTaskTracker> INSTANCE =
@@ -204,7 +204,7 @@
 
     /**
      * @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
+     * Will return empty array if device is not in staged split
      */
     public int[] getRunningSplitTaskIds() {
         if (mMainStagePosition.taskId == INVALID_TASK_ID
@@ -295,7 +295,7 @@
          * If the given task holds an activity that is excluded from recents, and there
          * is another running task that is not excluded from recents, returns that underlying task.
          */
-        public @Nullable CachedTaskInfo otherVisibleTaskThisIsExcludedOver() {
+        public @Nullable CachedTaskInfo getVisibleNonExcludedTask() {
             if (mTopTask == null
                     || (mTopTask.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0) {
                 // Not an excluded task.
@@ -303,7 +303,9 @@
             }
             List<RunningTaskInfo> visibleNonExcludedTasks = mAllCachedTasks.stream()
                     .filter(t -> t.isVisible
-                            && (t.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0)
+                            && (t.baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0
+                            && t.getActivityType() != ACTIVITY_TYPE_HOME
+                            && t.getActivityType() != ACTIVITY_TYPE_RECENTS)
                     .toList();
             return visibleNonExcludedTasks.isEmpty() ? null
                     : new CachedTaskInfo(visibleNonExcludedTasks);
@@ -337,7 +339,7 @@
          */
         public Task[] getPlaceholderTasks() {
             return mTopTask == null ? new Task[0]
-                    : new Task[] {Task.from(new TaskKey(mTopTask), mTopTask, false)};
+                    : new Task[]{Task.from(new TaskKey(mTopTask), mTopTask, false)};
         }
 
         /**
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 7aa99d9..bfdc3df 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -47,8 +47,6 @@
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SYSUI_PROXY;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNFOLD_ANIMATION_FORWARDER;
 import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER;
-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.wm.shell.Flags.enableBubblesLongPressNavHandle;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
@@ -512,6 +510,9 @@
 
                 private boolean isTrackpadDevice(int deviceId) {
                     InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
+                    if (inputDevice == null) {
+                        return false;
+                    }
                     return inputDevice.getSources() == (InputDevice.SOURCE_MOUSE
                             | InputDevice.SOURCE_TOUCHPAD);
                 }
@@ -578,6 +579,7 @@
         mMainChoreographer = Choreographer.getInstance();
         mAM = ActivityManagerWrapper.getInstance();
         mDeviceState = new RecentsAnimationDeviceState(this, true);
+        mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
         mAllAppsActionManager = new AllAppsActionManager(
                 this, UI_HELPER_EXECUTOR, this::createAllAppsPendingIntent);
         mInputManager = getSystemService(InputManager.class);
@@ -590,7 +592,6 @@
             }
         }
         mTaskbarManager = new TaskbarManager(this, mAllAppsActionManager, mNavCallbacks);
-        mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
 
         // Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
@@ -719,16 +720,7 @@
             SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
             mOverviewComponentObserver.onSystemUiStateChanged();
             mTaskbarManager.onSystemUiFlagsChanged(systemUiStateFlags);
-
-            long isShadeExpandedFlag =
-                    SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
-            boolean wasExpanded = (lastSysUIFlags & isShadeExpandedFlag) != 0;
-            boolean isExpanded = (systemUiStateFlags & isShadeExpandedFlag) != 0;
-            if (wasExpanded != isExpanded && isExpanded) {
-                // End live tile when expanding the notification panel for the first time from
-                // overview.
-                mTaskAnimationManager.endLiveTile();
-            }
+            mTaskAnimationManager.onSystemUiFlagsChanged(lastSysUIFlags, systemUiStateFlags);
         }
     }
 
@@ -1253,7 +1245,7 @@
         // running activity as the task behind the overlay.
         TopTaskTracker.CachedTaskInfo otherVisibleTask = runningTask == null
                 ? null
-                : runningTask.otherVisibleTaskThisIsExcludedOver();
+                : runningTask.getVisibleNonExcludedTask();
         if (otherVisibleTask != null) {
             ActiveGestureLog.INSTANCE.addLog(new CompoundString("Changing active task to ")
                     .append(otherVisibleTask.getPackageName())
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 485d6c4..f4a2738 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -93,7 +93,7 @@
     }
 
     @Override
-    public StateManager<RecentsState> getStateManager() {
+    public StateManager<RecentsState, RecentsActivity> getStateManager() {
         return mContainer.getStateManager();
     }
 
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index 9a25c32..95295b0 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -21,6 +21,7 @@
 import static android.view.MotionEvent.INVALID_POINTER_ID;
 
 import static com.android.launcher3.Flags.enableCursorHoverStates;
+import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.MotionEventsUtils.isTrackpadMotionEvent;
 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TOUCHING;
 
@@ -32,6 +33,7 @@
 import android.os.Looper;
 import android.view.InputDevice;
 import android.view.MotionEvent;
+import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 
 import androidx.annotation.Nullable;
@@ -55,6 +57,9 @@
 public class TaskbarUnstashInputConsumer extends DelegateInputConsumer {
 
     private static final int HOVER_TASKBAR_UNSTASH_TIMEOUT = 500;
+
+    private static final int NUM_MOTION_MOVE_THRESHOLD = 3;
+
     private static final Handler sUnstashHandler = new Handler(Looper.getMainLooper());
 
     private final TaskbarActivityContext mTaskbarActivityContext;
@@ -83,6 +88,11 @@
 
     private final @Nullable TransitionCallback mTransitionCallback;
     private final GestureState mGestureState;
+    private VelocityTracker mVelocityTracker;
+    private boolean mCanPlayTaskbarBgAlphaAnimation = true;
+    private int mMotionMoveCount = 0;
+    // Velocity defined as dp per s
+    private float mTaskbarSlowVelocityYThreshold;
 
     public TaskbarUnstashInputConsumer(Context context, InputConsumer delegate,
             InputMonitorCompat inputMonitor, TaskbarActivityContext taskbarActivityContext,
@@ -101,6 +111,8 @@
         mIsTaskbarAllAppsOpen = mTaskbarActivityContext.isTaskbarAllAppsOpen();
 
         mIsTransientTaskbar = DisplayController.isTransientTaskbar(context);
+        mTaskbarSlowVelocityYThreshold =
+                res.getDimensionPixelSize(R.dimen.taskbar_slow_velocity_y_threshold);
 
         mBottomScreenEdge = res.getDimensionPixelSize(
                 R.dimen.taskbar_stashed_screen_edge_hover_deadzone_height);
@@ -125,6 +137,9 @@
 
     @Override
     public void onMotionEvent(MotionEvent ev) {
+        if (enableScalingRevealHomeAnimation() && mIsTransientTaskbar) {
+            checkVelocityForTaskbarBackground(ev);
+        }
         if (mState != STATE_ACTIVE) {
             boolean isStashedTaskbarHovered = isMouseEvent(ev)
                     && isStashedTaskbarHovered((int) ev.getX(), (int) ev.getY());
@@ -254,6 +269,31 @@
         }
     }
 
+    private void checkVelocityForTaskbarBackground(MotionEvent ev) {
+        int actionMasked = ev.getActionMasked();
+        if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) {
+            mVelocityTracker.clear();
+        }
+        if (mVelocityTracker == null) {
+            mVelocityTracker = VelocityTracker.obtain();
+        }
+        mVelocityTracker.addMovement(ev);
+
+        mVelocityTracker.computeCurrentVelocity(1000);
+        if (ev.getAction() == ACTION_MOVE) {
+            mMotionMoveCount++;
+        }
+
+        float velocityYPxPerS = mVelocityTracker.getYVelocity();
+        if (mCanPlayTaskbarBgAlphaAnimation
+                && mMotionMoveCount >= NUM_MOTION_MOVE_THRESHOLD // Arbitrary value
+                && velocityYPxPerS != 0 // Ignore these
+                && velocityYPxPerS >= mTaskbarSlowVelocityYThreshold) {
+            mTaskbarActivityContext.playTaskbarBackgroundAlphaAnimation();
+            mCanPlayTaskbarBgAlphaAnimation = false;
+        }
+    }
+
     private void cleanupAfterMotionEvent() {
         mTaskbarActivityContext.setAutohideSuspendFlag(
                 FLAG_AUTOHIDE_SUSPEND_TOUCHING, false);
@@ -264,6 +304,13 @@
         mIsInBubbleBarArea = false;
         mIsVerticalGestureOverBubbleBar = false;
         mIsPassedBubbleBarSlop = false;
+
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+        }
+        mVelocityTracker = null;
+        mCanPlayTaskbarBgAlphaAnimation = true;
+        mMotionMoveCount = 0;
     }
 
     private boolean isInBubbleBarArea(float x) {
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index 4f1dbbe..36ea926 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -59,6 +59,7 @@
 
     @Nullable private TutorialType[] mTutorialSteps;
     private GestureSandboxFragment mCurrentFragment;
+    private GestureSandboxFragment mPendingFragment;
 
     private int mCurrentStep;
     private int mNumSteps;
@@ -176,16 +177,26 @@
                     && getResources().getConfiguration().orientation
                     == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 
-            showFragment(showRotationPrompt
+            GestureSandboxFragment fragment = showRotationPrompt
                     ? new RotationPromptFragment()
-                    : mCurrentFragment.canRecreateFragment()
-                            ? mCurrentFragment.recreateFragment() : mCurrentFragment);
+                    : mCurrentFragment.canRecreateFragment() || mPendingFragment == null
+                            ? mCurrentFragment.recreateFragment()
+                            : mPendingFragment.recreateFragment();
+            showFragment(fragment == null ? mCurrentFragment : fragment);
+
         } else {
             setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
         }
     }
 
     private void showFragment(@NonNull GestureSandboxFragment fragment) {
+        // Store the current fragment in mPendingFragment so that it can be recreated after the
+        // new fragment is shown.
+        if (mCurrentFragment.canRecreateFragment()) {
+            mPendingFragment = mCurrentFragment;
+        } else {
+            mPendingFragment = null;
+        }
         mCurrentFragment = fragment;
         getSupportFragmentManager().beginTransaction()
                 .replace(R.id.gesture_tutorial_fragment_container, mCurrentFragment)
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
index 758a737..eeacee1 100644
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
@@ -259,7 +259,8 @@
             return new Pair<>(translationX, translationY);
         }
 
-        bannerParams.gravity = BOTTOM | ((deviceProfile.isLandscape) ? START : CENTER_HORIZONTAL);
+        bannerParams.gravity =
+                BOTTOM | (deviceProfile.isLeftRightSplit ? START : CENTER_HORIZONTAL);
 
         // Set correct width
         if (desiredTaskId == splitBounds.leftTopTaskId) {
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 2836c89..dbe2b19 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -53,25 +53,31 @@
         TaskThumbnailViewModel(
             recentsView.mRecentsViewData,
             (parent as TaskView).taskViewData,
+            (parent as TaskView).getTaskContainerForTaskThumbnailView(this)!!.taskContainerData,
             recentsView.mTasksRepository,
         )
     }
 
     private var uiState: TaskThumbnailUiState = Uninitialized
     private var inheritedScale: Float = 1f
+    private var dimProgress: Float = 0f
 
     private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
+    private val scrimPaint = Paint().apply { color = Color.BLACK }
     private val _measuredBounds = Rect()
     private val measuredBounds: Rect
         get() {
             _measuredBounds.set(0, 0, measuredWidth, measuredHeight)
             return _measuredBounds
         }
+
     private var cornerRadius: Float = TaskCornerRadius.get(context)
     private var fullscreenCornerRadius: Float = QuickStepContract.getWindowCornerRadius(context)
 
     constructor(context: Context?) : super(context)
+
     constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
+
     constructor(
         context: Context?,
         attrs: AttributeSet?,
@@ -87,6 +93,13 @@
                 invalidate()
             }
         }
+        MainScope().launch {
+            viewModel.dimProgress.collect { dimProgress ->
+                // TODO(b/348195366) Add fade in/out for scrim
+                this@TaskThumbnailView.dimProgress = dimProgress
+                invalidate()
+            }
+        }
         MainScope().launch { viewModel.recentsFullscreenProgress.collect { invalidateOutline() } }
         MainScope().launch {
             viewModel.inheritedScale.collect { viewModelInheritedScale ->
@@ -111,6 +124,10 @@
             is Snapshot -> drawSnapshotState(canvas, uiStateVal)
             is BackgroundOnly -> drawBackgroundOnly(canvas, uiStateVal.backgroundColor)
         }
+
+        if (dimProgress > 0) {
+            drawScrim(canvas)
+        }
     }
 
     private fun drawBackgroundOnly(canvas: Canvas, @ColorInt backgroundColor: Int) {
@@ -135,6 +152,11 @@
         canvas.drawBitmap(snapshot.bitmap, snapshot.drawnRect, measuredBounds, null)
     }
 
+    private fun drawScrim(canvas: Canvas) {
+        scrimPaint.alpha = (dimProgress * MAX_SCRIM_ALPHA).toInt()
+        canvas.drawRect(measuredBounds, scrimPaint)
+    }
+
     private fun getCurrentCornerRadius() =
         Utilities.mapRange(
             viewModel.recentsFullscreenProgress.value,
@@ -145,5 +167,6 @@
     companion object {
         private val CLEAR_PAINT =
             Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) }
+        private const val MAX_SCRIM_ALPHA = (0.4f * 255).toInt()
     }
 }
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt
index 4511ea7..fe21174 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModel.kt
@@ -25,6 +25,7 @@
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
+import com.android.quickstep.task.viewmodel.TaskContainerData
 import com.android.quickstep.task.viewmodel.TaskViewData
 import com.android.systemui.shared.recents.model.Task
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -40,6 +41,7 @@
 class TaskThumbnailViewModel(
     recentsViewData: RecentsViewData,
     taskViewData: TaskViewData,
+    taskContainerData: TaskContainerData,
     private val tasksRepository: RecentTasksRepository,
 ) {
     private val task = MutableStateFlow<Flow<Task?>>(flowOf(null))
@@ -50,6 +52,7 @@
         combine(recentsViewData.scale, taskViewData.scale) { recentsScale, taskScale ->
             recentsScale * taskScale
         }
+    val dimProgress: Flow<Float> = taskContainerData.taskMenuOpenProgress
     val uiState: Flow<TaskThumbnailUiState> =
         task
             .flatMapLatest { taskFlow ->
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
new file mode 100644
index 0000000..769424c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.task.viewmodel
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class TaskContainerData {
+    val taskMenuOpenProgress = MutableStateFlow(0f)
+}
diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
index cfa6b98..3140fff 100644
--- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
+++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java
@@ -40,6 +40,7 @@
         SCROLLER_ANIMATION_ABORTED, TASK_APPEARED, EXPECTING_TASK_APPEARED,
         FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER, LAUNCHER_DESTROYED, RECENT_TASKS_MISSING,
         INVALID_VELOCITY_ON_SWIPE_UP, RECENTS_ANIMATION_START_PENDING,
+        QUICK_SWITCH_FROM_HOME_FALLBACK, QUICK_SWITCH_FROM_HOME_FAILED,
 
         /**
          * These GestureEvents are specifically associated to state flags that get set in
@@ -282,6 +283,22 @@
                                     + " animation is still pending.",
                             writer);
                     break;
+                case QUICK_SWITCH_FROM_HOME_FALLBACK:
+                    errorDetected |= printErrorIfTrue(
+                            true,
+                            prefix,
+                            /* errorMessage= */ "Quick switch from home fallback case: the "
+                                    + "TaskView at the current page index was missing.",
+                            writer);
+                    break;
+                case QUICK_SWITCH_FROM_HOME_FAILED:
+                    errorDetected |= printErrorIfTrue(
+                            true,
+                            prefix,
+                            /* errorMessage= */ "Quick switch from home failed: the TaskViews at "
+                                    + "the current page index and index 0 were missing.",
+                            writer);
+                    break;
                 case EXPECTING_TASK_APPEARED:
                 case MOTION_DOWN:
                 case SET_END_TARGET:
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index c9647f5..c7c04ed 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -39,6 +39,7 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.AllAppsSwipeController;
 import com.android.quickstep.DeviceConfigWrapper;
@@ -163,7 +164,8 @@
                     recentsOrientedState.getContainerInterface().getCreatedContainer();
             if (container != null) {
                 RecentsView recentsView = container.getOverviewPanel();
-                StateManager<LauncherState> stateManager = recentsView.getStateManager();
+                StateManager<LauncherState, StatefulActivity<LauncherState>> stateManager =
+                        recentsView.getStateManager();
                 if (stateManager.isInStableState(LauncherState.BACKGROUND_APP)
                         && stateManager.isInTransition()) {
 
diff --git a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
index cda87c0..c26fc0c5 100644
--- a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
+++ b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
@@ -18,8 +18,6 @@
 import static android.content.Intent.ACTION_TIMEZONE_CHANGED;
 import static android.content.Intent.ACTION_TIME_CHANGED;
 
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -64,9 +62,7 @@
     private AsyncClockEventDelegate(Context context) {
         super(context);
         mContext = context;
-
-        UI_HELPER_EXECUTOR.execute(() ->
-                mReceiver.register(mContext, ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED));
+        mReceiver.registerAsync(mContext, ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED);
     }
 
     @Override
@@ -127,6 +123,6 @@
     public void close() {
         mDestroyed = true;
         SettingsCache.INSTANCE.get(mContext).unregister(mFormatUri, this);
-        UI_HELPER_EXECUTOR.execute(() -> mReceiver.unregisterReceiverSafely(mContext));
+        mReceiver.unregisterReceiverSafelyAsync(mContext);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
index 132d1c1..492c801 100644
--- a/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
+++ b/quickstep/src/com/android/quickstep/util/OverviewToHomeAnim.java
@@ -62,7 +62,7 @@
      * {@link WorkspaceRevealAnim}.
      */
     public void animateWithVelocity(float velocity) {
-        StateManager<LauncherState> stateManager = mLauncher.getStateManager();
+        StateManager<LauncherState, Launcher> stateManager = mLauncher.getStateManager();
         LauncherState startState = stateManager.getState();
         if (startState != OVERVIEW) {
             Log.e(TAG, "animateFromOverviewToHome: unexpected start state " + startState);
diff --git a/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java
index 3d9e09e..cbe0f19 100644
--- a/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java
+++ b/quickstep/src/com/android/quickstep/util/PhoneSplitToConfirmTimings.java
@@ -27,6 +27,8 @@
     public int getPlaceholderIconFadeInEnd() { return 133; }
     public int getStagedRectSlideStart() { return 0; }
     public int getStagedRectSlideEnd() { return 333; }
+    public int getBackingScrimFadeInStart() { return 0; }
+    public int getBackingScrimFadeInEnd() { return 266; }
 
     public int getDuration() { return PHONE_CONFIRM_DURATION; }
 }
diff --git a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
index 2a27dea..cfe5b2c 100644
--- a/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
+++ b/quickstep/src/com/android/quickstep/util/QuickstepOnboardingPrefs.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_SEEN;
 import static com.android.launcher3.util.OnboardingPrefs.HOTSEAT_DISCOVERY_TIP_COUNT;
 
+import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
@@ -48,7 +49,7 @@
      * Sets up the initial onboarding behavior for the launcher
      */
     public static void setup(QuickstepLauncher launcher) {
-        StateManager<LauncherState> stateManager = launcher.getStateManager();
+        StateManager<LauncherState, Launcher> stateManager = launcher.getStateManager();
         if (!HOME_BOUNCE_SEEN.get(launcher)) {
             stateManager.addStateListener(new StateListener<LauncherState>() {
                 @Override
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 8243ede..7ea04b1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -281,64 +281,45 @@
     }
 
     /**
-     * Creates and returns a view to fade in at .4 animation progress and adds it to the provided
-     * [pendingAnimation]. Assumes that animation will be the final split placeholder launch anim.
-     *
-     * [secondPlaceholderEndingBounds] refers to the second placeholder view that gets added on
-     * screen, not the logical second app. For landscape it's the left app and for portrait the top
-     * one.
+     * Creates and returns a fullscreen scrim to fade in behind the split confirm animation, and
+     * adds it to the provided [pendingAnimation].
      */
-    fun addDividerPlaceholderViewToAnim(
+    fun addScrimBehindAnim(
         pendingAnimation: PendingAnimation,
         container: RecentsViewContainer,
-        secondPlaceholderEndingBounds: Rect,
         context: Context
     ): View {
-        val mSplitDividerPlaceholderView = View(context)
+        val scrim = View(context)
         val recentsView = container.getOverviewPanel<RecentsView<*, *>>()
-        val dp: com.android.launcher3.DeviceProfile = container.getDeviceProfile()
+        val dp: DeviceProfile = container.getDeviceProfile()
         // Add it before/under the most recently added first floating taskView
         val firstAddedSplitViewIndex: Int =
             container
                 .getDragLayer()
                 .indexOfChild(recentsView.splitSelectController.firstFloatingTaskView)
-        container.getDragLayer().addView(mSplitDividerPlaceholderView, firstAddedSplitViewIndex)
-        val lp = mSplitDividerPlaceholderView.layoutParams as InsettableFrameLayout.LayoutParams
+        container.getDragLayer().addView(scrim, firstAddedSplitViewIndex)
+        // Make the scrim fullscreen
+        val lp = scrim.layoutParams as InsettableFrameLayout.LayoutParams
         lp.topMargin = 0
+        lp.height = dp.heightPx
+        lp.width = dp.widthPx
 
-        if (dp.isLeftRightSplit) {
-            lp.height = secondPlaceholderEndingBounds.height()
-            lp.width =
-                container
-                    .asContext()
-                    .resources
-                    .getDimensionPixelSize(R.dimen.split_divider_handle_region_height)
-            mSplitDividerPlaceholderView.translationX =
-                secondPlaceholderEndingBounds.right - lp.width / 2f
-            mSplitDividerPlaceholderView.translationY = 0f
-        } else {
-            lp.height =
-                container
-                    .asContext()
-                    .resources
-                    .getDimensionPixelSize(R.dimen.split_divider_handle_region_height)
-            lp.width = secondPlaceholderEndingBounds.width()
-            mSplitDividerPlaceholderView.translationY =
-                secondPlaceholderEndingBounds.top - lp.height / 2f
-            mSplitDividerPlaceholderView.translationX = 0f
-        }
-
-        mSplitDividerPlaceholderView.alpha = 0f
-        mSplitDividerPlaceholderView.setBackgroundColor(
+        scrim.alpha = 0f
+        scrim.setBackgroundColor(
             container.asContext().resources.getColor(R.color.taskbar_background_dark)
         )
-        val timings = AnimUtils.getDeviceSplitToConfirmTimings(dp.isTablet)
+        val timings = AnimUtils.getDeviceSplitToConfirmTimings(dp.isTablet) as SplitToConfirmTimings
         pendingAnimation.setViewAlpha(
-            mSplitDividerPlaceholderView,
+            scrim,
             1f,
-            Interpolators.clampToProgress(timings.stagedRectScaleXInterpolator, 0.4f, 1f)
+            Interpolators.clampToProgress(
+                timings.backingScrimFadeInterpolator,
+                timings.backingScrimFadeInStartOffset,
+                timings.backingScrimFadeInEndOffset
+            )
         )
-        return mSplitDividerPlaceholderView
+
+        return scrim
     }
 
     /** Does not play any animation if user is not currently in split selection state. */
@@ -510,7 +491,7 @@
         apps: Array<RemoteAnimationTarget>?,
         wallpapers: Array<RemoteAnimationTarget>?,
         nonApps: Array<RemoteAnimationTarget>?,
-        stateManager: StateManager<*>,
+        stateManager: StateManager<*, *>,
         depthController: DepthController?,
         info: TransitionInfo?,
         t: Transaction?,
@@ -589,7 +570,7 @@
     @VisibleForTesting
     fun composeRecentsSplitLaunchAnimator(
         launchingTaskView: GroupedTaskView,
-        stateManager: StateManager<*>,
+        stateManager: StateManager<*, *>,
         depthController: DepthController?,
         info: TransitionInfo,
         t: Transaction,
@@ -617,7 +598,7 @@
         apps: Array<RemoteAnimationTarget>,
         wallpapers: Array<RemoteAnimationTarget>,
         nonApps: Array<RemoteAnimationTarget>,
-        stateManager: StateManager<*>,
+        stateManager: StateManager<*, *>,
         depthController: DepthController?,
         finishCallback: Runnable
     ) {
diff --git a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
index d1ec2b6..7557ad1 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToConfirmTimings.java
@@ -17,6 +17,7 @@
 package com.android.quickstep.util;
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.app.animation.Interpolators.LINEAR;
 
 import android.view.animation.Interpolator;
 
@@ -31,6 +32,8 @@
     abstract public int getPlaceholderIconFadeInEnd();
     abstract public int getStagedRectSlideStart();
     abstract public int getStagedRectSlideEnd();
+    abstract public int getBackingScrimFadeInStart();
+    abstract public int getBackingScrimFadeInEnd();
 
     // Common timings
     public int getInstructionsFadeStart() { return 0; }
@@ -39,6 +42,7 @@
     public Interpolator getStagedRectYInterpolator() { return EMPHASIZED; }
     public Interpolator getStagedRectScaleXInterpolator() { return EMPHASIZED; }
     public Interpolator getStagedRectScaleYInterpolator() { return EMPHASIZED; }
+    public Interpolator getBackingScrimFadeInterpolator() { return LINEAR; }
 
     abstract public int getDuration();
 
@@ -48,4 +52,10 @@
     public float getInstructionsFadeEndOffset() {
         return (float) getInstructionsFadeEnd() / getDuration();
     }
+    public float getBackingScrimFadeInStartOffset() {
+        return (float) getBackingScrimFadeInStart() / getDuration();
+    }
+    public float getBackingScrimFadeInEndOffset() {
+        return (float) getBackingScrimFadeInEnd() / getDuration();
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index 2396512..4962367 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -163,9 +163,8 @@
                 new RectF(firstTaskStartingBounds), firstTaskEndingBounds,
                 false /* fadeWithThumbnail */, true /* isStagedTask */);
 
-        View mSplitDividerPlaceholderView = recentsView.getSplitSelectController()
-                .getSplitAnimationController().addDividerPlaceholderViewToAnim(pendingAnimation,
-                        mLauncher, secondTaskEndingBounds, view.getContext());
+        View backingScrim = recentsView.getSplitSelectController().getSplitAnimationController()
+                .addScrimBehindAnim(pendingAnimation, mLauncher, view.getContext());
 
         FloatingTaskView secondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mLauncher,
                 view, bitmap, icon, secondTaskStartingBounds);
@@ -197,7 +196,7 @@
             private void cleanUp() {
                 mLauncher.getDragLayer().removeView(firstFloatingTaskView);
                 mLauncher.getDragLayer().removeView(secondFloatingTaskView);
-                mLauncher.getDragLayer().removeView(mSplitDividerPlaceholderView);
+                mLauncher.getDragLayer().removeView(backingScrim);
                 mController.getSplitAnimationController().removeSplitInstructionsView(mLauncher);
                 mController.resetState();
             }
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index b13e52d..88c3a08 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -50,6 +50,8 @@
 
     private static final float END_PROGRESS = 1.0f;
 
+    private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.01f;
+
     private final int mTaskId;
     private final ActivityInfo mActivityInfo;
     private final SurfaceControl mLeash;
@@ -135,6 +137,7 @@
         mDestinationBoundsTransformed.set(destinationBoundsTransformed);
         mSurfaceTransactionHelper = new PipSurfaceTransactionHelper(cornerRadius, shadowRadius);
 
+        final float aspectRatio = destinationBounds.width() / (float) destinationBounds.height();
         String reasonForCreateOverlay = null; // For debugging purpose.
         if (sourceRectHint.isEmpty()) {
             reasonForCreateOverlay = "Source rect hint is empty";
@@ -149,29 +152,19 @@
         } else if (!appBounds.contains(sourceRectHint)) {
             // This is a situation in which the source hint rect is outside the app bounds, so it is
             // not a valid rectangle to use for cropping app surface
-            sourceRectHint.setEmpty();
             reasonForCreateOverlay = "Source rect hint exceeds display bounds " + sourceRectHint;
+            sourceRectHint.setEmpty();
+        } else if (Math.abs(
+                aspectRatio - (sourceRectHint.width() / (float) sourceRectHint.height()))
+                > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) {
+            // The source rect hint does not aspect ratio
+            reasonForCreateOverlay = "Source rect hint does not match aspect ratio "
+                    + sourceRectHint + " aspect ratio " + aspectRatio;
+            sourceRectHint.setEmpty();
         }
 
         if (sourceRectHint.isEmpty()) {
-            // Crop a Rect matches the aspect ratio and pivots at the center point.
-            // To make the animation path simplified.
-            final float aspectRatio = destinationBounds.width()
-                    / (float) destinationBounds.height();
-            if ((appBounds.width() / (float) appBounds.height()) > aspectRatio) {
-                // use the full height.
-                mSourceRectHint.set(0, 0,
-                        (int) (appBounds.height() * aspectRatio), appBounds.height());
-                mSourceRectHint.offset(
-                        (appBounds.width() - mSourceRectHint.width()) / 2, 0);
-            } else {
-                // use the full width.
-                mSourceRectHint.set(0, 0,
-                        appBounds.width(), (int) (appBounds.width() / aspectRatio));
-                mSourceRectHint.offset(
-                        0, (appBounds.height() - mSourceRectHint.height()) / 2);
-            }
-
+            mSourceRectHint.set(getEnterPipWithOverlaySrcRectHint(appBounds, aspectRatio));
             // Create a new overlay layer. We do not call detach on this instance, it's propagated
             // to other classes like PipTaskOrganizer / RecentsAnimationController to complete
             // the cleanup.
@@ -217,6 +210,26 @@
         addOnUpdateListener(this::onAnimationUpdate);
     }
 
+    /**
+     * Crop a Rect matches the aspect ratio and pivots at the center point.
+     */
+    private Rect getEnterPipWithOverlaySrcRectHint(Rect appBounds, float aspectRatio) {
+        final float appBoundsAspectRatio = appBounds.width() / (float) appBounds.height();
+        final int width, height;
+        int left = appBounds.left;
+        int top = appBounds.top;
+        if (appBoundsAspectRatio < aspectRatio) {
+            width = appBounds.width();
+            height = (int) (width / aspectRatio);
+            top = appBounds.top + (appBounds.height() - height) / 2;
+        } else {
+            height = appBounds.height();
+            width = (int) (height * aspectRatio);
+            left = appBounds.left + (appBounds.width() - width) / 2;
+        }
+        return new Rect(left, top, left + width, top + height);
+    }
+
     private void onAnimationUpdate(RectF currentRect, float progress) {
         if (mHasAnimationEnded) return;
         final SurfaceControl.Transaction tx =
@@ -267,6 +280,10 @@
         return mAppBounds;
     }
 
+    public Rect getSourceRectHint() {
+        return mSourceRectHint;
+    }
+
     @Nullable
     public SurfaceControl getContentOverlay() {
         return mPipContentOverlay == null ? null : mPipContentOverlay.getLeash();
@@ -425,13 +442,21 @@
             return this;
         }
 
+        public Builder setDisplayCutoutInsets(@NonNull Rect displayCutoutInsets) {
+            mDisplayCutoutInsets = new Rect(displayCutoutInsets);
+            return this;
+        }
+
         public SwipePipToHomeAnimator build() {
             if (mDestinationBoundsTransformed.isEmpty()) {
                 mDestinationBoundsTransformed.set(mDestinationBounds);
             }
             // adjust the mSourceRectHint / mAppBounds by display cutout if applicable.
             if (mSourceRectHint != null && mDisplayCutoutInsets != null) {
-                if (mFromRotation == Surface.ROTATION_90) {
+                if (mFromRotation == Surface.ROTATION_0 && mDisplayCutoutInsets.top >= 0) {
+                    // TODO: this is to special case the issues on Pixel Foldable device(s).
+                    mSourceRectHint.offset(mDisplayCutoutInsets.left, mDisplayCutoutInsets.top);
+                } else if (mFromRotation == Surface.ROTATION_90) {
                     mSourceRectHint.offset(mDisplayCutoutInsets.left, mDisplayCutoutInsets.top);
                 } else if (mFromRotation == Surface.ROTATION_270) {
                     mAppBounds.inset(mDisplayCutoutInsets);
@@ -445,15 +470,6 @@
         }
     }
 
-    private static class RotatedPosition {
-        private final float degree;
-        private final float positionX;
-        private final float positionY;
-
-        private RotatedPosition(float degree, float positionX, float positionY) {
-            this.degree = degree;
-            this.positionX = positionX;
-            this.positionY = positionY;
-        }
+    private record RotatedPosition(float degree, float positionX, float positionY) {
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/SystemUiFlagUtils.kt b/quickstep/src/com/android/quickstep/util/SystemUiFlagUtils.kt
new file mode 100644
index 0000000..5f4388c
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SystemUiFlagUtils.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util
+
+import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
+
+/** Util class for holding and checking [SystemUiStateFlags] masks. */
+object SystemUiFlagUtils {
+    const val KEYGUARD_SYSUI_FLAGS =
+        (QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING or
+            QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING or
+            QuickStepContract.SYSUI_STATE_DEVICE_DOZING or
+            QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED or
+            QuickStepContract.SYSUI_STATE_HOME_DISABLED or
+            QuickStepContract.SYSUI_STATE_BACK_DISABLED or
+            QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED or
+            QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK)
+
+    // If any of these SysUi flags (via QuickstepContract) is set, the device to be considered
+    // locked.
+    private const val MASK_ANY_SYSUI_LOCKED =
+        (QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING or
+            QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING or
+            QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED or
+            QuickStepContract.SYSUI_STATE_DEVICE_DREAMING)
+
+    /**
+     * Returns true iff the given [SystemUiStateFlags] imply that the device is considered locked.
+     */
+    @JvmStatic
+    fun isLocked(@SystemUiStateFlags flags: Long): Boolean {
+        return hasAnyFlag(flags, MASK_ANY_SYSUI_LOCKED) &&
+            !hasAnyFlag(flags, QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY)
+    }
+
+    private fun hasAnyFlag(@SystemUiStateFlags flags: Long, flagMask: Long): Boolean {
+        return (flags and flagMask) != 0L
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java b/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
index 3756b4a..d74473c 100644
--- a/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
+++ b/quickstep/src/com/android/quickstep/util/TabletSplitToConfirmTimings.java
@@ -27,6 +27,8 @@
     public int getPlaceholderIconFadeInEnd() { return 250; }
     public int getStagedRectSlideStart() { return 0; }
     public int getStagedRectSlideEnd() { return 500; }
+    public int getBackingScrimFadeInStart() { return 0; }
+    public int getBackingScrimFadeInEnd() { return 400; }
 
     public int getDuration() { return TABLET_CONFIRM_DURATION; }
 }
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 936f6a1..4c78e21 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -193,23 +193,23 @@
             }
             val taskContainer =
                 TaskContainer(
-                        task,
-                        // TODO(b/338360089): Support new TTV for DesktopTaskView
-                        thumbnailView = null,
-                        thumbnailViewDeprecated,
-                        iconView,
-                        TransformingTouchDelegate(iconView.asView()),
-                        SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
-                        digitalWellBeingToast = null,
-                        showWindowsView = null,
-                        taskOverlayFactory
-                    )
-                    .apply { thumbnailViewDeprecated.bind(task, overlay) }
+                    task,
+                    // TODO(b/338360089): Support new TTV for DesktopTaskView
+                    thumbnailView = null,
+                    thumbnailViewDeprecated,
+                    iconView,
+                    TransformingTouchDelegate(iconView.asView()),
+                    SplitConfigurationOptions.STAGE_POSITION_UNDEFINED,
+                    digitalWellBeingToast = null,
+                    showWindowsView = null,
+                    taskOverlayFactory
+                )
             if (index >= taskContainers.size) {
                 taskContainers.add(taskContainer)
             } else {
                 taskContainers[index] = taskContainer
             }
+            taskContainer.bind()
         }
         repeat(taskContainers.size - tasks.size) {
             with(taskContainers.removeLast()) {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index d6a3376..6296b0e 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -145,6 +145,8 @@
                     taskOverlayFactory
                 )
             )
+        taskContainers.forEach { it.bind() }
+
         this.splitBoundsConfig =
             splitBoundsConfig?.also {
                 taskContainers[0]
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 5284b44..e48a7c6 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -37,6 +37,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.desktop.DesktopRecentsTransitionController;
@@ -104,7 +105,7 @@
     }
 
     @Override
-    public StateManager<LauncherState> getStateManager() {
+    public StateManager<LauncherState, Launcher> getStateManager() {
         return mContainer.getStateManager();
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 604d072..d9468c7 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -278,7 +278,7 @@
         boolean showSingleTaskActions = !mIsGroupedTask;
         boolean showGroupActions = mIsGroupedTask && mDp.isTablet && mCanSaveAppPair;
         Log.d(TAG, "updateActionButtonsVisibility() called: showSingleTaskActions = ["
-                + showSingleTaskActions + ", showGroupActions = [" + showGroupActions + "]");
+                + showSingleTaskActions + "], showGroupActions = [" + showGroupActions + "]");
         getActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showSingleTaskActions ? 1 : 0);
         getGroupActionsAlphas().get(INDEX_GROUPED_ALPHA).setValue(showGroupActions ? 1 : 0);
     }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 9096b75..d806e3d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -31,9 +31,11 @@
 import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.app.animation.Interpolators.OVERSHOOT_0_75;
 import static com.android.app.animation.Interpolators.clampToProgress;
+import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_TASK_MENU;
 import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
+import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
 import static com.android.launcher3.Flags.enableGridOnlyOverview;
 import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
 import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
@@ -130,6 +132,7 @@
 import androidx.core.graphics.ColorUtils;
 
 import com.android.internal.jank.Cuj;
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseActivity.MultiWindowModeChangedListener;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Flags;
@@ -152,6 +155,7 @@
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StateManager;
+import com.android.launcher3.statemanager.StatefulContainer;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.OverScroll;
@@ -312,7 +316,6 @@
     /**
      * Can be used to tint the color of the RecentsView to simulate a scrim that can views
      * excluded from. Really should be a proper scrim.
-     * TODO(b/187528071): Remove this and replace with a real scrim.
      */
     private static final FloatProperty<RecentsView> COLOR_TINT =
             new FloatProperty<RecentsView>("colorTint") {
@@ -553,7 +556,6 @@
     @Nullable
     protected GestureState.GestureEndTarget mCurrentGestureEndTarget;
 
-    // TODO(b/187528071): Remove these and replace with a real scrim.
     private float mColorTint;
     private final int mTintingColor;
     @Nullable
@@ -738,7 +740,11 @@
     private int mSplitHiddenTaskViewIndex = -1;
     @Nullable
     private FloatingTaskView mSecondFloatingTaskView;
-    private View mSplitDividerPlaceholderView;
+    /**
+     * A fullscreen scrim that goes behind the splitscreen animation to hide color conflicts and
+     * possible flickers. Removed after tasks + divider finish animating in.
+     */
+    private View mSplitScrim;
 
     /**
      * The task to be removed and immediately re-added. Should not be added to task pool.
@@ -2487,7 +2493,9 @@
     /** Returns whether user can start home based on state in {@link OverviewCommandHelper}. */
     protected abstract boolean canStartHomeSafely();
 
-    public abstract StateManager<STATE_TYPE> getStateManager();
+    /** Returns the state manager used in RecentsView **/
+    public abstract StateManager<STATE_TYPE,
+            ? extends StatefulContainer<STATE_TYPE>> getStateManager();
 
     public void reset() {
         setCurrentTask(-1);
@@ -2681,6 +2689,7 @@
     }
 
     private void animateRotation(int newRotation) {
+        AbstractFloatingView.closeAllOpenViewsExcept(mContainer, false, TYPE_REBIND_SAFE);
         AnimatorSet pa = setRecentsChangedOrientation(true);
         pa.addListener(AnimatorListeners.forSuccessCallback(() -> {
             setLayoutRotation(newRotation, mOrientationState.getDisplayRotation());
@@ -4513,6 +4522,9 @@
      * than the running task, when updating page offsets.
      */
     public void setOffsetMidpointIndexOverride(int offsetMidpointIndexOverride) {
+        if (!enableAdditionalHomeAnimations()) {
+            return;
+        }
         mOffsetMidpointIndexOverride = offsetMidpointIndexOverride;
         updatePageOffsets();
     }
@@ -4926,9 +4938,8 @@
                 mSplitSelectStateController.getActiveSplitStagePosition(), firstTaskEndingBounds,
                 secondTaskEndingBounds);
 
-        mSplitDividerPlaceholderView = mSplitSelectStateController
-                .getSplitAnimationController().addDividerPlaceholderViewToAnim(pendingAnimation,
-                        mContainer, secondTaskEndingBounds, getContext());
+        mSplitScrim = mSplitSelectStateController.getSplitAnimationController()
+                .addScrimBehindAnim(pendingAnimation, mContainer, getContext());
         FloatingTaskView firstFloatingTaskView =
                 mSplitSelectStateController.getFirstFloatingTaskView();
         firstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
@@ -4983,7 +4994,7 @@
             safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView());
             safeRemoveDragLayerView(mSecondFloatingTaskView);
             safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView());
-            safeRemoveDragLayerView(mSplitDividerPlaceholderView);
+            safeRemoveDragLayerView(mSplitScrim);
             mSecondFloatingTaskView = null;
             mSplitSelectSource = null;
             mSplitSelectStateController.getSplitAnimationController()
@@ -6019,6 +6030,7 @@
      * tasks to be dimmed while other elements in the recents view are left alone.
      */
     public void showForegroundScrim(boolean show) {
+        // TODO(b/335606129) Add scrim response into new TTV - this is called from overlay
         if (!show && mColorTint == 0) {
             if (mTintingAnimator != null) {
                 mTintingAnimator.cancel();
@@ -6034,7 +6046,6 @@
     }
 
     /** Tint the RecentsView and TaskViews in to simulate a scrim. */
-    // TODO(b/187528071): Replace this tinting with a scrim on top of RecentsView
     private void setColorTint(float tintAmount) {
         mColorTint = tintAmount;
 
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index a56d51e..3d994e8 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -18,13 +18,16 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON;
+import static com.android.settingslib.widget.theme.R.dimen.settingslib_preferred_minimum_touch_target;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.content.Context;
+import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
+import android.view.TouchDelegate;
 import android.view.ViewGroup;
 import android.widget.LinearLayout;
 import android.widget.TextView;
@@ -41,9 +44,7 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StateManager;
-import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.states.StateAnimationConfig;
-
 import com.android.quickstep.util.SplitSelectStateController;
 
 /**
@@ -133,6 +134,28 @@
             cancelTextView.setVisibility(VISIBLE);
             cancelTextView.setOnClickListener((v) -> exitSplitSelection());
             instructionTextView.setText(R.string.toast_contextual_split_select_app);
+
+            // After layout, expand touch target of cancel button to meet minimum a11y measurements.
+            post(() -> {
+                int minTouchSize = getResources()
+                        .getDimensionPixelSize(settingslib_preferred_minimum_touch_target);
+                Rect r = new Rect();
+                cancelTextView.getHitRect(r);
+
+                if (r.width() < minTouchSize) {
+                    // add 1 to ensure ceiling on int division
+                    int expandAmount = (minTouchSize + 1 - r.width()) / 2;
+                    r.left -= expandAmount;
+                    r.right += expandAmount;
+                }
+                if (r.height() < minTouchSize) {
+                    int expandAmount = (minTouchSize + 1 - r.height()) / 2;
+                    r.top -= expandAmount;
+                    r.bottom += expandAmount;
+                }
+
+                setTouchDelegate(new TouchDelegate(r, cancelTextView));
+            });
         }
 
         // Set accessibility title, will be announced by a11y tools.
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index eda58c5..4f446b2 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -18,6 +18,7 @@
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.launcher3.Flags.enableOverviewIconMenu;
+import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.quickstep.views.TaskThumbnailViewDeprecated.DIM_ALPHA;
@@ -367,6 +368,14 @@
                         mTaskContainer.getThumbnailViewDeprecated(), DIM_ALPHA,
                         closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA),
                 ObjectAnimator.ofFloat(this, ALPHA, closing ? 0 : 1));
+        if (enableRefactorTaskThumbnail()) {
+            mRevealAnimator.addUpdateListener(animation -> {
+                float animatedFraction = animation.getAnimatedFraction();
+                float openProgress = closing ? (1 - animatedFraction) : animatedFraction;
+                mTaskContainer.getTaskContainerData()
+                        .getTaskMenuOpenProgress().setValue(openProgress);
+            });
+        }
         mOpenCloseAnimator.addListener(new AnimationSuccessListener() {
             @Override
             public void onAnimationStart(Animator animation) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
index 447002f..4283d0e 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
@@ -61,6 +61,8 @@
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper;
 
+import java.util.Objects;
+
 /**
  * A task in the Recents view.
  *
@@ -222,14 +224,16 @@
     public void setThumbnail(@Nullable Task task, @Nullable ThumbnailData thumbnailData,
             boolean refreshNow) {
         mTask = task;
-        boolean thumbnailWasNull = mThumbnailData == null;
+        ThumbnailData oldThumbnailData = mThumbnailData;
         mThumbnailData = (thumbnailData != null && thumbnailData.getThumbnail() != null)
                 ? thumbnailData : null;
         if (mTask != null) {
             updateSplashView(mTask.icon);
         }
         if (refreshNow) {
-            refresh(thumbnailWasNull && mThumbnailData != null);
+            Long oldSnapshotId = oldThumbnailData != null ? oldThumbnailData.getSnapshotId() : null;
+            Long snapshotId = mThumbnailData != null ? mThumbnailData.getSnapshotId() : null;
+            refresh(snapshotId != null && !Objects.equals(oldSnapshotId, snapshotId));
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 5e79743..7a3b00f 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -63,6 +63,7 @@
 import com.android.launcher3.testing.TestLogging
 import com.android.launcher3.testing.shared.TestProtocol
 import com.android.launcher3.util.CancellableTask
+import com.android.launcher3.util.DisplayController
 import com.android.launcher3.util.Executors
 import com.android.launcher3.util.MultiPropertyFactory
 import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
@@ -76,6 +77,7 @@
 import com.android.launcher3.util.TransformingTouchDelegate
 import com.android.launcher3.util.ViewPool
 import com.android.launcher3.util.rects.set
+import com.android.launcher3.views.ActivityContext
 import com.android.quickstep.RecentsModel
 import com.android.quickstep.RemoteAnimationTargets
 import com.android.quickstep.TaskAnimationManager
@@ -86,6 +88,7 @@
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler
 import com.android.quickstep.task.thumbnail.TaskThumbnail
 import com.android.quickstep.task.thumbnail.TaskThumbnailView
+import com.android.quickstep.task.viewmodel.TaskContainerData
 import com.android.quickstep.task.viewmodel.TaskViewData
 import com.android.quickstep.util.ActiveGestureErrorDetector
 import com.android.quickstep.util.ActiveGestureLog
@@ -665,6 +668,7 @@
                     taskOverlayFactory
                 )
             )
+        taskContainers.forEach { it.bind() }
         setOrientationState(orientedState)
     }
 
@@ -691,24 +695,16 @@
         }
         val iconView = getOrInflateIconView(iconViewId)
         return TaskContainer(
-                task,
-                thumbnailView,
-                thumbnailViewDeprecated,
-                iconView,
-                TransformingTouchDelegate(iconView.asView()),
-                stagePosition,
-                DigitalWellBeingToast(container, this),
-                findViewById(showWindowViewId)!!,
-                taskOverlayFactory
-            )
-            .apply {
-                if (enableRefactorTaskThumbnail()) {
-                    thumbnailViewDeprecated.setTaskOverlay(overlay)
-                    bindThumbnailView()
-                } else {
-                    thumbnailViewDeprecated.bind(task, overlay)
-                }
-            }
+            task,
+            thumbnailView,
+            thumbnailViewDeprecated,
+            iconView,
+            TransformingTouchDelegate(iconView.asView()),
+            stagePosition,
+            DigitalWellBeingToast(container, this),
+            findViewById(showWindowViewId)!!,
+            taskOverlayFactory
+        )
     }
 
     protected fun getOrInflateIconView(@IdRes iconViewId: Int): TaskViewIcon {
@@ -1377,7 +1373,6 @@
     open fun setColorTint(amount: Float, tintColor: Int) {
         taskContainers.forEach {
             if (!enableRefactorTaskThumbnail()) {
-                // TODO(b/334832108) Add scrim to new TTV
                 it.thumbnailViewDeprecated.dimAlpha = amount
             }
             it.iconView.setIconColorTint(tintColor, amount)
@@ -1520,6 +1515,9 @@
         resetViewTransforms()
     }
 
+    fun getTaskContainerForTaskThumbnailView(taskThumbnailView: TaskThumbnailView): TaskContainer? =
+        taskContainers.firstOrNull { it.thumbnailView == taskThumbnailView }
+
     open fun resetViewTransforms() {
         // fullscreenTranslation and accumulatedTranslation should not be reset, as
         // resetViewTransforms is called during QuickSwitch scrolling.
@@ -1574,7 +1572,20 @@
 
         @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
         open fun computeWindowCornerRadius(context: Context): Float {
-            return QuickStepContract.getWindowCornerRadius(context)
+            val activityContext: ActivityContext? = ActivityContext.lookupContextNoThrow(context)
+
+            // The corner radius is fixed to match when Taskbar is persistent mode
+            return if (
+                activityContext != null &&
+                    activityContext.deviceProfile?.isTaskbarPresent == true &&
+                    DisplayController.isTransientTaskbar(context)
+            ) {
+                context.resources
+                    .getDimensionPixelSize(R.dimen.persistent_taskbar_corner_radius)
+                    .toFloat()
+            } else {
+                QuickStepContract.getWindowCornerRadius(context)
+            }
         }
 
         /** Sets the progress in range [0, 1] */
@@ -1608,6 +1619,7 @@
         taskOverlayFactory: TaskOverlayFactory
     ) {
         val overlay: TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
+        val taskContainerData = TaskContainerData()
 
         val snapshotView: View
             get() = thumbnailView ?: thumbnailViewDeprecated
@@ -1641,6 +1653,15 @@
             thumbnailView?.let { taskView.removeView(it) }
         }
 
+        fun bind() {
+            if (enableRefactorTaskThumbnail() && thumbnailView != null) {
+                thumbnailViewDeprecated.setTaskOverlay(overlay)
+                bindThumbnailView()
+            } else {
+                thumbnailViewDeprecated.bind(task, overlay)
+            }
+        }
+
         // TODO(b/335649589): TaskView's VM will already have access to TaskThumbnailView's VM
         //  so there will be no need to access TaskThumbnailView's VM through the TaskThumbnailView
         fun bindThumbnailView() {
diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
similarity index 94%
rename from quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
index 5c7b4ab..039dce4 100644
--- a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
@@ -34,7 +34,7 @@
 import com.android.launcher3.model.WidgetPredictionsRequester.filterPredictions
 import com.android.launcher3.model.WidgetPredictionsRequester.notOnUiSurfaceFilter
 import com.android.launcher3.util.ActivityContextWrapper
-import com.android.launcher3.util.PackageUserKey
+import com.android.launcher3.util.ComponentKey
 import com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
 import com.google.common.truth.Truth.assertThat
@@ -62,7 +62,7 @@
     private lateinit var widgetItem1b: WidgetItem
     private lateinit var widgetItem2: WidgetItem
 
-    private lateinit var allWidgets: Map<PackageUserKey, List<WidgetItem>>
+    private lateinit var allWidgets: Map<ComponentKey, WidgetItem>
 
     @Mock private lateinit var iconCache: IconCache
 
@@ -93,9 +93,9 @@
 
         allWidgets =
             mapOf(
-                PackageUserKey(APP_1_PACKAGE_NAME, mUserHandle) to
-                    listOf(widgetItem1a, widgetItem1b),
-                PackageUserKey(APP_2_PACKAGE_NAME, mUserHandle) to listOf(widgetItem2),
+                ComponentKey(widgetItem1a.componentName, widgetItem1a.user) to widgetItem1a,
+                ComponentKey(widgetItem1b.componentName, widgetItem1b.user) to widgetItem1b,
+                ComponentKey(widgetItem2.componentName, widgetItem2.user) to widgetItem2,
             )
     }
 
@@ -156,7 +156,7 @@
     }
 
     @Test
-    fun filterPredictions_appPredictions_returnsWidgetFromPackage() {
+    fun filterPredictions_appPredictions_returnsEmptyList() {
         val widgetsAlreadyOnSurface = arrayListOf(widget1bInfo)
         val filter: Predicate<WidgetItem> = notOnUiSurfaceFilter(widgetsAlreadyOnSurface)
 
@@ -176,8 +176,7 @@
                 ),
             )
 
-        assertThat(filterPredictions(predictions, allWidgets, filter))
-            .containsExactly(widgetItem1a, widgetItem2)
+        assertThat(filterPredictions(predictions, allWidgets, filter)).isEmpty()
     }
 
     private fun createWidgetItem(
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRule.kt
new file mode 100644
index 0000000..3b53cdc
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRule.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2024 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.taskbar
+
+import com.android.launcher3.taskbar.TaskbarModeRule.Mode
+import com.android.launcher3.taskbar.TaskbarModeRule.TaskbarMode
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.MainThreadInitializedObject
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.android.launcher3.util.NavigationMode
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.spy
+
+/**
+ * Allows tests to specify which Taskbar [Mode] to run under.
+ *
+ * [context] should match the test's target context, so that [MainThreadInitializedObject] instances
+ * are properly sandboxed.
+ *
+ * Annotate tests with [TaskbarMode] to set a mode. If the annotation is omitted for any tests, this
+ * rule is a no-op.
+ *
+ * Make sure this rule precedes any rules that depend on [DisplayController], or else the instance
+ * might be inconsistent across the test lifecycle.
+ */
+class TaskbarModeRule(private val context: SandboxContext) : TestRule {
+    /** The selected Taskbar mode. */
+    enum class Mode {
+        TRANSIENT,
+        PINNED,
+        THREE_BUTTONS,
+    }
+
+    /** Overrides Taskbar [mode] for a test. */
+    @Retention(AnnotationRetention.RUNTIME)
+    @Target(AnnotationTarget.FUNCTION)
+    annotation class TaskbarMode(val mode: Mode)
+
+    override fun apply(base: Statement, description: Description): Statement {
+        val taskbarMode = description.getAnnotation(TaskbarMode::class.java) ?: return base
+
+        return object : Statement() {
+            override fun evaluate() {
+                val mode = taskbarMode.mode
+
+                context.putObject(
+                    DisplayController.INSTANCE,
+                    object : DisplayController(context) {
+                        override fun getInfo(): Info {
+                            return spy(super.getInfo()) {
+                                on { isTransientTaskbar } doReturn (mode == Mode.TRANSIENT)
+                                on { isPinnedTaskbar } doReturn (mode == Mode.PINNED)
+                                on { navigationMode } doReturn
+                                    when (mode) {
+                                        Mode.TRANSIENT,
+                                        Mode.PINNED -> NavigationMode.NO_BUTTON
+                                        Mode.THREE_BUTTONS -> NavigationMode.THREE_BUTTONS
+                                    }
+                            }
+                        }
+                    },
+                )
+
+                base.evaluate()
+            }
+        }
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRuleTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRuleTest.kt
new file mode 100644
index 0000000..7dfbb9a
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarModeRuleTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2024 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.taskbar
+
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.taskbar.TaskbarModeRule.Mode.PINNED
+import com.android.launcher3.taskbar.TaskbarModeRule.Mode.THREE_BUTTONS
+import com.android.launcher3.taskbar.TaskbarModeRule.Mode.TRANSIENT
+import com.android.launcher3.taskbar.TaskbarModeRule.TaskbarMode
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.android.launcher3.util.NavigationMode
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+class TaskbarModeRuleTest {
+
+    private val context = SandboxContext(getInstrumentation().targetContext)
+
+    @get:Rule val taskbarModeRule = TaskbarModeRule(context)
+
+    @Test
+    @TaskbarMode(TRANSIENT)
+    fun testTaskbarMode_transient_overridesDisplayController() {
+        assertThat(DisplayController.isTransientTaskbar(context)).isTrue()
+        assertThat(DisplayController.isPinnedTaskbar(context)).isFalse()
+        assertThat(DisplayController.getNavigationMode(context)).isEqualTo(NavigationMode.NO_BUTTON)
+    }
+
+    @Test
+    @TaskbarMode(TRANSIENT)
+    fun testTaskbarMode_transient_overridesDeviceProfile() {
+        val dp = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context)
+        assertThat(dp.isTransientTaskbar).isTrue()
+        assertThat(dp.isGestureMode).isTrue()
+    }
+
+    @Test
+    @TaskbarMode(PINNED)
+    fun testTaskbarMode_pinned_overridesDisplayController() {
+        assertThat(DisplayController.isTransientTaskbar(context)).isFalse()
+        assertThat(DisplayController.isPinnedTaskbar(context)).isTrue()
+        assertThat(DisplayController.getNavigationMode(context)).isEqualTo(NavigationMode.NO_BUTTON)
+    }
+
+    @Test
+    @TaskbarMode(PINNED)
+    fun testTaskbarMode_pinned_overridesDeviceProfile() {
+        val dp = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context)
+        assertThat(dp.isTransientTaskbar).isFalse()
+        assertThat(dp.isGestureMode).isTrue()
+    }
+
+    @Test
+    @TaskbarMode(THREE_BUTTONS)
+    fun testTaskbarMode_threeButtons_overridesDisplayController() {
+        assertThat(DisplayController.isTransientTaskbar(context)).isFalse()
+        assertThat(DisplayController.isPinnedTaskbar(context)).isFalse()
+        assertThat(DisplayController.getNavigationMode(context))
+            .isEqualTo(NavigationMode.THREE_BUTTONS)
+    }
+
+    @Test
+    @TaskbarMode(THREE_BUTTONS)
+    fun testTaskbarMode_threeButtons_overridesDeviceProfile() {
+        val dp = InvariantDeviceProfile.INSTANCE.get(context).getDeviceProfile(context)
+        assertThat(dp.isTransientTaskbar).isFalse()
+        assertThat(dp.isGestureMode).isFalse()
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt
new file mode 100644
index 0000000..bbf738e
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarUnitTestRule.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2024 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.taskbar
+
+import android.app.Instrumentation
+import android.app.PendingIntent
+import android.content.Context
+import android.content.IIntentSender
+import android.content.Intent
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.ServiceTestRule
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
+import com.android.quickstep.AllAppsActionManager
+import com.android.quickstep.TouchInteractionService
+import com.android.quickstep.TouchInteractionService.TISBinder
+import org.junit.Assume.assumeTrue
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Manages the Taskbar lifecycle for unit tests.
+ *
+ * Tests should pass in themselves as [testInstance]. They also need to provide their target
+ * [context] through the constructor.
+ *
+ * See [InjectController] for grabbing controller(s) under test with minimal boilerplate.
+ *
+ * The rule interacts with [TaskbarManager] on the main thread. A good rule of thumb for tests is
+ * that code that is executed on the main thread in production should also happen on that thread
+ * when tested.
+ *
+ * `@UiThreadTest` is a simple way to run an entire test body on the main thread. But if a test
+ * executes code that appends message(s) to the main thread's `MessageQueue`, the annotation will
+ * prevent those messages from being processed until after the test body finishes.
+ *
+ * To test pending messages, instead use something like [Instrumentation.runOnMainSync] to perform
+ * only sections of the test body on the main thread synchronously:
+ * ```
+ * @Test
+ * fun example() {
+ *     instrumentation.runOnMainSync { doWorkThatPostsMessage() }
+ *     // Second lambda will not execute until message is processed.
+ *     instrumentation.runOnMainSync { verifyMessageResults() }
+ * }
+ * ```
+ */
+class TaskbarUnitTestRule(private val testInstance: Any, private val context: Context) : TestRule {
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val serviceTestRule = ServiceTestRule()
+
+    private lateinit var taskbarManager: TaskbarManager
+
+    val activityContext: TaskbarActivityContext
+        get() {
+            return taskbarManager.currentActivityContext
+                ?: throw RuntimeException("Failed to obtain TaskbarActivityContext.")
+        }
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return object : Statement() {
+            override fun evaluate() {
+
+                instrumentation.runOnMainSync {
+                    assumeTrue(
+                        LauncherAppState.getIDP(context).getDeviceProfile(context).isTaskbarPresent
+                    )
+                }
+
+                // Check for existing Taskbar instance from Launcher process.
+                val launcherTaskbarManager: TaskbarManager? =
+                    if (!isRunningInRobolectric) {
+                        try {
+                            val tisBinder =
+                                serviceTestRule.bindService(
+                                    Intent(context, TouchInteractionService::class.java)
+                                ) as? TISBinder
+                            tisBinder?.taskbarManager
+                        } catch (_: Exception) {
+                            null
+                        }
+                    } else {
+                        null
+                    }
+
+                instrumentation.runOnMainSync {
+                    taskbarManager =
+                        TaskbarManager(
+                            context,
+                            AllAppsActionManager(context, UI_HELPER_EXECUTOR) {
+                                PendingIntent(IIntentSender.Default())
+                            },
+                            object : TaskbarNavButtonCallbacks {},
+                        )
+                }
+
+                try {
+                    // Replace Launcher Taskbar window with test instance.
+                    instrumentation.runOnMainSync {
+                        launcherTaskbarManager?.removeTaskbarRootViewFromWindow()
+                        taskbarManager.onUserUnlocked() // Required to complete initialization.
+                    }
+
+                    injectControllers()
+                    base.evaluate()
+                } finally {
+                    // Revert Taskbar window.
+                    instrumentation.runOnMainSync {
+                        taskbarManager.destroy()
+                        launcherTaskbarManager?.addTaskbarRootViewToWindow()
+                    }
+                }
+            }
+        }
+    }
+
+    /** Simulates Taskbar recreation lifecycle. */
+    fun recreateTaskbar() {
+        taskbarManager.recreateTaskbar()
+        injectControllers()
+    }
+
+    private fun injectControllers() {
+        val controllers = activityContext.controllers
+        val controllerFieldsByType = controllers.javaClass.fields.associateBy { it.type }
+        testInstance.javaClass.fields
+            .filter { it.isAnnotationPresent(InjectController::class.java) }
+            .forEach {
+                it.set(
+                    testInstance,
+                    controllerFieldsByType[it.type]?.get(controllers)
+                        ?: throw NoSuchElementException("Failed to find controller for ${it.type}"),
+                )
+            }
+    }
+
+    /**
+     * Annotates test controller fields to inject the corresponding controllers from the current
+     * [TaskbarControllers] instance.
+     *
+     * Controllers are injected during test setup and upon calling [recreateTaskbar].
+     *
+     * Multiple controllers can be injected if needed.
+     */
+    @Retention(AnnotationRetention.RUNTIME)
+    @Target(AnnotationTarget.FIELD)
+    annotation class InjectController
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
new file mode 100644
index 0000000..bfad697
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsControllerTest.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2024 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.taskbar.allapps
+
+import android.animation.AnimatorTestRule
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Process
+import androidx.test.annotation.UiThreadTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.appprediction.PredictionRowView
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.notification.NotificationKeyData
+import com.android.launcher3.taskbar.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.TaskbarUnitTestRule.InjectController
+import com.android.launcher3.taskbar.overlay.TaskbarOverlayController
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.android.launcher3.util.PackageUserKey
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
+class TaskbarAllAppsControllerTest {
+
+    @get:Rule
+    val taskbarUnitTestRule = TaskbarUnitTestRule(this, getInstrumentation().targetContext)
+    @get:Rule val animatorTestRule = AnimatorTestRule(this)
+
+    @InjectController lateinit var allAppsController: TaskbarAllAppsController
+    @InjectController lateinit var overlayController: TaskbarOverlayController
+
+    @Test
+    @UiThreadTest
+    fun testToggle_once_showsAllApps() {
+        allAppsController.toggle()
+        assertThat(allAppsController.isOpen).isTrue()
+    }
+
+    @Test
+    @UiThreadTest
+    fun testToggle_twice_closesAllApps() {
+        allAppsController.toggle()
+        allAppsController.toggle()
+        assertThat(allAppsController.isOpen).isFalse()
+    }
+
+    @Test
+    @UiThreadTest
+    fun testToggle_taskbarRecreated_allAppsReopened() {
+        allAppsController.toggle()
+        taskbarUnitTestRule.recreateTaskbar()
+        assertThat(allAppsController.isOpen).isTrue()
+    }
+
+    @Test
+    @UiThreadTest
+    fun testSetApps_beforeOpened_cachesInfo() {
+        allAppsController.setApps(TEST_APPS, 0, emptyMap())
+        allAppsController.toggle()
+
+        val overlayContext = overlayController.requestWindow()
+        assertThat(overlayContext.appsView.appsStore.apps).isEqualTo(TEST_APPS)
+    }
+
+    @Test
+    @UiThreadTest
+    fun testSetApps_afterOpened_updatesStore() {
+        allAppsController.toggle()
+        allAppsController.setApps(TEST_APPS, 0, emptyMap())
+
+        val overlayContext = overlayController.requestWindow()
+        assertThat(overlayContext.appsView.appsStore.apps).isEqualTo(TEST_APPS)
+    }
+
+    @Test
+    @UiThreadTest
+    fun testSetPredictedApps_beforeOpened_cachesInfo() {
+        allAppsController.setPredictedApps(TEST_PREDICTED_APPS)
+        allAppsController.toggle()
+
+        val predictedApps =
+            overlayController
+                .requestWindow()
+                .appsView
+                .floatingHeaderView
+                .findFixedRowByType(PredictionRowView::class.java)
+                .predictedApps
+        assertThat(predictedApps).isEqualTo(TEST_PREDICTED_APPS)
+    }
+
+    @Test
+    @UiThreadTest
+    fun testSetPredictedApps_afterOpened_cachesInfo() {
+        allAppsController.toggle()
+        allAppsController.setPredictedApps(TEST_PREDICTED_APPS)
+
+        val predictedApps =
+            overlayController
+                .requestWindow()
+                .appsView
+                .floatingHeaderView
+                .findFixedRowByType(PredictionRowView::class.java)
+                .predictedApps
+        assertThat(predictedApps).isEqualTo(TEST_PREDICTED_APPS)
+    }
+
+    @Test
+    fun testUpdateNotificationDots_appInfo_hasDot() {
+        getInstrumentation().runOnMainSync {
+            allAppsController.setApps(TEST_APPS, 0, emptyMap())
+            allAppsController.toggle()
+            taskbarUnitTestRule.activityContext.popupDataProvider.onNotificationPosted(
+                PackageUserKey.fromItemInfo(TEST_APPS[0]),
+                NotificationKeyData("key"),
+            )
+        }
+
+        // Ensure the recycler view fully inflates before trying to grab an icon.
+        getInstrumentation().runOnMainSync {
+            val btv =
+                overlayController
+                    .requestWindow()
+                    .appsView
+                    .activeRecyclerView
+                    .findViewHolderForAdapterPosition(0)
+                    ?.itemView as? BubbleTextView
+            assertThat(btv?.hasDot()).isTrue()
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    fun testUpdateNotificationDots_predictedApp_hasDot() {
+        allAppsController.setPredictedApps(TEST_PREDICTED_APPS)
+        allAppsController.toggle()
+
+        taskbarUnitTestRule.activityContext.popupDataProvider.onNotificationPosted(
+            PackageUserKey.fromItemInfo(TEST_PREDICTED_APPS[0]),
+            NotificationKeyData("key"),
+        )
+
+        val predictionRowView =
+            overlayController
+                .requestWindow()
+                .appsView
+                .floatingHeaderView
+                .findFixedRowByType(PredictionRowView::class.java)
+        val btv = predictionRowView.getChildAt(0) as BubbleTextView
+        assertThat(btv.hasDot()).isTrue()
+    }
+
+    @Test
+    fun testToggleSearch_searchEditTextFocused() {
+        getInstrumentation().runOnMainSync { allAppsController.toggleSearch() }
+        getInstrumentation().runOnMainSync {
+            // All Apps is now attached to window. Open animation is posted but not started.
+        }
+
+        getInstrumentation().runOnMainSync {
+            // Animation has started. Advance to end of animation.
+            animatorTestRule.advanceTimeBy(overlayController.openDuration.toLong())
+        }
+        val editText = overlayController.requestWindow().appsView.searchUiManager.editText
+        assertThat(editText?.hasFocus()).isTrue()
+    }
+
+    private companion object {
+        private val TEST_APPS =
+            Array(16) {
+                AppInfo(
+                    ComponentName(
+                        getInstrumentation().context,
+                        "com.android.launcher3.tests.Activity$it",
+                    ),
+                    "Test App $it",
+                    Process.myUserHandle(),
+                    Intent(),
+                )
+            }
+
+        private val TEST_PREDICTED_APPS = TEST_APPS.take(4).map { WorkspaceItemInfo(it) }
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt
new file mode 100644
index 0000000..20bd617
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimatorTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 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.taskbar.bubbles.animation
+
+import androidx.core.animation.AnimatorTestRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleAnimatorTest {
+
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+
+    private lateinit var bubbleAnimator: BubbleAnimator
+
+    @Test
+    fun animateNewBubble_isRunning() {
+        bubbleAnimator =
+            BubbleAnimator(
+                iconSize = 40f,
+                expandedBarIconSpacing = 10f,
+                bubbleCount = 5,
+                onLeft = false
+            )
+        val listener = TestBubbleAnimatorListener()
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            bubbleAnimator.animateNewBubble(selectedBubbleIndex = 2, listener)
+        }
+
+        assertThat(bubbleAnimator.isRunning).isTrue()
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            animatorTestRule.advanceTimeBy(250)
+        }
+        assertThat(bubbleAnimator.isRunning).isFalse()
+    }
+
+    @Test
+    fun animateRemovedBubble_isRunning() {
+        bubbleAnimator =
+            BubbleAnimator(
+                iconSize = 40f,
+                expandedBarIconSpacing = 10f,
+                bubbleCount = 5,
+                onLeft = false
+            )
+        val listener = TestBubbleAnimatorListener()
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            bubbleAnimator.animateRemovedBubble(
+                bubbleIndex = 2,
+                selectedBubbleIndex = 3,
+                removingLastBubble = false,
+                listener
+            )
+        }
+
+        assertThat(bubbleAnimator.isRunning).isTrue()
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            animatorTestRule.advanceTimeBy(250)
+        }
+        assertThat(bubbleAnimator.isRunning).isFalse()
+    }
+
+    private class TestBubbleAnimatorListener : BubbleAnimator.Listener {
+
+        override fun onAnimationUpdate(animatedFraction: Float) {}
+
+        override fun onAnimationCancel() {}
+
+        override fun onAnimationEnd() {}
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
index cc579ab..e9c0dd6 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -24,6 +24,7 @@
 import android.view.View
 import android.view.View.VISIBLE
 import android.widget.FrameLayout
+import androidx.core.animation.AnimatorTestRule
 import androidx.core.graphics.drawable.toBitmap
 import androidx.dynamicanimation.animation.DynamicAnimation
 import androidx.test.core.app.ApplicationProvider
@@ -41,6 +42,7 @@
 import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
@@ -53,6 +55,8 @@
 @RunWith(AndroidJUnit4::class)
 class BubbleBarViewAnimatorTest {
 
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+
     private val context = ApplicationProvider.getApplicationContext<Context>()
     private lateinit var animatorScheduler: TestBubbleBarViewAnimatorScheduler
     private lateinit var overflowView: BubbleView
@@ -380,6 +384,46 @@
         verify(bubbleStashController).showBubbleBarImmediate()
     }
 
+    @Test
+    fun animateBubbleBarForCollapsed() {
+        setUpBubbleBar()
+        setUpBubbleStashController()
+        whenever(bubbleStashController.bubbleBarTranslationY)
+            .thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+
+        val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
+
+        val animator =
+            BubbleBarViewAnimator(bubbleBarView, bubbleStashController, animatorScheduler)
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            animator.animateBubbleBarForCollapsed(bubble)
+        }
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+        // verify we started animating
+        assertThat(bubbleBarView.isAnimatingNewBubble).isTrue()
+
+        // advance the animation handler by the duration of the initial lift
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            animatorTestRule.advanceTimeBy(250)
+        }
+
+        // the lift animation is complete; the spring back animation should start now
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+        barAnimator.assertIsRunning()
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+        assertThat(animatorScheduler.delayedBlock).isNotNull()
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
+
+        assertThat(bubbleBarView.isAnimatingNewBubble).isFalse()
+        // the bubble bar translation y should be back to its initial value
+        assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+
+        verify(bubbleStashController).showBubbleBarImmediate()
+    }
+
     private fun setUpBubbleBar() {
         bubbleBarView = BubbleBarView(context)
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
new file mode 100644
index 0000000..72bdc16
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayControllerTest.kt
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2024 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.taskbar.overlay
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.view.MotionEvent
+import androidx.test.annotation.UiThreadTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.AbstractFloatingView.TYPE_OPTIONS_POPUP
+import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_ALL_APPS
+import com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY
+import com.android.launcher3.AbstractFloatingView.hasOpenView
+import com.android.launcher3.taskbar.TaskbarActivityContext
+import com.android.launcher3.taskbar.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.TaskbarUnitTestRule.InjectController
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.android.launcher3.views.BaseDragLayer
+import com.android.systemui.shared.system.TaskStackChangeListeners
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+@EmulatedDevices(["pixelFoldable2023"])
+class TaskbarOverlayControllerTest {
+
+    @get:Rule
+    val taskbarUnitTestRule = TaskbarUnitTestRule(this, getInstrumentation().targetContext)
+    @InjectController lateinit var overlayController: TaskbarOverlayController
+
+    private val taskbarContext: TaskbarActivityContext
+        get() = taskbarUnitTestRule.activityContext
+
+    @Test
+    @UiThreadTest
+    fun testRequestWindow_twice_reusesWindow() {
+        val context1 = overlayController.requestWindow()
+        val context2 = overlayController.requestWindow()
+        assertThat(context1).isSameInstanceAs(context2)
+    }
+
+    @Test
+    @UiThreadTest
+    fun testRequestWindow_afterHidingExistingWindow_createsNewWindow() {
+        val context1 = overlayController.requestWindow()
+        overlayController.hideWindow()
+
+        val context2 = overlayController.requestWindow()
+        assertThat(context1).isNotSameInstanceAs(context2)
+    }
+
+    @Test
+    @UiThreadTest
+    fun testRequestWindow_afterHidingOverlay_createsNewWindow() {
+        val context1 = overlayController.requestWindow()
+        TestOverlayView.show(context1)
+        overlayController.hideWindow()
+
+        val context2 = overlayController.requestWindow()
+        assertThat(context1).isNotSameInstanceAs(context2)
+    }
+
+    @Test
+    @UiThreadTest
+    fun testRequestWindow_addsProxyView() {
+        TestOverlayView.show(overlayController.requestWindow())
+        assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isTrue()
+    }
+
+    @Test
+    @UiThreadTest
+    fun testRequestWindow_closeProxyView_closesOverlay() {
+        val overlay = TestOverlayView.show(overlayController.requestWindow())
+        AbstractFloatingView.closeOpenContainer(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)
+        assertThat(overlay.isOpen).isFalse()
+    }
+
+    @Test
+    fun testRequestWindow_attachesDragLayer() {
+        lateinit var dragLayer: BaseDragLayer<*>
+        getInstrumentation().runOnMainSync {
+            dragLayer = overlayController.requestWindow().dragLayer
+        }
+
+        // Allow drag layer to attach before checking.
+        getInstrumentation().runOnMainSync { assertThat(dragLayer.isAttachedToWindow).isTrue() }
+    }
+
+    @Test
+    @UiThreadTest
+    fun testHideWindow_closesOverlay() {
+        val overlay = TestOverlayView.show(overlayController.requestWindow())
+        overlayController.hideWindow()
+        assertThat(overlay.isOpen).isFalse()
+    }
+
+    @Test
+    fun testHideWindow_detachesDragLayer() {
+        lateinit var dragLayer: BaseDragLayer<*>
+        getInstrumentation().runOnMainSync {
+            dragLayer = overlayController.requestWindow().dragLayer
+        }
+
+        // Wait for drag layer to be attached to window before hiding.
+        getInstrumentation().runOnMainSync {
+            overlayController.hideWindow()
+            assertThat(dragLayer.isAttachedToWindow).isFalse()
+        }
+    }
+
+    @Test
+    @UiThreadTest
+    fun testTwoOverlays_closeOne_windowStaysOpen() {
+        val context = overlayController.requestWindow()
+        val overlay1 = TestOverlayView.show(context)
+        val overlay2 = TestOverlayView.show(context)
+
+        overlay1.close(false)
+        assertThat(overlay2.isOpen).isTrue()
+        assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isTrue()
+    }
+
+    @Test
+    @UiThreadTest
+    fun testTwoOverlays_closeAll_closesWindow() {
+        val context = overlayController.requestWindow()
+        val overlay1 = TestOverlayView.show(context)
+        val overlay2 = TestOverlayView.show(context)
+
+        overlay1.close(false)
+        overlay2.close(false)
+        assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse()
+    }
+
+    @Test
+    @UiThreadTest
+    fun testRecreateTaskbar_closesWindow() {
+        TestOverlayView.show(overlayController.requestWindow())
+        taskbarUnitTestRule.recreateTaskbar()
+        assertThat(hasOpenView(taskbarContext, TYPE_TASKBAR_OVERLAY_PROXY)).isFalse()
+    }
+
+    @Test
+    fun testTaskMovedToFront_closesOverlay() {
+        lateinit var overlay: TestOverlayView
+        getInstrumentation().runOnMainSync {
+            overlay = TestOverlayView.show(overlayController.requestWindow())
+        }
+
+        TaskStackChangeListeners.getInstance().listenerImpl.onTaskMovedToFront(RunningTaskInfo())
+        // Make sure TaskStackChangeListeners' Handler posts the callback before checking state.
+        getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() }
+    }
+
+    @Test
+    fun testTaskStackChanged_allAppsClosed_overlayStaysOpen() {
+        lateinit var overlay: TestOverlayView
+        getInstrumentation().runOnMainSync {
+            overlay = TestOverlayView.show(overlayController.requestWindow())
+            taskbarContext.controllers.sharedState?.allAppsVisible = false
+        }
+
+        TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged()
+        getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isTrue() }
+    }
+
+    @Test
+    fun testTaskStackChanged_allAppsOpen_closesOverlay() {
+        lateinit var overlay: TestOverlayView
+        getInstrumentation().runOnMainSync {
+            overlay = TestOverlayView.show(overlayController.requestWindow())
+            taskbarContext.controllers.sharedState?.allAppsVisible = true
+        }
+
+        TaskStackChangeListeners.getInstance().listenerImpl.onTaskStackChanged()
+        getInstrumentation().runOnMainSync { assertThat(overlay.isOpen).isFalse() }
+    }
+
+    @Test
+    @UiThreadTest
+    fun testUpdateLauncherDeviceProfile_overlayNotRebindSafe_closesOverlay() {
+        val overlayContext = overlayController.requestWindow()
+        val overlay = TestOverlayView.show(overlayContext).apply { type = TYPE_OPTIONS_POPUP }
+
+        overlayController.updateLauncherDeviceProfile(
+            overlayController.launcherDeviceProfile
+                .toBuilder(overlayContext)
+                .setGestureMode(false)
+                .build()
+        )
+
+        assertThat(overlay.isOpen).isFalse()
+    }
+
+    @Test
+    @UiThreadTest
+    fun testUpdateLauncherDeviceProfile_overlayRebindSafe_overlayStaysOpen() {
+        val overlayContext = overlayController.requestWindow()
+        val overlay = TestOverlayView.show(overlayContext).apply { type = TYPE_TASKBAR_ALL_APPS }
+
+        overlayController.updateLauncherDeviceProfile(
+            overlayController.launcherDeviceProfile
+                .toBuilder(overlayContext)
+                .setGestureMode(false)
+                .build()
+        )
+
+        assertThat(overlay.isOpen).isTrue()
+    }
+
+    private class TestOverlayView
+    private constructor(
+        private val overlayContext: TaskbarOverlayContext,
+    ) : AbstractFloatingView(overlayContext, null) {
+
+        var type = TYPE_OPTIONS_POPUP
+
+        private fun show() {
+            mIsOpen = true
+            overlayContext.dragLayer.addView(this)
+        }
+
+        override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean = false
+
+        override fun handleClose(animate: Boolean) = overlayContext.dragLayer.removeView(this)
+
+        override fun isOfType(type: Int): Boolean = (type and this.type) != 0
+
+        companion object {
+            /** Adds a generic View to the Overlay window for testing. */
+            fun show(context: TaskbarOverlayContext): TestOverlayView {
+                return TestOverlayView(context).apply { show() }
+            }
+        }
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
index 3b8754c..a394b65 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelTest.kt
@@ -28,6 +28,7 @@
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
+import com.android.quickstep.task.viewmodel.TaskContainerData
 import com.android.quickstep.task.viewmodel.TaskViewData
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
@@ -43,9 +44,10 @@
 class TaskThumbnailViewModelTest {
     private val recentsViewData = RecentsViewData()
     private val taskViewData = TaskViewData()
+    private val taskContainerData = TaskContainerData()
     private val tasksRepository = FakeTasksRepository()
     private val systemUnderTest =
-        TaskThumbnailViewModel(recentsViewData, taskViewData, tasksRepository)
+        TaskThumbnailViewModel(recentsViewData, taskViewData, taskContainerData, tasksRepository)
 
     private val tasks = (0..5).map(::createTaskWithId)
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt
new file mode 100644
index 0000000..0bf68eb
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/customization/TaskbarSpecsEvaluatorTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 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.taskbar.customization
+
+import com.android.launcher3.taskbar.customization.TaskbarFeatureEvaluator
+import com.android.launcher3.taskbar.customization.TaskbarIconSpecs
+import com.android.launcher3.taskbar.customization.TaskbarSpecsEvaluator
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
+
+@RunWith(LauncherMultivalentJUnit::class)
+class TaskbarSpecsEvaluatorTest {
+
+    private val taskbarFeatureEvaluator = mock<TaskbarFeatureEvaluator>()
+    private val taskbarSpecsEvaluator = spy(TaskbarSpecsEvaluator(taskbarFeatureEvaluator))
+
+    @Test
+    fun testGetIconSizeByGrid_whenTaskbarIsTransient_withValidRowAndColumnInLandscape() {
+        doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+        doReturn(true).whenever(taskbarFeatureEvaluator).isLandscape
+        assertThat(taskbarSpecsEvaluator.getIconSizeByGrid(4, 4))
+            .isEqualTo(TaskbarIconSpecs.iconSize52dp)
+    }
+
+    @Test
+    fun testGetIconSizeByGrid_whenTaskbarIsTransient_withValidRowAndColumnInPortrait() {
+        doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+        doReturn(false).whenever(taskbarFeatureEvaluator).isLandscape
+        assertThat(taskbarSpecsEvaluator.getIconSizeByGrid(4, 4))
+            .isEqualTo(TaskbarIconSpecs.iconSize48dp)
+    }
+
+    @Test
+    fun testGetIconSizeByGrid_whenTaskbarIsTransient_withInvalidRowAndColumn() {
+        doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeByGrid(1, 2))
+            .isEqualTo(TaskbarIconSpecs.defaultTransientIconSize)
+    }
+
+    @Test
+    fun testGetIconSizeByGrid_whenTaskbarIsPersistent() {
+        doReturn(false).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeByGrid(6, 5))
+            .isEqualTo(TaskbarIconSpecs.defaultPersistentIconSize)
+    }
+
+    @Test
+    fun testGetIconSizeStepDown_whenTaskbarIsPersistent() {
+        doReturn(false).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeStepDown(TaskbarIconSpecs.iconSize44dp))
+            .isEqualTo(TaskbarIconSpecs.defaultPersistentIconSize)
+    }
+
+    @Test
+    fun testGetIconSizeStepDown_whenTaskbarIsTransientAndIconSizeAreInBound() {
+        doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeStepDown(TaskbarIconSpecs.iconSize52dp))
+            .isEqualTo(TaskbarIconSpecs.iconSize48dp)
+    }
+
+    @Test
+    fun testGetIconSizeStepDown_whenTaskbarIsTransientAndIconSizeAreOutOfBound() {
+        doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeStepDown(TaskbarIconSpecs.iconSize44dp))
+            .isEqualTo(TaskbarIconSpecs.iconSize44dp)
+    }
+
+    @Test
+    fun testGetIconSizeStepUp_whenTaskbarIsPersistent() {
+        doReturn(false).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeStepUp(TaskbarIconSpecs.iconSize40dp))
+            .isEqualTo(TaskbarIconSpecs.iconSize40dp)
+    }
+
+    @Test
+    fun testGetIconSizeStepUp_whenTaskbarIsTransientAndIconSizeAreInBound() {
+        doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeStepUp(TaskbarIconSpecs.iconSize44dp))
+            .isEqualTo(TaskbarIconSpecs.iconSize48dp)
+    }
+
+    @Test
+    fun testGetIconSizeStepUp_whenTaskbarIsTransientAndIconSizeAreOutOfBound() {
+        doReturn(true).whenever(taskbarFeatureEvaluator).isTransient
+        assertThat(taskbarSpecsEvaluator.getIconSizeStepUp(TaskbarIconSpecs.iconSize52dp))
+            .isEqualTo(TaskbarIconSpecs.iconSize52dp)
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
index 250dc7b..d40f8ab 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -78,7 +78,7 @@
     private val mockSplitSourceDrawable: Drawable = mock()
     private val mockSplitSourceView: View = mock()
 
-    private val stateManager: StateManager<*> = mock()
+    private val stateManager: StateManager<*, *> = mock()
     private val depthController: DepthController = mock()
     private val transitionInfo: TransitionInfo = mock()
     private val transaction: Transaction = mock()
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 aa08ca4..bab84ef 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -31,6 +31,7 @@
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.statehandlers.DepthController
 import com.android.launcher3.statemanager.StateManager
+import com.android.launcher3.statemanager.StatefulActivity
 import com.android.launcher3.util.ComponentKey
 import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.quickstep.RecentsModel
@@ -61,7 +62,7 @@
     private val depthController: DepthController = mock()
     private val statsLogManager: StatsLogManager = mock()
     private val statsLogger: StatsLogger = mock()
-    private val stateManager: StateManager<LauncherState> = mock()
+    private val stateManager: StateManager<LauncherState, StatefulActivity<LauncherState>> = mock()
     private val handler: Handler = mock()
     private val context: RecentsViewContainer = mock()
     private val recentsModel: RecentsModel = mock()
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsControllerTest.kt
deleted file mode 100644
index 5b56710..0000000
--- a/quickstep/tests/src/com/android/launcher3/taskbar/DesktopTaskbarRunningAppsControllerTest.kt
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2024 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.taskbar
-
-import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
-import android.content.ComponentName
-import android.content.Intent
-import android.os.Process
-import android.os.UserHandle
-import android.testing.AndroidTestingRunner
-import com.android.launcher3.model.data.AppInfo
-import com.android.launcher3.model.data.ItemInfo
-import com.android.launcher3.statehandlers.DesktopVisibilityController
-import com.android.quickstep.RecentsModel
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.junit.MockitoJUnit
-import org.mockito.kotlin.whenever
-
-@RunWith(AndroidTestingRunner::class)
-class DesktopTaskbarRunningAppsControllerTest : TaskbarBaseTestCase() {
-
-    @get:Rule val mockitoRule = MockitoJUnit.rule()
-
-    @Mock private lateinit var mockRecentsModel: RecentsModel
-    @Mock private lateinit var mockDesktopVisibilityController: DesktopVisibilityController
-
-    private var nextTaskId: Int = 500
-
-    private lateinit var taskbarRunningAppsController: DesktopTaskbarRunningAppsController
-    private lateinit var userHandle: UserHandle
-
-    @Before
-    fun setUp() {
-        super.setup()
-        userHandle = Process.myUserHandle()
-        taskbarRunningAppsController =
-            DesktopTaskbarRunningAppsController(mockRecentsModel) {
-                mockDesktopVisibilityController
-            }
-        taskbarRunningAppsController.init(taskbarControllers)
-        taskbarRunningAppsController.setApps(
-            ALL_APP_PACKAGES.map { createTestAppInfo(packageName = it) }.toTypedArray()
-        )
-    }
-
-    @Test
-    fun updateHotseatItemInfos_notInDesktopMode_returnsExistingHotseatItems() {
-        setInDesktopMode(false)
-        val hotseatItems =
-            createHotseatItemsFromPackageNames(listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2))
-
-        assertThat(taskbarRunningAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray()))
-            .isEqualTo(hotseatItems.toTypedArray())
-    }
-
-    @Test
-    fun updateHotseatItemInfos_notInDesktopMode_runningApps_returnsExistingHotseatItems() {
-        setInDesktopMode(false)
-        val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
-        val hotseatItems = createHotseatItemsFromPackageNames(hotseatPackages)
-        val runningTasks =
-            createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
-        whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
-        taskbarRunningAppsController.updateRunningApps()
-
-        val newHotseatItems =
-            taskbarRunningAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
-
-        assertThat(newHotseatItems.map { it?.targetPackage })
-            .containsExactlyElementsIn(hotseatPackages)
-    }
-
-    @Test
-    fun updateHotseatItemInfos_noRunningApps_returnsExistingHotseatItems() {
-        setInDesktopMode(true)
-        val hotseatItems =
-            createHotseatItemsFromPackageNames(listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2))
-
-        assertThat(taskbarRunningAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray()))
-            .isEqualTo(hotseatItems.toTypedArray())
-    }
-
-    @Test
-    fun updateHotseatItemInfos_returnsExistingHotseatItemsAndRunningApps() {
-        setInDesktopMode(true)
-        val hotseatItems =
-            createHotseatItemsFromPackageNames(listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2))
-        val runningTasks =
-            createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
-        whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
-        taskbarRunningAppsController.updateRunningApps()
-
-        val newHotseatItems =
-            taskbarRunningAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
-
-        val expectedPackages =
-            listOf(
-                HOTSEAT_PACKAGE_1,
-                HOTSEAT_PACKAGE_2,
-                RUNNING_APP_PACKAGE_1,
-                RUNNING_APP_PACKAGE_2,
-            )
-        assertThat(newHotseatItems.map { it?.targetPackage })
-            .containsExactlyElementsIn(expectedPackages)
-    }
-
-    @Test
-    fun updateHotseatItemInfos_runningAppIsHotseatItem_returnsDistinctItems() {
-        setInDesktopMode(true)
-        val hotseatItems =
-            createHotseatItemsFromPackageNames(listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2))
-        val runningTasks =
-            createDesktopTasksFromPackageNames(
-                listOf(HOTSEAT_PACKAGE_1, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
-            )
-        whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
-        taskbarRunningAppsController.updateRunningApps()
-
-        val newHotseatItems =
-            taskbarRunningAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
-
-        val expectedPackages =
-            listOf(
-                HOTSEAT_PACKAGE_1,
-                HOTSEAT_PACKAGE_2,
-                RUNNING_APP_PACKAGE_1,
-                RUNNING_APP_PACKAGE_2,
-            )
-        assertThat(newHotseatItems.map { it?.targetPackage })
-            .containsExactlyElementsIn(expectedPackages)
-    }
-
-    @Test
-    fun getRunningApps_notInDesktopMode_returnsEmptySet() {
-        setInDesktopMode(false)
-        val runningTasks =
-            createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
-        whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
-        taskbarRunningAppsController.updateRunningApps()
-
-        assertThat(taskbarRunningAppsController.runningApps).isEmpty()
-        assertThat(taskbarRunningAppsController.minimizedApps).isEmpty()
-    }
-
-    @Test
-    fun getRunningApps_inDesktopMode_returnsRunningApps() {
-        setInDesktopMode(true)
-        val runningTasks =
-            createDesktopTasksFromPackageNames(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
-        whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
-        taskbarRunningAppsController.updateRunningApps()
-
-        assertThat(taskbarRunningAppsController.runningApps)
-            .containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
-        assertThat(taskbarRunningAppsController.minimizedApps).isEmpty()
-    }
-
-    @Test
-    fun getMinimizedApps_inDesktopMode_returnsAllAppsRunningAndInvisibleAppsMinimized() {
-        setInDesktopMode(true)
-        val runningTasks =
-            ArrayList(
-                listOf(
-                    createDesktopTaskInfo(RUNNING_APP_PACKAGE_1) { isVisible = true },
-                    createDesktopTaskInfo(RUNNING_APP_PACKAGE_2) { isVisible = true },
-                    createDesktopTaskInfo(RUNNING_APP_PACKAGE_3) { isVisible = false },
-                )
-            )
-        whenever(mockRecentsModel.runningTasks).thenReturn(runningTasks)
-        taskbarRunningAppsController.updateRunningApps()
-
-        assertThat(taskbarRunningAppsController.runningApps)
-            .containsExactly(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3)
-        assertThat(taskbarRunningAppsController.minimizedApps)
-            .containsExactly(RUNNING_APP_PACKAGE_3)
-    }
-
-    private fun createHotseatItemsFromPackageNames(packageNames: List<String>): List<ItemInfo> {
-        return packageNames.map { createTestAppInfo(packageName = it) }
-    }
-
-    private fun createDesktopTasksFromPackageNames(
-        packageNames: List<String>
-    ): ArrayList<RunningTaskInfo> {
-        return ArrayList(packageNames.map { createDesktopTaskInfo(packageName = it) })
-    }
-
-    private fun createDesktopTaskInfo(
-        packageName: String,
-        init: RunningTaskInfo.() -> Unit = { isVisible = true },
-    ): RunningTaskInfo {
-        return RunningTaskInfo().apply {
-            taskId = nextTaskId++
-            configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
-            realActivity = ComponentName(packageName, "TestActivity")
-            init()
-        }
-    }
-
-    private fun createTestAppInfo(
-        packageName: String = "testPackageName",
-        className: String = "testClassName"
-    ) = AppInfo(ComponentName(packageName, className), className /* title */, userHandle, Intent())
-
-    private fun setInDesktopMode(inDesktopMode: Boolean) {
-        whenever(mockDesktopVisibilityController.areDesktopTasksVisible()).thenReturn(inDesktopMode)
-    }
-
-    private companion object {
-        const val HOTSEAT_PACKAGE_1 = "hotseat1"
-        const val HOTSEAT_PACKAGE_2 = "hotseat2"
-        const val RUNNING_APP_PACKAGE_1 = "running1"
-        const val RUNNING_APP_PACKAGE_2 = "running2"
-        const val RUNNING_APP_PACKAGE_3 = "running3"
-        val ALL_APP_PACKAGES =
-            listOf(
-                HOTSEAT_PACKAGE_1,
-                HOTSEAT_PACKAGE_2,
-                RUNNING_APP_PACKAGE_1,
-                RUNNING_APP_PACKAGE_2,
-                RUNNING_APP_PACKAGE_3,
-            )
-    }
-}
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
index f3115c6..04012c0 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
@@ -37,7 +37,7 @@
     lateinit var stateListener: StateManager.StateListener<RecentsState>
 
     private val recentsActivity: RecentsActivity = mock()
-    private val stateManager: StateManager<RecentsState> = mock()
+    private val stateManager: StateManager<RecentsState, RecentsActivity> = mock()
 
     @Before
     override fun setup() {
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
new file mode 100644
index 0000000..486dc68
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -0,0 +1,591 @@
+/*
+ * Copyright (C) 2024 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.taskbar
+
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Process
+import android.os.UserHandle
+import android.testing.AndroidTestingRunner
+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.statehandlers.DesktopVisibilityController
+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.systemui.shared.recents.model.Task
+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.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidTestingRunner::class)
+class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() {
+
+    @get:Rule val mockitoRule = MockitoJUnit.rule()
+
+    @Mock private lateinit var mockIconCache: TaskIconCache
+    @Mock private lateinit var mockRecentsModel: RecentsModel
+    @Mock private lateinit var mockDesktopVisibilityController: DesktopVisibilityController
+
+    private var nextTaskId: Int = 500
+    private var taskListChangeId: Int = 1
+
+    private lateinit var recentAppsController: TaskbarRecentAppsController
+    private lateinit var recentTasksChangedListener: RecentTasksChangedListener
+    private lateinit var userHandle: UserHandle
+
+    @Before
+    fun setUp() {
+        super.setup()
+        userHandle = Process.myUserHandle()
+
+        whenever(mockRecentsModel.iconCache).thenReturn(mockIconCache)
+        recentAppsController =
+            TaskbarRecentAppsController(mockRecentsModel) { mockDesktopVisibilityController }
+        recentAppsController.init(taskbarControllers)
+        recentAppsController.canShowRunningApps = true
+        recentAppsController.canShowRecentApps = true
+
+        val listenerCaptor = ArgumentCaptor.forClass(RecentTasksChangedListener::class.java)
+        verify(mockRecentsModel).registerRecentTasksChangedListener(listenerCaptor.capture())
+        recentTasksChangedListener = listenerCaptor.value
+    }
+
+    @Test
+    fun updateHotseatItemInfos_cantShowRunning_inDesktopMode_returnsAllHotseatItems() {
+        recentAppsController.canShowRunningApps = false
+        setInDesktopMode(true)
+        val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1)
+        val newHotseatItems =
+            prepareHotseatAndRunningAndRecentApps(
+                hotseatPackages = hotseatPackages,
+                runningTaskPackages = emptyList(),
+                recentTaskPackages = emptyList()
+            )
+        assertThat(newHotseatItems.map { it?.targetPackage })
+            .containsExactlyElementsIn(hotseatPackages)
+    }
+
+    @Test
+    fun updateHotseatItemInfos_cantShowRecent_notInDesktopMode_returnsAllHotseatItems() {
+        recentAppsController.canShowRecentApps = false
+        setInDesktopMode(false)
+        val hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1)
+        val newHotseatItems =
+            prepareHotseatAndRunningAndRecentApps(
+                hotseatPackages = hotseatPackages,
+                runningTaskPackages = emptyList(),
+                recentTaskPackages = emptyList()
+            )
+        assertThat(newHotseatItems.map { it?.targetPackage })
+            .containsExactlyElementsIn(hotseatPackages)
+    }
+
+    @Test
+    fun updateHotseatItemInfos_canShowRunning_inDesktopMode_returnsNonPredictedHotseatItems() {
+        recentAppsController.canShowRunningApps = true
+        setInDesktopMode(true)
+        val newHotseatItems =
+            prepareHotseatAndRunningAndRecentApps(
+                hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
+                runningTaskPackages = emptyList(),
+                recentTaskPackages = emptyList()
+            )
+        val expectedPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
+        assertThat(newHotseatItems.map { it?.targetPackage })
+            .containsExactlyElementsIn(expectedPackages)
+    }
+
+    @Test
+    fun updateHotseatItemInfos_canShowRecent_notInDesktopMode_returnsNonPredictedHotseatItems() {
+        recentAppsController.canShowRecentApps = true
+        setInDesktopMode(false)
+        val newHotseatItems =
+            prepareHotseatAndRunningAndRecentApps(
+                hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
+                runningTaskPackages = emptyList(),
+                recentTaskPackages = emptyList()
+            )
+        val expectedPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2)
+        assertThat(newHotseatItems.map { it?.targetPackage })
+            .containsExactlyElementsIn(expectedPackages)
+    }
+
+    @Test
+    fun onRecentTasksChanged_cantShowRunning_inDesktopMode_shownTasks_returnsEmptyList() {
+        recentAppsController.canShowRunningApps = false
+        setInDesktopMode(true)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
+            runningTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
+            recentTaskPackages = emptyList()
+        )
+        assertThat(recentAppsController.shownTasks).isEmpty()
+    }
+
+    @Test
+    fun onRecentTasksChanged_cantShowRecent_notInDesktopMode_shownTasks_returnsEmptyList() {
+        recentAppsController.canShowRecentApps = false
+        setInDesktopMode(false)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2, PREDICTED_PACKAGE_1),
+            runningTaskPackages = emptyList(),
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+        )
+        assertThat(recentAppsController.shownTasks).isEmpty()
+    }
+
+    @Test
+    fun onRecentTasksChanged_notInDesktopMode_noRecentTasks_shownTasks_returnsEmptyList() {
+        setInDesktopMode(false)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+            recentTaskPackages = emptyList()
+        )
+        assertThat(recentAppsController.shownTasks).isEmpty()
+        assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+    }
+
+    @Test
+    fun onRecentTasksChanged_inDesktopMode_noRunningApps_shownTasks_returnsEmptyList() {
+        setInDesktopMode(true)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = emptyList(),
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+        )
+        assertThat(recentAppsController.shownTasks).isEmpty()
+    }
+
+    @Test
+    fun onRecentTasksChanged_inDesktopMode_shownTasks_returnsRunningTasks() {
+        setInDesktopMode(true)
+        val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = runningTaskPackages,
+            recentTaskPackages = emptyList()
+        )
+        val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
+        assertThat(shownPackages).containsExactlyElementsIn(runningTaskPackages)
+    }
+
+    @Test
+    fun onRecentTasksChanged_inDesktopMode_runningAppIsHotseatItem_shownTasks_returnsDistinctItems() {
+        setInDesktopMode(true)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
+            runningTaskPackages =
+                listOf(HOTSEAT_PACKAGE_1, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+            recentTaskPackages = emptyList()
+        )
+        val expectedPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+        val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
+        assertThat(shownPackages).containsExactlyElementsIn(expectedPackages)
+    }
+
+    @Test
+    fun onRecentTasksChanged_notInDesktopMode_getRunningApps_returnsEmptySet() {
+        setInDesktopMode(false)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+            recentTaskPackages = emptyList()
+        )
+        assertThat(recentAppsController.runningAppPackages).isEmpty()
+        assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+    }
+
+    @Test
+    fun onRecentTasksChanged_inDesktopMode_getRunningApps_returnsAllDesktopTasks() {
+        setInDesktopMode(true)
+        val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = runningTaskPackages,
+            recentTaskPackages = emptyList()
+        )
+        assertThat(recentAppsController.runningAppPackages)
+            .containsExactlyElementsIn(runningTaskPackages)
+        assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+    }
+
+    @Test
+    fun onRecentTasksChanged_inDesktopMode_getRunningApps_includesHotseat() {
+        setInDesktopMode(true)
+        val runningTaskPackages =
+            listOf(HOTSEAT_PACKAGE_1, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = listOf(HOTSEAT_PACKAGE_1, HOTSEAT_PACKAGE_2),
+            runningTaskPackages = runningTaskPackages,
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+        )
+        assertThat(recentAppsController.runningAppPackages)
+            .containsExactlyElementsIn(runningTaskPackages)
+        assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+    }
+
+    @Test
+    fun getMinimizedApps_inDesktopMode_returnsAllAppsRunningAndInvisibleAppsMinimized() {
+        setInDesktopMode(true)
+        val runningTaskPackages =
+            listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3)
+        val minimizedTaskIndices = setOf(2) // RUNNING_APP_PACKAGE_3
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = runningTaskPackages,
+            minimizedTaskIndices = minimizedTaskIndices,
+            recentTaskPackages = emptyList()
+        )
+        assertThat(recentAppsController.runningAppPackages)
+            .containsExactlyElementsIn(runningTaskPackages)
+        assertThat(recentAppsController.minimizedAppPackages).containsExactly(RUNNING_APP_PACKAGE_3)
+    }
+
+    @Test
+    fun getMinimizedApps_inDesktopMode_twoTasksSamePackageOneMinimizedReturnsNotMinimized() {
+        setInDesktopMode(true)
+        val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_1)
+        val minimizedTaskIndices = setOf(1) // The second RUNNING_APP_PACKAGE_1 task.
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = runningTaskPackages,
+            minimizedTaskIndices = minimizedTaskIndices,
+            recentTaskPackages = emptyList()
+        )
+        assertThat(recentAppsController.runningAppPackages)
+            .containsExactlyElementsIn(runningTaskPackages.toSet())
+        assertThat(recentAppsController.minimizedAppPackages).isEmpty()
+    }
+
+    @Test
+    fun onRecentTasksChanged_inDesktopMode_shownTasks_maintainsOrder() {
+        setInDesktopMode(true)
+        val originalOrder = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = originalOrder,
+            recentTaskPackages = emptyList()
+        )
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = listOf(RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_1),
+            recentTaskPackages = emptyList()
+        )
+        val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
+        assertThat(shownPackages).isEqualTo(originalOrder)
+    }
+
+    @Test
+    fun onRecentTasksChanged_notInDesktopMode_shownTasks_maintainsRecency() {
+        setInDesktopMode(false)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = emptyList(),
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
+        )
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = emptyList(),
+            recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3, RECENT_PACKAGE_1)
+        )
+        val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
+        // Most recent packages, minus the currently running one (RECENT_PACKAGE_1).
+        assertThat(shownPackages).isEqualTo(listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3))
+    }
+
+    @Test
+    fun onRecentTasksChanged_inDesktopMode_addTask_shownTasks_maintainsOrder() {
+        setInDesktopMode(true)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+            recentTaskPackages = emptyList()
+        )
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages =
+                listOf(RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_3),
+            recentTaskPackages = emptyList()
+        )
+        val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
+        val expectedOrder =
+            listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3)
+        assertThat(shownPackages).isEqualTo(expectedOrder)
+    }
+
+    @Test
+    fun onRecentTasksChanged_notInDesktopMode_addTask_shownTasks_maintainsRecency() {
+        setInDesktopMode(false)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = emptyList(),
+            recentTaskPackages = listOf(RECENT_PACKAGE_3, RECENT_PACKAGE_2)
+        )
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = emptyList(),
+            recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3, RECENT_PACKAGE_1)
+        )
+        val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
+        // Most recent packages, minus the currently running one (RECENT_PACKAGE_1).
+        assertThat(shownPackages).isEqualTo(listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3))
+    }
+
+    @Test
+    fun onRecentTasksChanged_inDesktopMode_removeTask_shownTasks_maintainsOrder() {
+        setInDesktopMode(true)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages =
+                listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_3),
+            recentTaskPackages = emptyList()
+        )
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = listOf(RUNNING_APP_PACKAGE_2, RUNNING_APP_PACKAGE_1),
+            recentTaskPackages = emptyList()
+        )
+        val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
+        assertThat(shownPackages).isEqualTo(listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2))
+    }
+
+    @Test
+    fun onRecentTasksChanged_notInDesktopMode_removeTask_shownTasks_maintainsRecency() {
+        setInDesktopMode(false)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = emptyList(),
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
+        )
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = emptyList(),
+            recentTaskPackages = listOf(RECENT_PACKAGE_2, RECENT_PACKAGE_3)
+        )
+        val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
+        // Most recent packages, minus the currently running one (RECENT_PACKAGE_3).
+        assertThat(shownPackages).isEqualTo(listOf(RECENT_PACKAGE_2))
+    }
+
+    @Test
+    fun onRecentTasksChanged_enterDesktopMode_shownTasks_onlyIncludesRunningTasks() {
+        setInDesktopMode(false)
+        val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+        val recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = runningTaskPackages,
+            recentTaskPackages = recentTaskPackages
+        )
+        setInDesktopMode(true)
+        recentTasksChangedListener.onRecentTasksChanged()
+        val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
+        assertThat(shownPackages).containsExactlyElementsIn(runningTaskPackages)
+    }
+
+    @Test
+    fun onRecentTasksChanged_exitDesktopMode_shownTasks_onlyIncludesRecentTasks() {
+        setInDesktopMode(true)
+        val runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+        val recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = runningTaskPackages,
+            recentTaskPackages = recentTaskPackages
+        )
+        setInDesktopMode(false)
+        recentTasksChangedListener.onRecentTasksChanged()
+        val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
+        // Don't expect RECENT_PACKAGE_3 because it is currently running.
+        val expectedPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+        assertThat(shownPackages).containsExactlyElementsIn(expectedPackages)
+    }
+
+    @Test
+    fun onRecentTasksChanged_notInDesktopMode_hasRecentTasks_shownTasks_returnsRecentTasks() {
+        setInDesktopMode(false)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = emptyList(),
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2, RECENT_PACKAGE_3)
+        )
+        val shownPackages = recentAppsController.shownTasks.flatMap { it.packageNames }
+        // RECENT_PACKAGE_3 is the top task (visible to user) so should be excluded.
+        val expectedPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+        assertThat(shownPackages).containsExactlyElementsIn(expectedPackages)
+    }
+
+    @Test
+    fun onRecentTasksChanged_notInDesktopMode_hasRecentAndRunningTasks_shownTasks_returnsRecentTaskAndDesktopTile() {
+        setInDesktopMode(false)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2),
+            recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+        )
+        val shownPackages = recentAppsController.shownTasks.map { it.packageNames }
+        // Only 2 recent tasks shown: Desktop Tile + 1 Recent Task
+        val desktopTilePackages = listOf(RUNNING_APP_PACKAGE_1, RUNNING_APP_PACKAGE_2)
+        val recentTaskPackages = listOf(RECENT_PACKAGE_1)
+        val expectedPackages = listOf(desktopTilePackages, recentTaskPackages)
+        assertThat(shownPackages).containsExactlyElementsIn(expectedPackages)
+    }
+
+    @Test
+    fun onRecentTasksChanged_notInDesktopMode_hasRecentAndSplitTasks_shownTasks_returnsRecentTaskAndPair() {
+        setInDesktopMode(false)
+        prepareHotseatAndRunningAndRecentApps(
+            hotseatPackages = emptyList(),
+            runningTaskPackages = emptyList(),
+            recentTaskPackages = listOf(RECENT_SPLIT_PACKAGES_1, RECENT_PACKAGE_1, RECENT_PACKAGE_2)
+        )
+        val shownPackages = recentAppsController.shownTasks.map { it.packageNames }
+        // Only 2 recent tasks shown: Pair + 1 Recent Task
+        val pairPackages = RECENT_SPLIT_PACKAGES_1.split("_")
+        val recentTaskPackages = listOf(RECENT_PACKAGE_1)
+        val expectedPackages = listOf(pairPackages, recentTaskPackages)
+        assertThat(shownPackages).containsExactlyElementsIn(expectedPackages)
+    }
+
+    private fun prepareHotseatAndRunningAndRecentApps(
+        hotseatPackages: List<String>,
+        runningTaskPackages: List<String>,
+        minimizedTaskIndices: Set<Int> = emptySet(),
+        recentTaskPackages: List<String>,
+    ): Array<ItemInfo?> {
+        val hotseatItems = createHotseatItemsFromPackageNames(hotseatPackages)
+        val newHotseatItems =
+            recentAppsController.updateHotseatItemInfos(hotseatItems.toTypedArray())
+        val runningTasks = createDesktopTask(runningTaskPackages, minimizedTaskIndices)
+        val recentTasks = createRecentTasksFromPackageNames(recentTaskPackages)
+        val allTasks =
+            ArrayList<GroupTask>().apply {
+                if (runningTasks != null) {
+                    add(runningTasks)
+                }
+                addAll(recentTasks)
+            }
+        doAnswer {
+                val callback: Consumer<ArrayList<GroupTask>> = it.getArgument(0)
+                callback.accept(allTasks)
+                taskListChangeId
+            }
+            .whenever(mockRecentsModel)
+            .getTasks(any<Consumer<List<GroupTask>>>())
+        recentTasksChangedListener.onRecentTasksChanged()
+        return newHotseatItems
+    }
+
+    private fun createHotseatItemsFromPackageNames(packageNames: List<String>): List<ItemInfo> {
+        return packageNames.map {
+            createTestAppInfo(packageName = it).apply {
+                container =
+                    if (it.startsWith("predicted")) {
+                        CONTAINER_HOTSEAT_PREDICTION
+                    } else {
+                        CONTAINER_HOTSEAT
+                    }
+            }
+        }
+    }
+
+    private fun createTestAppInfo(
+        packageName: String = "testPackageName",
+        className: String = "testClassName"
+    ) = AppInfo(ComponentName(packageName, className), className /* title */, userHandle, Intent())
+
+    private fun createDesktopTask(
+        packageNames: List<String>,
+        minimizedTaskIndices: Set<Int>
+    ): DesktopTask? {
+        if (packageNames.isEmpty()) return null
+
+        return DesktopTask(
+            ArrayList(
+                packageNames.mapIndexed { index, packageName ->
+                    createTask(packageName, index !in minimizedTaskIndices)
+                }
+            )
+        )
+    }
+
+    private fun createRecentTasksFromPackageNames(packageNames: List<String>): List<GroupTask> {
+        return packageNames.map {
+            if (it.startsWith("split")) {
+                val splitPackages = it.split("_")
+                GroupTask(
+                    createTask(splitPackages[0]),
+                    createTask(splitPackages[1]),
+                    /* splitBounds = */ null
+                )
+            } else {
+                GroupTask(createTask(it))
+            }
+        }
+    }
+
+    private fun createTask(packageName: String, isVisible: Boolean = true): Task {
+        return Task(
+                Task.TaskKey(
+                    nextTaskId++,
+                    WINDOWING_MODE_FREEFORM,
+                    Intent().apply { `package` = packageName },
+                    ComponentName(packageName, "TestActivity"),
+                    userHandle.identifier,
+                    0
+                )
+            )
+            .apply { this.isVisible = isVisible }
+    }
+
+    private fun setInDesktopMode(inDesktopMode: Boolean) {
+        whenever(mockDesktopVisibilityController.areDesktopTasksVisible()).thenReturn(inDesktopMode)
+    }
+
+    private val GroupTask.packageNames: List<String>
+        get() = tasks.map { task -> task.key.packageName }
+
+    private companion object {
+        const val HOTSEAT_PACKAGE_1 = "hotseat1"
+        const val HOTSEAT_PACKAGE_2 = "hotseat2"
+        const val PREDICTED_PACKAGE_1 = "predicted1"
+        const val RUNNING_APP_PACKAGE_1 = "running1"
+        const val RUNNING_APP_PACKAGE_2 = "running2"
+        const val RUNNING_APP_PACKAGE_3 = "running3"
+        const val RECENT_PACKAGE_1 = "recent1"
+        const val RECENT_PACKAGE_2 = "recent2"
+        const val RECENT_PACKAGE_3 = "recent3"
+        const val RECENT_SPLIT_PACKAGES_1 = "split1_split2"
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
index ed5526f..03244eb 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
@@ -48,6 +48,8 @@
 
     @Mock
     private SystemUiProxy mockSystemUiProxy;
+    @Mock
+    private TopTaskTracker mTopTaskTracker;
 
     // Class under test
     private RecentTasksList mRecentTasksList;
@@ -58,7 +60,7 @@
         LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class);
         KeyguardManager mockKeyguardManager = mock(KeyguardManager.class);
         mRecentTasksList = new RecentTasksList(mockMainThreadExecutor, mockKeyguardManager,
-                mockSystemUiProxy);
+                mockSystemUiProxy, mTopTaskTracker);
     }
 
     @Test
diff --git a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
index 5157c71..80fbce7 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
@@ -9,8 +9,19 @@
 import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
 import com.android.launcher3.util.DisplayController.Info
 import com.android.launcher3.util.NavigationMode
-import com.android.launcher3.util.window.WindowManagerProxy
 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
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED
+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.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -28,7 +39,6 @@
 class RecentsAnimationDeviceStateTest {
 
     @Mock private lateinit var exclusionManager: GestureExclusionManager
-    @Mock private lateinit var windowManagerProxy: WindowManagerProxy
     @Mock private lateinit var info: Info
 
     private val context = ApplicationProvider.getApplicationContext() as Context
@@ -108,4 +118,88 @@
 
         verifyZeroInteractions(exclusionManager)
     }
+
+    @Test
+    fun trackpadGesturesNotAllowedForSelectedStates() {
+        val disablingStates = GESTURE_DISABLING_SYSUI_STATES +
+                SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED
+
+        allSysUiStates().forEach { state ->
+            val canStartGesture = !disablingStates.contains(state)
+            underTest.setSystemUiFlags(state)
+            assertThat(underTest.canStartTrackpadGesture()).isEqualTo(canStartGesture)
+        }
+    }
+
+    @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
+        )
+
+        stateToExpectedResult.forEach { (state, allowed) ->
+            underTest.setSystemUiFlags(state)
+            assertThat(underTest.canStartTrackpadGesture()).isEqualTo(allowed)
+        }
+    }
+
+    @Test
+    fun systemGesturesNotAllowedForSelectedStates() {
+        val disablingStates = GESTURE_DISABLING_SYSUI_STATES + SYSUI_STATE_NAV_BAR_HIDDEN
+
+        allSysUiStates().forEach { state ->
+            val canStartGesture = !disablingStates.contains(state)
+            underTest.setSystemUiFlags(state)
+            assertThat(underTest.canStartSystemGesture()).isEqualTo(canStartGesture)
+        }
+    }
+
+    @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,
+        )
+
+        stateToExpectedResult.forEach {(state, gestureAllowed) ->
+            underTest.setSystemUiFlags(state)
+            assertThat(underTest.canStartSystemGesture()).isEqualTo(gestureAllowed)
+        }
+    }
+
+    private fun allSysUiStates(): List<Long> {
+        // SYSUI_STATES_* are binary flags
+        return (0..SYSUI_STATES_COUNT).map { 1L shl it }
+    }
+
+    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 const val SYSUI_STATES_COUNT = 33
+        private const val DEFAULT_STATE = 0L
+    }
+
+    private fun Long.enable(state: Long) = this or state
+
+    private fun Long.disable(state: Long) = this and state.inv()
 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
index fa10b61..b7fd8be 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplOverviewIconTest.java
@@ -88,6 +88,8 @@
     }
 
     private void createAndLaunchASplitPair() {
+        clearAllRecentTasks();
+
         startTestActivity(2);
         startTestActivity(3);
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
index 729452a..23a29f7 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
@@ -49,6 +49,7 @@
 
     private static final String PRIVATE_PROFILE_NAME = "LauncherPrivateProfile";
     private static final String INSTALLED_APP_NAME = "Aardwolf";
+    private static final int MAX_STATE_TOGGLE_TRIES = 2;
     private static final String TAG = "TaplPrivateSpaceTest";
 
     @Override
@@ -172,7 +173,9 @@
         HomeAllApps homeAllApps = mLauncher.getAllApps();
 
         // Disable Private Space
-        togglePrivateSpace(PrivateProfileManager.STATE_DISABLED, homeAllApps);
+        togglePrivateSpaceWithRetry(PrivateProfileManager.STATE_DISABLED, homeAllApps);
+        // Scroll to the bottom of All Apps
+        executeOnLauncher(launcher -> launcher.getAppsView().resetAndScrollToPrivateSpaceHeader());
 
         homeAllApps.freeze();
         try {
@@ -184,7 +187,9 @@
         }
 
         // Enable Private Space
-        togglePrivateSpace(PrivateProfileManager.STATE_ENABLED, homeAllApps);
+        togglePrivateSpaceWithRetry(PrivateProfileManager.STATE_ENABLED, homeAllApps);
+        // Scroll to the bottom of All Apps
+        executeOnLauncher(launcher -> launcher.getAppsView().resetAndScrollToPrivateSpaceHeader());
 
         homeAllApps.freeze();
         try {
@@ -217,6 +222,25 @@
         waitForLauncherUIUpdate();
     }
 
+    private void togglePrivateSpaceWithRetry(int state, HomeAllApps homeAllApps) {
+        int togglePsCount = 0;
+        boolean shouldRetry;
+        do {
+            togglePsCount ++;
+            try {
+                togglePrivateSpace(state, homeAllApps);
+                // No need to retry if the toggle was successful.
+                shouldRetry = false;
+            } catch (AssertionError error) {
+                if (togglePsCount < MAX_STATE_TOGGLE_TRIES) {
+                    shouldRetry = true;
+                } else {
+                    throw error;
+                }
+            }
+        } while (shouldRetry);
+    }
+
     private void waitForPrivateSpaceSetup() {
         waitForLauncherCondition("Private Profile not setup",
                 launcher -> launcher.getAppsView().hasPrivateProfile(),
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 7877e8a..1dfab26 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -401,6 +401,7 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
+    @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/325659406
     public void testQuickSwitchFromHome() throws Exception {
         startTestActivity(2);
         mLauncher.goHome().quickSwitchToPreviousApp();
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index 6be082a..8adf793 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
@@ -33,9 +33,7 @@
 import com.android.launcher3.tapl.Overview;
 import com.android.launcher3.tapl.Taskbar;
 import com.android.launcher3.tapl.TaskbarAppIcon;
-import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
 import com.android.launcher3.util.rule.TestStabilityRule;
-import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
 import com.android.wm.shell.Flags;
 
 import org.junit.After;
@@ -74,14 +72,6 @@
     }
 
     @Test
-    @PortraitLandscape
-    public void testSplitFromOverview() {
-        createAndLaunchASplitPair();
-    }
-
-    @Test
-    @PortraitLandscape
-    @TaskbarModeSwitch
     @TestStabilityRule.Stability(flavors = PLATFORM_POSTSUBMIT | LOCAL) // b/295225524
     public void testSplitAppFromHomeWithItself() throws Exception {
         // Currently only tablets have Taskbar in Overview, so test is only active on tablets
@@ -152,11 +142,7 @@
         // Currently only tablets have Taskbar in Overview, so test is only active on tablets
         assumeTrue(mLauncher.isTablet());
 
-        if (!mLauncher.getRecentTasks().isEmpty()) {
-            // Clear all recent tasks
-            mLauncher.goHome().switchToOverview().dismissAllTasks();
-        }
-
+        clearAllRecentTasks();
         startAppFast(getAppPackageName());
 
         Overview overview = mLauncher.goHome().switchToOverview();
@@ -173,6 +159,8 @@
     }
 
     private void createAndLaunchASplitPair() {
+        clearAllRecentTasks();
+
         startTestActivity(2);
         startTestActivity(3);
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java b/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
new file mode 100644
index 0000000..2d79623
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 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 org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class TaskAnimationManagerTest {
+
+    @Mock
+    private Context mContext;
+
+    @Mock
+    private SystemUiProxy mSystemUiProxy;
+
+    private TaskAnimationManager mTaskAnimationManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mTaskAnimationManager = new TaskAnimationManager(mContext) {
+            @Override
+            SystemUiProxy getSystemUiProxy() {
+                return mSystemUiProxy;
+            }
+        };
+    }
+
+    @Test
+    public void startRecentsActivity_allowBackgroundLaunch() {
+        assumeTrue(TaskAnimationManager.ENABLE_SHELL_TRANSITIONS);
+
+        final LauncherActivityInterface activityInterface = mock(LauncherActivityInterface.class);
+        final GestureState gestureState = mock(GestureState.class);
+        final RecentsAnimationCallbacks.RecentsAnimationListener listener =
+                mock(RecentsAnimationCallbacks.RecentsAnimationListener.class);
+        doReturn(activityInterface).when(gestureState).getContainerInterface();
+        mTaskAnimationManager.startRecentsAnimation(gestureState, new Intent(), listener);
+
+        final ArgumentCaptor<ActivityOptions> optionsCaptor =
+                ArgumentCaptor.forClass(ActivityOptions.class);
+        verify(mSystemUiProxy).startRecentsActivity(any(), optionsCaptor.capture(), any());
+        assertTrue(optionsCaptor.getValue()
+                .isPendingIntentBackgroundActivityLaunchAllowedByPermission());
+    }
+}
diff --git a/res/color-night-v31/taskbar_stroke.xml b/res/color-night-v31/taskbar_stroke.xml
new file mode 100644
index 0000000..db7a510
--- /dev/null
+++ b/res/color-night-v31/taskbar_stroke.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="#9AA0A6" />
+</selector>
\ No newline at end of file
diff --git a/res/color/taskbar_stroke.xml b/res/color/taskbar_stroke.xml
new file mode 100644
index 0000000..b691082
--- /dev/null
+++ b/res/color/taskbar_stroke.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2024 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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="#BDC1C6" />
+</selector>
\ No newline at end of file
diff --git a/res/layout/bubble_bar_overflow_button.xml b/res/layout/bubble_bar_overflow_button.xml
new file mode 100644
index 0000000..cb54990
--- /dev/null
+++ b/res/layout/bubble_bar_overflow_button.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<com.android.launcher3.taskbar.bubbles.BubbleView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/bubble_overflow_button"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"/>
\ No newline at end of file
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index ba50721..90e784d 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Legstukke gedeaktiveer in Veiligmodus"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Kortpad is nie beskikbaar nie"</string>
     <string name="home_screen" msgid="5629429142036709174">"Tuis"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Verdeelde skerm"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Programinligting vir %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Gebruikinstellings vir %1$s"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 10d68aa..1289b8c 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"ምግብሮች በደህንነቱ የተጠበቀ ሁኔታ ተሰናክለዋል"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"አቋራጭ አይገኝም"</string>
     <string name="home_screen" msgid="5629429142036709174">"መነሻ"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"የተከፈለ ማያ ገፅ"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"የመተግበሪያ መረጃ ለ%1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"የ%1$s የአጠቃቀም ቅንብሮች"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 6ba2ce6..37d6fba 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"الأدوات غير مفعّلة في الوضع الآمن"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"الاختصار غير متاح"</string>
     <string name="home_screen" msgid="5629429142036709174">"الشاشة الرئيسية"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"تقسيم الشاشة"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‏معلومات تطبيق %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"‏إعدادات استخدام \"%1$s\""</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index ce86039..e983ce8 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"ৱিজেটবোৰক সুৰক্ষিত ম\'ডত অক্ষম কৰা হ’ল"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"শ্বৰ্টকাট নাই"</string>
     <string name="home_screen" msgid="5629429142036709174">"গৃহ স্ক্ৰীন"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"বিভাজিত স্ক্ৰীন"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$sৰ বাবে এপৰ তথ্য"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sৰ বাবে ব্যৱহাৰৰ ছেটিং"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 9ad053a..f390c7c 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Vidcetlər Güvənli rejimdə deaktiv edilib"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Qısayol əlçatan deyil"</string>
     <string name="home_screen" msgid="5629429142036709174">"Əsas səhifə"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekran bölünməsi"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ilə bağlı tətbiq məlumatı"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s üzrə istifadə ayarları"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 408a425..fc71eeb 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Vidžeti su onemogućeni u Bezbednom režimu"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Prečica nije dostupna"</string>
     <string name="home_screen" msgid="5629429142036709174">"Početni ekran"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podeljeni ekran"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji za: %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Podešavanja potrošnje za %1$s"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index c7ed9d9..4589125 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Віджэты адключаны ў Бяспечным рэжыме"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Ярлык недаступны"</string>
     <string name="home_screen" msgid="5629429142036709174">"Галоўны экран"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Падзелены экран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Інфармацыя пра праграму для: %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s: налады выкарыстання"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index ce62054..643c24e 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Приспособленията са деактивирани в безопасния режим"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Няма достъп до прекия път"</string>
     <string name="home_screen" msgid="5629429142036709174">"Начален екран"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Разделен екран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Информация за приложението за %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Настройки за използването на %1$s"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index a9f4585..7e10a0e 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"সুরক্ষিত মোডে উইজেট নিষ্ক্রিয় থাকে"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"শর্টকাটগুলি অনুপলব্ধ"</string>
     <string name="home_screen" msgid="5629429142036709174">"হোম"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"স্প্লিট স্ক্রিন"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-এর জন্য অ্যাপ সম্পর্কিত তথ্য"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s-এর জন্য ব্যবহারের সেটিংস"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index e838b29..10aa86a 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Vidžeti su onemogućeni u sigurnom načinu rada."</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Prečica nije dostupna"</string>
     <string name="home_screen" msgid="5629429142036709174">"Početni ekran"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podijeljeni ekran"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Postavke korištenja za: %1$s"</string>
@@ -192,6 +194,6 @@
     <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privatno, zaključano."</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Zaključaj"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Prelazak u privatan prostor"</string>
-    <string name="ps_add_button_label" msgid="8127988716897128773">"Instaliraj"</string>
+    <string name="ps_add_button_label" msgid="8127988716897128773">"Instalirajte"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Instaliranje aplikacija u privatni prostor"</string>
 </resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 50ded7b..3d2ff4a 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"En Mode segur, els widgets estan desactivats."</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"La drecera no està disponible"</string>
     <string name="home_screen" msgid="5629429142036709174">"Inici"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informació de l\'aplicació %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Configuració d\'ús de %1$s"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index c570ef1..14a3583 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"V nouzovém režimu jsou widgety zakázány."</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Zkratka není k dispozici"</string>
     <string name="home_screen" msgid="5629429142036709174">"Domů"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Rozdělit obrazovku"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informace o aplikaci %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Nastavení využití pro aplikaci %1$s"</string>
@@ -192,6 +194,6 @@
     <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Soukromé, uzamčeno."</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Zamknout"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Převádění soukromého prostoru"</string>
-    <string name="ps_add_button_label" msgid="8127988716897128773">"Instalovat"</string>
+    <string name="ps_add_button_label" msgid="8127988716897128773">"Nainstalovat"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Instalovat aplikace do soukromého prostoru"</string>
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index e6f741f..f18e1e8 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets er deaktiveret i Beskyttet tilstand"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Genvejen er ikke tilgængelig"</string>
     <string name="home_screen" msgid="5629429142036709174">"Startskærm"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Opdel skærm"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinfo for %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Indstillinger for brug af %1$s"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index ec2285d..9a4e5e2 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets im abgesicherten Modus deaktiviert"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Verknüpfung nicht verfügbar"</string>
     <string name="home_screen" msgid="5629429142036709174">"Startbildschirm"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Splitscreen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App-Info für %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Nutzungseinstellungen für %1$s"</string>
@@ -186,7 +188,7 @@
     <string name="remote_action_failed" msgid="1383965239183576790">"Fehler: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
     <string name="private_space_label" msgid="2359721649407947001">"Vertrauliches Profil"</string>
     <string name="private_space_secondary_label" msgid="9203933341714508907">"Zum Einrichten oder Öffnen tippen"</string>
-    <string name="ps_container_title" msgid="4391796149519594205">"Privat"</string>
+    <string name="ps_container_title" msgid="4391796149519594205">"Vertraulich"</string>
     <string name="ps_container_settings" msgid="6059734123353320479">"Einstellungen für vertrauliches Profil"</string>
     <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privat, entsperrt."</string>
     <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privat, gesperrt."</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 8ace970..840ff5b 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Τα γραφικά στοιχεία απενεργοποιήθηκαν στην ασφαλή λειτουργία"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Η συντόμευση δεν είναι διαθέσιμη"</string>
     <string name="home_screen" msgid="5629429142036709174">"Αρχική οθόνη"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Διαχωρισμός οθόνης"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Πληροφορίες εφαρμογής για %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Ρυθμίσεις χρήσης για %1$s"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 3fe69cd..4deacb6 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
     <string name="home_screen" msgid="5629429142036709174">"Home"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index dee4a30..08ff6e7 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -27,6 +27,7 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
     <string name="home_screen" msgid="5629429142036709174">"Home"</string>
+    <string name="set_default_home_app" msgid="5808906607627586381">"Set <xliff:g id="LAUNCHER_NAME">%1$s</xliff:g> as default home app in Settings"</string>
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 3fe69cd..4deacb6 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
     <string name="home_screen" msgid="5629429142036709174">"Home"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 3fe69cd..4deacb6 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets disabled in Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Shortcut isn\'t available"</string>
     <string name="home_screen" msgid="5629429142036709174">"Home"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App info for %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Usage settings for %1$s"</string>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index 69added..fa6d1f1 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -27,6 +27,7 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‏‎‏‎‏‏‎‏‏‎‎‏‏‎‎‎‏‏‏‎‎‏‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‎Widgets disabled in Safe mode‎‏‎‎‏‎"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‎‏‏‎‏‏‏‏‎‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‏‎‏‎‏‎‏‎‎‏‏‏‎‏‏‏‏‎‏‎Shortcut isn\'t available‎‏‎‎‏‎"</string>
     <string name="home_screen" msgid="5629429142036709174">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‏‏‎‏‎‏‎‏‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎Home‎‏‎‎‏‎"</string>
+    <string name="set_default_home_app" msgid="5808906607627586381">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‎‏‏‏‎‎‏‎‎‎‎‏‏‎‎‎‎‏‎‎‎‎‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‎‏‏‎‏‎Set ‎‏‎‎‏‏‎<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>‎‏‎‎‏‏‏‎ as default home app in Settings‎‏‎‎‏‎"</string>
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‎‏‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎‏‏‎‏‏‎‎‏‏‎‎‎‏‏‎‏‎‏‎‏‎‎‏‏‏‏‏‏‎Split screen‎‏‎‎‏‎"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‎‎‎‏‏‏‎‏‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎App info for %1$s‎‏‎‎‏‎"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‎‏‎‏‎‎‎‎‏‎‎‎‎‎‎‎‏‏‏‎‎‎‎‏‏‏‏‎‎‎‏‎‏‏‏‏‎‏‎Usage settings for %1$s‎‏‎‎‏‎"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 75b6979..e734744 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo seguro"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"El acceso directo no está disponible"</string>
     <string name="home_screen" msgid="5629429142036709174">"Pantalla principal"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Información de la app de %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Configuración del uso de %1$s"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 91e05a7..3e0e516 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets inhabilitados en modo Seguro"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Acceso directo no disponible"</string>
     <string name="home_screen" msgid="5629429142036709174">"Inicio"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Información de la aplicación %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Ajustes de uso para %1$s"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 4ad9c9e..bab7da3 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Turvarežiimis on vidinad keelatud"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Otsetee pole saadaval"</string>
     <string name="home_screen" msgid="5629429142036709174">"Avakuva"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Jagatud ekraanikuva"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Rakenduse teave: %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Kasutuse seaded: %1$s"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 8e68ebd..b8017d6 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgetak desgaitu egin dira modu seguruan"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Lasterbideak ez daude erabilgarri"</string>
     <string name="home_screen" msgid="5629429142036709174">"Orri nagusia"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantaila zatitzea"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s aplikazioari buruzko informazioa"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s aplikazioaren erabilera-ezarpenak"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index a191340..b39945b 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"ابزارک‌ها در حالت ایمن غیرفعال هستند"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"میان‌بر دردسترس نیست"</string>
     <string name="home_screen" msgid="5629429142036709174">"صفحه اصلی"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"صفحهٔ دونیمه"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‏اطلاعات برنامه %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"‏تنظیمات مصرف برای %1$s"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 5ff367e..181c89e 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgetit poistettu käytöstä vikasietotilassa"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Pikakuvake ei ole käytettävissä."</string>
     <string name="home_screen" msgid="5629429142036709174">"Etusivu"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Jaettu näyttö"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Sovellustiedot: %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Käyttöasetus tälle: %1$s"</string>
@@ -48,7 +50,7 @@
     <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Kaikki tarvittava"</string>
     <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Uutiset ja aikakauslehdet"</string>
     <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Viihde"</string>
-    <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Sosiaalinen"</string>
+    <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Some"</string>
     <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Sinulle ehdotetut"</string>
     <string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> widgetit oikealla, haku ja vaihtoehdot vasemmalla"</string>
     <string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# widget}other{# widgetiä}}"</string>
@@ -85,7 +87,7 @@
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Poista asennus"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Sovelluksen tiedot"</string>
     <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Asenna yksityisesti"</string>
-    <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Sovelluksen poistaminen"</string>
+    <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Poista sovellus"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"Asenna"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"Älä ehdota sovellusta"</string>
     <string name="pin_prediction" msgid="4196423321649756498">"Kiinnitä sovellus"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index daa4b5f..f3b08cb 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets désactivés en mode sans échec"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Le raccourci n\'est pas disponible"</string>
     <string name="home_screen" msgid="5629429142036709174">"Accueil"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Écran divisé"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Renseignements sur l\'appli pour %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Paramètres d\'utilisation pour %1$s"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 0ea277b..4a7c7fb 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Les widgets sont désactivés en mode sécurisé."</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Raccourci non disponible"</string>
     <string name="home_screen" msgid="5629429142036709174">"Accueil"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Écran partagé"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Infos sur l\'appli pour %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Paramètres d\'utilisation pour %1$s"</string>
@@ -85,7 +87,7 @@
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Désinstaller"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Infos sur l\'appli"</string>
     <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Installer en mode privé"</string>
-    <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Désinstaller l\'application"</string>
+    <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Désinstaller l\'appli"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"Installer"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"Ne pas suggérer d\'appli"</string>
     <string name="pin_prediction" msgid="4196423321649756498">"Épingler la prédiction"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index fc667bc..0430a9a 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Os widgets están desactivados no modo seguro"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"O atallo non está dispoñible"</string>
     <string name="home_screen" msgid="5629429142036709174">"Inicio"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Pantalla dividida"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Información da aplicación para %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Configuración de uso para %1$s"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index ce411e6..3c248db 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"સુરક્ષિત મોડમાં વિજેટ્સ અક્ષમ કર્યા"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"શૉર્ટકટ ઉપલબ્ધ નથી"</string>
     <string name="home_screen" msgid="5629429142036709174">"હોમ સ્ક્રીન"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"સ્ક્રીનને વિભાજિત કરો"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s માટે ઍપ માહિતી"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sના વપરાશ સંબંધિત સેટિંગ"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index b504260..50d4762 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"विजेट सुरक्षित मोड में अक्षम हैं"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"शॉर्टकट उपलब्ध नहीं है"</string>
     <string name="home_screen" msgid="5629429142036709174">"होम स्क्रीन"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रीन"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s के लिए ऐप्लिकेशन की जानकारी"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s के लिए खर्च की सेटिंग"</string>
@@ -49,7 +51,7 @@
     <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"खबरों और पत्रिकाओं वाले ऐप्लिकेशन"</string>
     <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"मनोरंजन से जुड़े ऐप्लिकेशन"</string>
     <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"सोशल मीडिया ऐप्लिकेशन"</string>
-    <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"आपके लिए सुझाए गए ऐप्लिकेशन"</string>
+    <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"आपके लिए सुझाए गए"</string>
     <string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"<xliff:g id="SELECTED_HEADER">%1$s</xliff:g> के विजेट दाईं ओर, खोज का विजेट और अन्य विकल्प बाईं ओर"</string>
     <string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# विजेट}one{# विजेट}other{# विजेट}}"</string>
     <string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# शॉर्टकट}one{# शॉर्टकट}other{# शॉर्टकट}}"</string>
@@ -88,7 +90,7 @@
     <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ऐप्लिकेशन अनइंस्टॉल करें"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"इंस्‍टॉल करें"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"ऐप्लिकेशन का सुझाव न दें"</string>
-    <string name="pin_prediction" msgid="4196423321649756498">"सुझाए गए ऐप्लिकेशन को पिन करें"</string>
+    <string name="pin_prediction" msgid="4196423321649756498">"सुझाए गए ऐप पिन करें"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"शॉर्टकट इंस्‍टॉल करें"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"ऐप को उपयोगकर्ता के हस्‍तक्षेप के बिना शॉर्टकट जोड़ने देती है."</string>
     <string name="permlab_read_settings" msgid="5136500343007704955">"होम स्क्रीन की सेटिंग और शॉर्टकट पढ़ने की अनुमति"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 9d60495..7a1fd94 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgeti su onemogućeni u Sigurnom načinu rada"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Prečac nije dostupan"</string>
     <string name="home_screen" msgid="5629429142036709174">"Početni zaslon"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podijeljeni zaslon"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacije o aplikaciji %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Postavke upotrebe za %1$s"</string>
@@ -192,6 +194,6 @@
     <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privatno, zaključano."</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"Zaključavanje"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Prelazak na privatni prostor"</string>
-    <string name="ps_add_button_label" msgid="8127988716897128773">"Instaliraj"</string>
+    <string name="ps_add_button_label" msgid="8127988716897128773">"Instalirajte"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Instaliranje aplikacija u privatni prostor"</string>
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index bb39c33..3b556da 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"A modulok ki vannak kapcsolva Csökkentett módban"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"A gyorsparancs nem áll rendelkezésre"</string>
     <string name="home_screen" msgid="5629429142036709174">"Kezdőképernyő"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Osztott képernyő"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Alkalmazásinformáció a következőhöz: %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"A(z) %1$s használati beállításai"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 63b935d..491ba05 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Վիջեթներն անջատված են անվտանգ ռեժիմում"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Դյուրանցումն անհասանելի է"</string>
     <string name="home_screen" msgid="5629429142036709174">"Հիմնական էկրան"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Տրոհել էկրանը"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Տեղեկություններ %1$s հավելվածի մասին"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Օգտագործման կարգավորումներ (%1$s)"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index ca30b42..1335385 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widget dinonaktifkan dalam mode Aman"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Pintasan tidak tersedia"</string>
     <string name="home_screen" msgid="5629429142036709174">"Layar utama"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Layar terpisah"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Info aplikasi untuk %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Setelan penggunaan untuk %1$s"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index aadb75e..4865264 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Græjur eru óvirkar í öruggri stillingu"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Flýtileið er ekki tiltæk"</string>
     <string name="home_screen" msgid="5629429142036709174">"Heim"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Skipta skjá"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Upplýsingar um forrit fyrir %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Notkunarstillingar fyrir %1$s"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 36950b8..41b4b60 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widget disabilitati in modalità provvisoria"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"La scorciatoia non è disponibile"</string>
     <string name="home_screen" msgid="5629429142036709174">"Home"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Schermo diviso"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informazioni sull\'app %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Impostazioni di utilizzo per %1$s"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 21581c3..ae71edd 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"ווידג\'טים מושבתים במצב בטוח"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"קיצור הדרך אינו זמין"</string>
     <string name="home_screen" msgid="5629429142036709174">"בית"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"מסך מפוצל"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‏פרטים על האפליקציה %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"‏הגדרות שימוש ב-%1$s"</string>
@@ -83,7 +85,7 @@
     <string name="all_apps_button_work_label" msgid="7270707118948892488">"רשימת אפליקציות עבודה"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"הסרה"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"להסרת התקנה"</string>
-    <string name="app_info_drop_target_label" msgid="692894985365717661">"פרטי אפליקציה"</string>
+    <string name="app_info_drop_target_label" msgid="692894985365717661">"פרטי האפליקציה"</string>
     <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"התקנה במרחב הפרטי"</string>
     <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"הסרת האפליקציה"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"התקנה"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 1f8a93f..c64d335 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"セーフモードではウィジェットは無効です"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ショートカットは使用できません"</string>
     <string name="home_screen" msgid="5629429142036709174">"ホーム"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割画面"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s のアプリ情報"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s の使用設定"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index eac12be..bcced9a 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"უსაფრთხო რეჟიმში ვიჯეტი გამორთულია"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"მალსახმობი მიუწვდომელია"</string>
     <string name="home_screen" msgid="5629429142036709174">"მთავარი გვერდი"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"ეკრანის გაყოფა"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-ის აპის ინფო"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"გამოყენების პარამეტრები %1$s-ისთვის"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 383a507..62bb983 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Қауіпсіз режимде виджеттер өшіріледі"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Таңбаша қолжетімді емес"</string>
     <string name="home_screen" msgid="5629429142036709174">"Негізгі экран"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Экранды бөлу"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s қолданбасы туралы ақпарат"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s пайдалану параметрлері"</string>
@@ -88,7 +90,7 @@
     <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Қолданбаны жою"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"Орнату"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"Қолданба ұсынбау"</string>
-    <string name="pin_prediction" msgid="4196423321649756498">"Болжанған қолданбаны бекіту"</string>
+    <string name="pin_prediction" msgid="4196423321649756498">"Болжамды бекіту"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"таңбаша орнату"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Қолданбаға пайдаланушының қатысуынсыз төте пернелерді қосу мүмкіндігін береді."</string>
     <string name="permlab_read_settings" msgid="5136500343007704955">"негізгі экран параметрлері мен таңбашаларын оқу"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index e319852..b733699 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"បាន​បិទ​ធាតុ​ក្រាហ្វិក​ក្នុង​របៀប​សុវត្ថិភាព"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ផ្លូវកាត់មិនអាចប្រើបានទេ"</string>
     <string name="home_screen" msgid="5629429142036709174">"អេក្រង់ដើម"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"មុខងារ​បំបែកអេក្រង់"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"ព័ត៌មានកម្មវិធី​សម្រាប់ %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"ការកំណត់ការប្រើប្រាស់សម្រាប់ %1$s"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 758d47c..16b39f8 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"ಸುರಕ್ಷಿತ ಮೋಡ್‌ನಲ್ಲಿ ವಿಜೆಟ್‌ಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ಶಾರ್ಟ್‌ಕಟ್ ಲಭ್ಯವಿಲ್ಲ"</string>
     <string name="home_screen" msgid="5629429142036709174">"ಹೋಮ್"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"ಸ್ಪ್ಲಿಟ್ ಸ್ಕ್ರೀನ್"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ಗಾಗಿ ಆ್ಯಪ್ ಮಾಹಿತಿ"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ಗೆ ಸಂಬಂಧಿಸಿದ ಬಳಕೆಯ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
@@ -85,7 +87,7 @@
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"ಆ್ಯಪ್ ಮಾಹಿತಿ"</string>
     <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ಖಾಸಗಿಯಾಗಿ ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
-    <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ಆ್ಯಪ್‌ ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
+    <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ಆ್ಯಪ್‌ ಅನ್ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"ಸ್ಥಾಪಿಸಿ"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"ಆ್ಯಪ್ ಅನ್ನು ಸೂಚಿಸಬೇಡಿ"</string>
     <string name="pin_prediction" msgid="4196423321649756498">"ಮುನ್ನೋಟ ಪಿನ್ ಮಾಡಿ"</string>
@@ -192,6 +194,6 @@
     <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"ಖಾಸಗಿ, ಲಾಕ್ ಮಾಡಲಾಗಿದೆ."</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"ಲಾಕ್ ಮಾಡಿ"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"ಖಾಸಗಿ ಸ್ಪೇಸ್ ಪರಿವರ್ತನೆಯಾಗುತ್ತಿದೆ"</string>
-    <string name="ps_add_button_label" msgid="8127988716897128773">"ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
+    <string name="ps_add_button_label" msgid="8127988716897128773">"ಇನ್‌ಸ್ಟಾಲ್"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"ಆ್ಯಪ್‌ಗಳನ್ನು ಪ್ರೈವೇಟ್ ಸ್ಪೇಸ್‌ನಲ್ಲಿ ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 4d623fa..1d5c58f 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"안전 모드에서 위젯 사용 중지됨"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"바로가기를 사용할 수 없음"</string>
     <string name="home_screen" msgid="5629429142036709174">"홈"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"화면 분할"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 앱 정보"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s의 사용량 설정"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index 98d1e73..6f01db3 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Виджеттер Коопсуз режимде өчүрүлгөн"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Кыска жол жок"</string>
     <string name="home_screen" msgid="5629429142036709174">"Башкы экран"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Экранды бөлүү"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s колдонмосу жөнүндө маалымат"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s колдонмосун пайдалануу параметрлери"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index e0bcfe0..7ec103b 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"​ວິດ​ເຈັດ​ຖືກ​ປິດ​ໃນ Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ບໍ່ສາມາດໃຊ້ທາງລັດໄດ້"</string>
     <string name="home_screen" msgid="5629429142036709174">"ໂຮມສະກຣີນ"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"ແບ່ງໜ້າຈໍ"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"ຂໍ້ມູນແອັບສຳລັບ %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"ການຕັ້ງຄ່າການນຳໃຊ້ສຳລັບ %1$s"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 6a2b8d7..1112474 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Valdikliai išjungti Saugiame režime"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Sparčiojo klavišo negalima naudoti"</string>
     <string name="home_screen" msgid="5629429142036709174">"Pagrindinis"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Išskaidyto ekrano režimas"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Programos „%1$s“ informacija"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"„%1$s“ naudojimo nustatymai"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index cae6b6e..4f1a92c 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Logrīki atspējoti drošajā režīmā"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Saīsne nav pieejama."</string>
     <string name="home_screen" msgid="5629429142036709174">"Sākums"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Sadalīt ekrānu"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s: informācija par lietotni"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Lietojuma iestatījumi: %1$s"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 46d8600..d984ca0 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Додатоците се оневозможени во безбеден режим"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Кратенката не е достапна"</string>
     <string name="home_screen" msgid="5629429142036709174">"Почетен екран"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Поделен екран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Податоци за апликација за %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Поставки за користење за %1$s"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index eec5f74..ea8849e 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"സുരക്ഷിത മോഡിൽ വിജറ്റുകൾ പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"കുറുക്കുവഴി ലഭ്യമല്ല"</string>
     <string name="home_screen" msgid="5629429142036709174">"ഹോം"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"സ്‌ക്രീൻ വിഭജന മോഡ്"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s എന്നതിന്റെ ആപ്പ് വിവരങ്ങൾ"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s എന്നതിനുള്ള ഉപയോഗ ക്രമീകരണം"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 396589c..8554717 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Safe горимд виджетүүдийг идэвхгүйжүүлсэн"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Товчлол алга"</string>
     <string name="home_screen" msgid="5629429142036709174">"Нүүр"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Дэлгэцийг хуваах"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s-н аппын мэдээлэл"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s-н ашиглалтын тохиргоо"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index 972c9f2..dcdf25c 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"विजेट सुरक्षित मोडमध्ये अक्षम झाले"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"शॉर्टकट उपलब्ध नाही"</string>
     <string name="home_screen" msgid="5629429142036709174">"होम"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रीन"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s साठी ॲपशी संबंधित माहिती"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s साठी वापरासंबंधित सेटिंग्ज"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index c25e3df..22ee2cb 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widget dilumpuhkan dalam mod Selamat"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Pintasan tidak tersedia"</string>
     <string name="home_screen" msgid="5629429142036709174">"Rumah"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Skrin pisah"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Maklumat apl untuk %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Tetapan penggunaan sebanyak %1$s"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 700278a..bca1c6e 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"လုံခြုံရေး မုဒ်ထဲမှာ ဝီဂျက်များကို ပိတ်ထား"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ဖြတ်လမ်း မရနိုင်ပါ"</string>
     <string name="home_screen" msgid="5629429142036709174">"ပင်မစာမျက်နှာ"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"မျက်နှာပြင် ခွဲ၍ပြသခြင်း"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s အတွက် အက်ပ်အချက်အလက်"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s အတွက် အသုံးပြုမှုဆက်တင်များ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index c2f99aa..1440443 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Moduler er deaktivert i sikker modus"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Snarveien er ikke tilgjengelig"</string>
     <string name="home_screen" msgid="5629429142036709174">"Startskjerm"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Delt skjerm"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinformasjon for %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Bruksinnstillinger for %1$s"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 5e93f89..52613cb 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"सुरक्षित मोडमा विगेटहरू अक्षम गरियो"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"सर्टकट उपलब्ध छैन"</string>
     <string name="home_screen" msgid="5629429142036709174">"होम"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"स्प्लिट स्क्रिन"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s का हकमा एपसम्बन्धी जानकारी"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s को प्रयोगसम्बन्धी सेटिङ"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index d7d4e7d..90dcd46 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets uitgezet in veilige modus"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Snelkoppeling is niet beschikbaar"</string>
     <string name="home_screen" msgid="5629429142036709174">"Startscherm"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Gesplitst scherm"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"App-info voor %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Gebruiksinstellingen voor %1$s"</string>
@@ -88,7 +90,7 @@
     <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"App verwijderen"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"Installeren"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"Geen app voorstellen"</string>
-    <string name="pin_prediction" msgid="4196423321649756498">"Vastzetvoorspelling"</string>
+    <string name="pin_prediction" msgid="4196423321649756498">"Voorspelling vastzetten"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"Snelle links instellen"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"Een app toestaan snelkoppelingen toe te voegen zonder tussenkomst van de gebruiker."</string>
     <string name="permlab_read_settings" msgid="5136500343007704955">"instellingen en snelkoppelingen op startscherm lezen"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 3c240ae..6d2f87e 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"ନିରାପଦ ମୋଡରେ ୱିଜେଟ୍‌ ଅକ୍ଷମ କରାଗଲା"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ଶର୍ଟକଟ୍‌ ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="home_screen" msgid="5629429142036709174">"ହୋମ"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"ସ୍କ୍ରିନ‌କୁ ସ୍ପ୍ଲିଟ କରନ୍ତୁ"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ପାଇଁ ଆପ ସୂଚନା"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ପାଇଁ ବ୍ୟବହାର ସେଟିଂସ"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 0df0c3f..b287b2f 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"ਵਿਜੇਟ ਸੁਰੱਖਿਅਤ ਮੋਡ ਵਿੱਚ ਅਸਮਰਥਿਤ"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ਸ਼ਾਰਟਕੱਟ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
     <string name="home_screen" msgid="5629429142036709174">"ਮੁੱਖ ਪੰਨਾ"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"ਸਪਲਿਟ ਸਕ੍ਰੀਨ"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ਲਈ ਐਪ ਜਾਣਕਾਰੀ"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ਲਈ ਵਰਤੋਂ ਸੈਟਿੰਗਾਂ"</string>
@@ -84,7 +86,7 @@
     <string name="remove_drop_target_label" msgid="7812859488053230776">"ਹਟਾਓ"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"ਅਣਸਥਾਪਤ ਕਰੋ"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"ਐਪ ਜਾਣਕਾਰੀ"</string>
-    <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ਨਿੱਜੀ ਵਜੋਂ ਸਥਾਪਤ ਕਰੋ"</string>
+    <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"ਪ੍ਰਾਈਵੇਟ ਵਜੋਂ ਸਥਾਪਤ ਕਰੋ"</string>
     <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ਐਪ ਅਣਸਥਾਪਤ ਕਰੋ"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"ਸਥਾਪਤ ਕਰੋ"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"ਐਪ ਦਾ ਸੁਝਾਅ ਨਾ ਦਿਓ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index a8e79e3..685b7d9 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widżety są wyłączone w trybie bezpiecznym"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Skrót nie jest dostępny"</string>
     <string name="home_screen" msgid="5629429142036709174">"Ekran główny"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Podziel ekran"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacje o aplikacji: %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s – ustawienia użycia"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 6e4a8e8..14a3cf5 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets desativados no Modo de segurança"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"O atalho não está disponível"</string>
     <string name="home_screen" msgid="5629429142036709174">"Página inicial"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ecrã dividido"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informações da app para %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Definições de utilização para %1$s"</string>
@@ -190,7 +192,7 @@
     <string name="ps_container_settings" msgid="6059734123353320479">"Definições do espaço privado"</string>
     <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privado, desbloqueado."</string>
     <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privado, bloqueado."</string>
-    <string name="ps_container_lock_title" msgid="2640257399982364682">"Bloquear"</string>
+    <string name="ps_container_lock_title" msgid="2640257399982364682">"Bloqueio"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Transição do espaço privado"</string>
     <string name="ps_add_button_label" msgid="8127988716897128773">"Instalar"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Instale apps no espaço privado"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index a049a80..4c4b13b 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets desativados no modo de segurança"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"O atalho não está disponível"</string>
     <string name="home_screen" msgid="5629429142036709174">"Início"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Tela dividida"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informações do app %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Configurações de uso de %1$s"</string>
@@ -184,13 +186,13 @@
     <string name="work_apps_enable_btn_text" msgid="1736198302467317371">"Ativar"</string>
     <string name="developer_options_filter_hint" msgid="5896817443635989056">"Filtrar"</string>
     <string name="remote_action_failed" msgid="1383965239183576790">"Falha: <xliff:g id="WHAT">%1$s</xliff:g>"</string>
-    <string name="private_space_label" msgid="2359721649407947001">"Espaço particular"</string>
+    <string name="private_space_label" msgid="2359721649407947001">"Espaço privado"</string>
     <string name="private_space_secondary_label" msgid="9203933341714508907">"Toque para configurar ou abrir"</string>
     <string name="ps_container_title" msgid="4391796149519594205">"Particular"</string>
     <string name="ps_container_settings" msgid="6059734123353320479">"Configurações do Espaço particular"</string>
     <string name="ps_container_unlock_button_content_description" msgid="9181551784092204234">"Privada, desbloqueado."</string>
     <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"Privada, bloqueado."</string>
-    <string name="ps_container_lock_title" msgid="2640257399982364682">"Bloquear"</string>
+    <string name="ps_container_lock_title" msgid="2640257399982364682">"Bloqueio"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"Espaço particular em transição"</string>
     <string name="ps_add_button_label" msgid="8127988716897128773">"Instalar"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"Instalar apps no espaço privado"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index bbade3c..256fa60 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgeturile sunt dezactivate în modul de siguranță"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Comanda rapidă nu este disponibilă"</string>
     <string name="home_screen" msgid="5629429142036709174">"Pagina de pornire"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ecran împărțit"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informații despre aplicație pentru %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Setări de utilizare pentru %1$s"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 995052e..402143d 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Виджеты отключены в безопасном режиме"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Ярлык недоступен"</string>
     <string name="home_screen" msgid="5629429142036709174">"Главный экран"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Разделить экран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Сведения о приложении \"%1$s\""</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Настройки использования приложения \"%1$s\""</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 9140854..455f18b 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"සුරක්ෂිත ආකාරය තුළ විජටය අබල කරන ලදි"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"කෙටි මග ලබා ගත නොහැකිය"</string>
     <string name="home_screen" msgid="5629429142036709174">"මුල් පිටුව"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"බෙදුම් තිරය"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s සඳහා යෙදුම් තතු"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s සඳහා භාවිත සැකසීම්"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 1ba5771..7771cf7 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Miniaplikácie sú v núdzovom režime zakázané"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Skratky nie sú k dispozícii"</string>
     <string name="home_screen" msgid="5629429142036709174">"Domov"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Rozdeliť obrazovku"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informácie o aplikácii pre %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Nastavenia používania pre %1$s"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index cea6e18..2e75b17 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Pripomočki so onemogočeni v varnem načinu"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Bližnjica ni na voljo"</string>
     <string name="home_screen" msgid="5629429142036709174">"Začetni zaslon"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Razdeljen zaslon"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Podatki o aplikaciji za: %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Nastavitve uporabe za »%1$s«"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index f328557..a00f11a 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Miniaplikacionet janë të çaktivizuara në modalitetin e sigurt"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Shkurtorja nuk është e disponueshme"</string>
     <string name="home_screen" msgid="5629429142036709174">"Ekrani bazë"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekrani i ndarë"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Informacioni i aplikacionit për %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Cilësimet e përdorimit për \"%1$s\""</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 6fdeb41..97d3ff4 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Виџети су онемогућени у Безбедном режиму"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Пречица није доступна"</string>
     <string name="home_screen" msgid="5629429142036709174">"Почетни екран"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Подељени екран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Информације о апликацији за: %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Подешавања потрошње за %1$s"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 1866f95..8a8524e 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Widgets är inaktiverade i felsäkert läge"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Genvägen är inte tillgänglig"</string>
     <string name="home_screen" msgid="5629429142036709174">"Startskärm"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Delad skärm"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Appinformation för %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Användningsinställningar för %1$s"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 5c02beb..64f6296 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Wijeti zimezimwa katika hali ya Usalama"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Hakuna njia ya mkato"</string>
     <string name="home_screen" msgid="5629429142036709174">"Skrini ya kwanza"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Gawa skrini"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Maelezo ya programu ya %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Mipangilio ya matumizi ya %1$s"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index ebc55ca..189f4ce 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"பாதுகாப்புப் பயன்முறையில் விட்ஜெட்கள் முடக்கப்பட்டுள்ளன"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ஷார்ட்கட் இல்லை"</string>
     <string name="home_screen" msgid="5629429142036709174">"முகப்பு"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"திரைப் பிரிப்பு"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$sக்கான ஆப்ஸ் தகவல்கள்"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sக்கான உபயோக அமைப்புகள்"</string>
@@ -88,7 +90,7 @@
     <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"ஆப்ஸை நிறுவல் நீக்கு"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"நிறுவு"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"பரிந்துரைக்காதே"</string>
-    <string name="pin_prediction" msgid="4196423321649756498">"கணிக்கப்பட்ட ஆப்ஸைப் பின் செய்தல்"</string>
+    <string name="pin_prediction" msgid="4196423321649756498">"கணிக்கப்பட்டதைப் பின் செய்"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"குறுக்குவழிகளை நிறுவுதல்"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"பயனரின் அனுமதி இல்லாமல் குறுக்குவழிகளைச் சேர்க்கப் ஆப்ஸை அனுமதிக்கிறது."</string>
     <string name="permlab_read_settings" msgid="5136500343007704955">"முகப்புத் திரையின் அமைப்புகளையும் ஷார்ட்கட்களையும் படித்தல்"</string>
@@ -192,6 +194,6 @@
     <string name="ps_container_lock_button_content_description" msgid="5961993384382649530">"தனிப்பட்டது, லாக் செய்யப்பட்டுள்ளது."</string>
     <string name="ps_container_lock_title" msgid="2640257399982364682">"பூட்டு"</string>
     <string name="ps_container_transition" msgid="8667331812048014412">"தனிப்பட்ட சேமிப்பிடத்திற்கு மாற்றுகிறது"</string>
-    <string name="ps_add_button_label" msgid="8127988716897128773">"நிறுவுதல்"</string>
+    <string name="ps_add_button_label" msgid="8127988716897128773">"நிறுவுக"</string>
     <string name="ps_add_button_content_description" msgid="3254274107740952556">"தனிப்பட்ட சேமிப்பிடத்தில் ஆப்ஸை நிறுவும்"</string>
 </resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index e1c4668..d789baa 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"సురక్షిత మోడ్‌లో విడ్జెట్‌లు నిలిపివేయబడ్డాయి"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"షార్ట్‌కట్ అందుబాటులో లేదు"</string>
     <string name="home_screen" msgid="5629429142036709174">"మొదటి ట్యాబ్"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"స్ప్లిట్ స్క్రీన్"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s కోసం యాప్ సమాచారం"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$sకు సంబంధించిన వినియోగ సెట్టింగ్‌లు"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 4288c24..f89638d 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"มีการปิดใช้งานวิดเจ็ตในเซฟโหมด"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"ทางลัดไม่พร้อมใช้งาน"</string>
     <string name="home_screen" msgid="5629429142036709174">"หน้าแรก"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"แยกหน้าจอ"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"ข้อมูลแอปสำหรับ %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"การตั้งค่าการใช้งานสำหรับ %1$s"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 305f532..8754a12 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Naka-disable ang mga widget sa Safe mode"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Hindi available ang shortcut"</string>
     <string name="home_screen" msgid="5629429142036709174">"Home"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Split screen"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Impormasyon ng app para sa %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Mga setting ng paggamit para sa %1$s"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 0681e3d..761ce56 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Güvenli modda widget\'lar devre dışı"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Kısayol kullanılamıyor"</string>
     <string name="home_screen" msgid="5629429142036709174">"Ana ekran"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Bölünmüş ekran"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s uygulama bilgileri"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s ile ilgili kullanım ayarları"</string>
@@ -85,7 +87,7 @@
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Kaldır"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"Uygulama bilgileri"</string>
     <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"Özel olarak yükle"</string>
-    <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Uygulamanın yüklemesini kaldır"</string>
+    <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"Uygulamayı kaldır"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"Yükle"</string>
     <string name="dismiss_prediction_label" msgid="3357562989568808658">"Uygulamayı önerme"</string>
     <string name="pin_prediction" msgid="4196423321649756498">"Tahmini Sabitle"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index c1d6b2f..2c85d5a 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"У безпечному режимі віджети вимкнено"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Ярлик недоступний"</string>
     <string name="home_screen" msgid="5629429142036709174">"Головний екран"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Розділити екран"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Інформація про додаток для %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Параметри використання (%1$s)"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 6c1a88e..09f4304 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"ویجیٹس کو محفوظ وضع میں غیر فعال کر دیا گیا"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"شارٹ کٹ دستیاب نہیں ہے"</string>
     <string name="home_screen" msgid="5629429142036709174">"ہوم"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"اسپلٹ اسکرین"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‏%1$s کے لیے ایپ کی معلومات"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"‏%1$s کیلئے استعمال کی ترتیبات"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 18431cc..d8dea69 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Xavfsiz rejimda vidjetlar o‘chirib qo‘yilgan"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Tezkor tugmadan foydalanib bo‘lmaydi"</string>
     <string name="home_screen" msgid="5629429142036709174">"Bosh ekran"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Ekranni ikkiga ajratish"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s ilovasi axboroti"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s uchun sarf sozlamalari"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index b5fb1d7..172c995 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Tiện ích bị vô hiệu hóa ở chế độ an toàn"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Lối tắt không khả dụng"</string>
     <string name="home_screen" msgid="5629429142036709174">"Màn hình chính"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Chia đôi màn hình"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Thông tin ứng dụng cho %1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Chế độ cài đặt mức sử dụng %1$s"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index d51f8fd..9c658dc 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"安全模式下不允许使用微件"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"无法使用快捷方式"</string>
     <string name="home_screen" msgid="5629429142036709174">"主屏幕"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"分屏"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 的应用信息"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"%1$s的使用设置"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 90ff515..af32638 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"在安全模式中無法使用小工具"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"沒有可用的捷徑"</string>
     <string name="home_screen" msgid="5629429142036709174">"主畫面"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割螢幕"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"%1$s 的應用程式資料"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"「%1$s」的用量設定"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 023a526..54a1c5d 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"在安全模式下無法使用小工具"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"目前無法使用捷徑"</string>
     <string name="home_screen" msgid="5629429142036709174">"主畫面"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"分割畫面"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"「%1$s」的應用程式資訊"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"「%1$s」的用量設定"</string>
@@ -87,7 +89,7 @@
     <string name="install_private_system_shortcut_label" msgid="1616889277073184841">"安裝在私人空間中"</string>
     <string name="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"解除安裝應用程式"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"安裝"</string>
-    <string name="dismiss_prediction_label" msgid="3357562989568808658">"不要提供應用程式建議"</string>
+    <string name="dismiss_prediction_label" msgid="3357562989568808658">"不要建議此應用程式"</string>
     <string name="pin_prediction" msgid="4196423321649756498">"固定預測的應用程式"</string>
     <string name="permlab_install_shortcut" msgid="5632423390354674437">"安裝捷徑"</string>
     <string name="permdesc_install_shortcut" msgid="923466509822011139">"允許應用程式自動新增捷徑。"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index a936725..9752e18 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -27,6 +27,8 @@
     <string name="safemode_widget_error" msgid="4863470563535682004">"Amawijethi akhutshaziwe kwimodi yokuphepha"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"Isinqamuleli asitholakali"</string>
     <string name="home_screen" msgid="5629429142036709174">"Ikhaya"</string>
+    <!-- no translation found for set_default_home_app (5808906607627586381) -->
+    <skip />
     <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Hlukanisa isikrini"</string>
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"Ulwazi lwe-App ye-%1$s"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"Amasethingi okusetshenziswa ka-%1$s"</string>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index aa83c01..05724e2 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -448,6 +448,9 @@
     <!-- Size of the maximum radius for the enforced rounded rectangles. -->
     <dimen name="enforced_rounded_corner_max_radius">16dp</dimen>
 
+    <!-- Size of the radius for the rounded corners of Persistent Taskbar. -->
+    <dimen name="persistent_taskbar_corner_radius">16dp</dimen>
+
     <!-- Base Swipe Detector, speed in dp/s -->
     <dimen name="base_swift_detector_fling_release_velocity">1dp</dimen>
 
diff --git a/res/values/strings.xml b/res/values/strings.xml
index a9cca6d..d33adc4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -36,6 +36,8 @@
     <string name="shortcut_not_available">Shortcut isn\'t available</string>
     <!-- User visible name for the launcher/home screen. [CHAR_LIMIT=30] -->
     <string name="home_screen">Home</string>
+    <!-- Description for setting the current launcher as the default home app. [CHAR_LIMIT=none]-->
+    <string name="set_default_home_app">Set <xliff:g id="launcher_name" example="Launcher3">%1$s</xliff:g> as default home app in Settings</string>
 
     <!-- Options for recent tasks -->
     <!-- Title for an option to enter split screen mode for a given app -->
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 7d09164..83427a0 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -430,10 +430,21 @@
         setDownloadStateContentDescription(info, info.getProgressLevel());
     }
 
+    /**
+     * Directly set the icon and label.
+     */
+    @UiThread
+    public void applyIconAndLabel(Drawable icon, CharSequence label) {
+        applyCompoundDrawables(icon);
+        setText(label);
+        setContentDescription(label);
+    }
+
     /** Updates whether the app this view represents is currently running. */
     @UiThread
     public void updateRunningState(RunningAppState runningAppState) {
         mRunningAppState = runningAppState;
+        invalidate();
     }
 
     protected void setItemInfo(ItemInfoWithIcon itemInfo) {
@@ -1291,13 +1302,4 @@
     public boolean canShowLongPressPopup() {
         return getTag() instanceof ItemInfo && ShortcutUtil.supportsShortcuts((ItemInfo) getTag());
     }
-
-    /** Returns the package name of the app this icon represents. */
-    public String getTargetPackageName() {
-        Object tag = getTag();
-        if (tag instanceof ItemInfo itemInfo) {
-            return itemInfo.getTargetPackage();
-        }
-        return null;
-    }
 }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 0daabb1..00db3a3 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -53,7 +53,6 @@
 
 import com.android.launcher3.CellLayout.ContainerType;
 import com.android.launcher3.DevicePaddings.DevicePadding;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.model.data.ItemInfo;
@@ -714,7 +713,7 @@
         overviewTaskThumbnailTopMarginPx =
                 enableOverviewIconMenu() ? 0 : overviewTaskIconSizePx + overviewTaskMarginPx;
         // Don't add margin with floating search bar to minimize risk of overlapping.
-        overviewActionsTopMarginPx = FeatureFlags.ENABLE_FLOATING_SEARCH_BAR.get() ? 0
+        overviewActionsTopMarginPx = Flags.floatingSearchBar() ? 0
                 : res.getDimensionPixelSize(R.dimen.overview_actions_top_margin);
         overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing);
         overviewActionsButtonSpacing = res.getDimensionPixelSize(
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index 8546454..f775673 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -97,10 +97,9 @@
         if (bubbleBarEnabled) {
             float adjustedBorderSpace = dp.getHotseatAdjustedBorderSpaceForBubbleBar(getContext());
             if (hasBubbles && Float.compare(adjustedBorderSpace, 0f) != 0) {
-                getShortcutsAndWidgets().setTranslationProvider(child -> {
-                    int index = getShortcutsAndWidgets().indexOfChild(child);
+                getShortcutsAndWidgets().setTranslationProvider(cellX -> {
                     float borderSpaceDelta = adjustedBorderSpace - dp.hotseatBorderSpace;
-                    return dp.iconSizePx + index * borderSpaceDelta;
+                    return dp.iconSizePx + cellX * borderSpaceDelta;
                 });
                 if (mQsb instanceof HorizontalInsettableView) {
                     HorizontalInsettableView insettableQsb = (HorizontalInsettableView) mQsb;
@@ -147,10 +146,7 @@
 
         // update the translation provider for future layout passes of hotseat icons.
         if (isBubbleBarVisible) {
-            icons.setTranslationProvider(child -> {
-                int index = icons.indexOfChild(child);
-                return dp.iconSizePx + index * borderSpaceDelta;
-            });
+            icons.setTranslationProvider(cellX -> dp.iconSizePx + cellX * borderSpaceDelta);
         } else {
             icons.setTranslationProvider(null);
         }
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 2b30dc4..d905801 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -38,6 +38,7 @@
 import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_BIND_PENDING_APPWIDGET;
 import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_CREATE_APPWIDGET;
 import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_CREATE_SHORTCUT;
+import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_HOME_ROLE;
 import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_PICK_APPWIDGET;
 import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET;
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
@@ -151,6 +152,7 @@
 import android.view.WindowManager.LayoutParams;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.OvershootInterpolator;
+import android.widget.Toast;
 import android.window.BackEvent;
 import android.window.OnBackAnimationCallback;
 
@@ -164,6 +166,7 @@
 import androidx.core.os.BuildCompat;
 import androidx.window.embedding.RuleController;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.ActivityAllAppsContainerView;
@@ -309,7 +312,7 @@
 
     private static boolean sIsNewProcess = true;
 
-    private StateManager<LauncherState> mStateManager;
+    private StateManager<LauncherState, Launcher> mStateManager;
 
     private static final int ON_ACTIVITY_RESULT_ANIMATION_DELAY = 500;
 
@@ -891,6 +894,17 @@
         }
         mPendingActivityResult = null;
 
+        if (requestCode == REQUEST_HOME_ROLE) {
+            if (resultCode != RESULT_OK) {
+                Toast.makeText(
+                        this,
+                        this.getString(R.string.set_default_home_app,
+                                this.getString(R.string.derived_app_name)),
+                        Toast.LENGTH_LONG).show();
+            }
+            return;
+        }
+
         // Reset the startActivity waiting flag
         final PendingRequestArgs requestArgs = mPendingRequestArgs;
         setWaitingForResult(null);
@@ -2761,7 +2775,7 @@
     }
 
     @Override
-    protected void collectStateHandlers(List<StateHandler> out) {
+     public void collectStateHandlers(List<StateHandler<LauncherState>> out) {
         out.add(getAllAppsController());
         out.add(getWorkspace());
     }
@@ -2783,8 +2797,11 @@
     }
 
     private void updateDisallowBack() {
-        if (BuildCompat.isAtLeastV() && Flags.enableDesktopWindowingMode()) {
-            // TODO(b/330183377) disable back in launcher when when we productionize
+        if (BuildCompat.isAtLeastV()
+                && Flags.enableDesktopWindowingMode()
+                && !Flags.enableDesktopWindowingWallpaperActivity()
+                && mDeviceProfile.isTablet) {
+            // TODO(b/333533253): Clean up after desktop wallpaper activity flag is rolled out
             return;
         }
         LauncherRootView rv = getRootView();
@@ -2981,7 +2998,7 @@
     }
 
     @Override
-    public StateManager<LauncherState> getStateManager() {
+    public StateManager<LauncherState, Launcher> getStateManager() {
         return mStateManager;
     }
 
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 3b8ff62..239967d 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -116,13 +116,13 @@
 
         SimpleBroadcastReceiver modelChangeReceiver =
                 new SimpleBroadcastReceiver(mModel::onBroadcastIntent);
-        modelChangeReceiver.register(mContext, Intent.ACTION_LOCALE_CHANGED,
+        modelChangeReceiver.registerAsync(mContext, Intent.ACTION_LOCALE_CHANGED,
                 ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
         if (BuildConfig.IS_STUDIO_BUILD) {
             mContext.registerReceiver(modelChangeReceiver, new IntentFilter(ACTION_FORCE_ROLOAD),
                     RECEIVER_EXPORTED);
         }
-        mOnTerminateCallback.add(() -> mContext.unregisterReceiver(modelChangeReceiver));
+        mOnTerminateCallback.add(() -> modelChangeReceiver.unregisterReceiverSafelyAsync(mContext));
 
         SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext)
                 .addUserEventListener(mModel::onUserEvent);
diff --git a/src/com/android/launcher3/LauncherConstants.java b/src/com/android/launcher3/LauncherConstants.java
index 1abfeb9..445fb41 100644
--- a/src/com/android/launcher3/LauncherConstants.java
+++ b/src/com/android/launcher3/LauncherConstants.java
@@ -41,6 +41,7 @@
 
         public static final int REQUEST_BIND_PENDING_APPWIDGET = 12;
         public static final int REQUEST_RECONFIGURE_APPWIDGET = 13;
+        public static final int REQUEST_HOME_ROLE = 14;
         static final int REQUEST_CREATE_SHORTCUT = 1;
         static final int REQUEST_CREATE_APPWIDGET = 5;
         static final int REQUEST_PICK_APPWIDGET = 9;
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 47a7115..ca1b2a9 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -480,7 +480,7 @@
                 }
             }
 
-            if (!removedIds.isEmpty()) {
+            if (!removedIds.isEmpty() && !isAppArchived) {
                 taskController.deleteAndBindComponentsRemoved(
                         ItemInfoMatcher.ofItemIds(removedIds),
                         "removed because install session failed");
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index d2d56f2..102189b 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -440,7 +440,7 @@
      */
     public void onBackInvoked(Launcher launcher) {
         if (this != NORMAL) {
-            StateManager<LauncherState> lsm = launcher.getStateManager();
+            StateManager<LauncherState, Launcher> lsm = launcher.getStateManager();
             LauncherState lastState = lsm.getLastState();
             lsm.goToState(lastState, forEndCallback(this::onBackAnimationCompleted));
         }
@@ -461,7 +461,7 @@
      */
     public void onBackProgressed(
             Launcher launcher, @FloatRange(from = 0.0, to = 1.0) float backProgress) {
-        StateManager<LauncherState> lsm = launcher.getStateManager();
+        StateManager<LauncherState, Launcher> lsm = launcher.getStateManager();
         LauncherState toState = lsm.getLastState();
         lsm.onBackProgressed(toState, backProgress);
     }
@@ -471,7 +471,7 @@
      * predictive back gesture.
      */
     public void onBackCancelled(Launcher launcher) {
-        StateManager<LauncherState> lsm = launcher.getStateManager();
+        StateManager<LauncherState, Launcher> lsm = launcher.getStateManager();
         LauncherState toState = lsm.getLastState();
         lsm.onBackCancelled(toState);
     }
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 7484b64..d2c3c78 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -245,7 +245,7 @@
         }
         child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
         if (mTranslationProvider != null) {
-            final float tx = mTranslationProvider.getTranslationX(child);
+            final float tx = mTranslationProvider.getTranslationX(lp.getCellX());
             if (child instanceof Reorderable) {
                 ((Reorderable) child).getTranslateDelegate()
                         .getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM)
@@ -330,6 +330,6 @@
 
     /** Provides translation values to apply when laying out child views. */
     interface TranslationProvider {
-        float getTranslationX(View child);
+        float getTranslationX(int cellX);
     }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 584d089..e601a3e 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -526,7 +526,7 @@
         }
 
         updateChildrenLayersEnabled();
-        StateManager<LauncherState> stateManager = mLauncher.getStateManager();
+        StateManager<LauncherState, Launcher> stateManager = mLauncher.getStateManager();
         stateManager.addStateListener(new StateManager.StateListener<LauncherState>() {
             @Override
             public void onStateTransitionComplete(LauncherState finalState) {
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index ba34f59..2a47222 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -305,9 +305,7 @@
 
     @Override
     public int getScrollBarTop() {
-        return ActivityContext.lookupContext(getContext()).getAppsView().isSearchSupported()
-                ? getResources().getDimensionPixelOffset(R.dimen.all_apps_header_top_padding)
-                : 0;
+        return getResources().getDimensionPixelOffset(R.dimen.all_apps_header_top_padding);
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index 63a168e..1e0f289 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -56,7 +56,7 @@
             return false;
         }
         Launcher launcher = mActivityContext;
-        StateManager<LauncherState> manager = launcher.getStateManager();
+        StateManager<LauncherState, Launcher> manager = launcher.getStateManager();
         if (manager.isInTransition() && manager.getTargetState() != null) {
             return manager.getTargetState().shouldFloatingSearchBarUsePillWhenUnfocused(launcher);
         }
@@ -70,7 +70,7 @@
             return super.getFloatingSearchBarRestingMarginBottom();
         }
         Launcher launcher = mActivityContext;
-        StateManager<LauncherState> stateManager = launcher.getStateManager();
+        StateManager<LauncherState, Launcher> stateManager = launcher.getStateManager();
 
         // We want to rest at the current state's resting position, unless we are in transition and
         // the target state's resting position is higher (that way if we are closing the keyboard,
@@ -95,7 +95,7 @@
             return super.getFloatingSearchBarRestingMarginStart();
         }
 
-        StateManager<LauncherState> stateManager = mActivityContext.getStateManager();
+        StateManager<LauncherState, Launcher> stateManager = mActivityContext.getStateManager();
 
         // Special case to not expand the search bar when exiting All Apps on phones.
         if (stateManager.getCurrentStableState() == LauncherState.ALL_APPS
@@ -117,7 +117,7 @@
             return super.getFloatingSearchBarRestingMarginEnd();
         }
 
-        StateManager<LauncherState> stateManager = mActivityContext.getStateManager();
+        StateManager<LauncherState, Launcher> stateManager = mActivityContext.getStateManager();
 
         // Special case to not expand the search bar when exiting All Apps on phones.
         if (stateManager.getCurrentStableState() == LauncherState.ALL_APPS
diff --git a/src/com/android/launcher3/allapps/PrivateProfileManager.java b/src/com/android/launcher3/allapps/PrivateProfileManager.java
index 5cacf60..6f021ea 100644
--- a/src/com/android/launcher3/allapps/PrivateProfileManager.java
+++ b/src/com/android/launcher3/allapps/PrivateProfileManager.java
@@ -281,11 +281,29 @@
 
     @Override
     public void setQuietMode(boolean enable) {
-        super.setQuietMode(enable);
+        UI_HELPER_EXECUTOR.post(() ->
+                mUserCache.getUserProfiles()
+                        .stream()
+                        .filter(getUserMatcher())
+                        .findFirst()
+                        .ifPresent(userHandle -> setQuietModeSafely(enable, userHandle)));
         mReadyToAnimate = true;
     }
 
     /**
+     * Sets Quiet Mode for Private Profile.
+     * If {@link SecurityException} is thrown, prompts the user to set this launcher as HOME app.
+     */
+    private void setQuietModeSafely(boolean enable, UserHandle userHandle) {
+        try {
+            mUserManager.requestQuietModeEnabled(enable, userHandle);
+        } catch (SecurityException ex) {
+            ApiWrapper.INSTANCE.get(mAllApps.mActivityContext)
+                    .assignDefaultHomeRole(mAllApps.mActivityContext);
+        }
+    }
+
+    /**
      * Expand the private space after the app list has been added and updated from
      * {@link AlphabeticalAppsList#onAppsUpdated()}
      */
diff --git a/src/com/android/launcher3/allapps/UserProfileManager.java b/src/com/android/launcher3/allapps/UserProfileManager.java
index eb74d20..93b6b29 100644
--- a/src/com/android/launcher3/allapps/UserProfileManager.java
+++ b/src/com/android/launcher3/allapps/UserProfileManager.java
@@ -55,12 +55,11 @@
     public @interface UserProfileState { }
 
     protected final StatsLogManager mStatsLogManager;
+    protected final UserManager mUserManager;
+    protected final UserCache mUserCache;
+
     @UserProfileState
     private int mCurrentState;
-
-    private final UserManager mUserManager;
-    private final UserCache mUserCache;
-
     protected UserProfileManager(UserManager userManager,
             StatsLogManager statsLogManager,
             UserCache userCache) {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 4b908bf..d0596fa 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.config.FeatureFlags.BooleanFlag.DISABLED;
 import static com.android.launcher3.config.FeatureFlags.BooleanFlag.ENABLED;
 import static com.android.wm.shell.Flags.enableTaskbarNavbarUnification;
+import static com.android.wm.shell.Flags.enableTaskbarOnPhones;
 
 import android.content.res.Resources;
 
@@ -84,11 +85,6 @@
                     + "data preparation for loading the home screen");
 
     // TODO(Block 4): Cleanup flags
-    public static final BooleanFlag ENABLE_FLOATING_SEARCH_BAR =
-            getReleaseFlag(268388460, "ENABLE_FLOATING_SEARCH_BAR", DISABLED,
-                    "Allow search bar to persist and animate across states, and attach to"
-                            + " the keyboard from the bottom of the screen");
-
     public static final BooleanFlag ENABLE_ALL_APPS_FROM_OVERVIEW =
             getDebugFlag(275132633, "ENABLE_ALL_APPS_FROM_OVERVIEW", DISABLED,
                     "Allow entering All Apps from Overview (e.g. long swipe up from app)");
@@ -148,7 +144,7 @@
             DISABLED, "Sends a notification whenever launcher encounters an uncaught exception.");
 
     public static final boolean ENABLE_TASKBAR_NAVBAR_UNIFICATION =
-            enableTaskbarNavbarUnification() && !isPhone();
+            enableTaskbarNavbarUnification() && (!isPhone() || enableTaskbarOnPhones());
 
     private static boolean isPhone() {
         final boolean isPhone;
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcast.java b/src/com/android/launcher3/model/FirstScreenBroadcast.java
index cc20cd1..f443f81 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcast.java
+++ b/src/com/android/launcher3/model/FirstScreenBroadcast.java
@@ -96,7 +96,7 @@
                 .collect(groupingBy(SessionInfo::getInstallerPackageName,
                         mapping(SessionInfo::getAppPackageName, Collectors.toSet())))
                 .forEach((installer, packages) ->
-                    sendBroadcastToInstaller(context, installer, packages, firstScreenItems));
+                        sendBroadcastToInstaller(context, installer, packages, firstScreenItems));
     }
 
     /**
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
new file mode 100644
index 0000000..aa62c32
--- /dev/null
+++ b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
@@ -0,0 +1,387 @@
+/*
+ * Copyright (C) 2024 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.model
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageInstaller.SessionInfo
+import android.os.Process
+import android.util.Log
+import androidx.annotation.AnyThread
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
+import com.android.launcher3.model.data.CollectionInfo
+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
+
+/**
+ * Helper class to send broadcasts to package installers that have:
+ * - Pending Items on first screen
+ * - Installed/Archived Items on first screen
+ * - Installed/Archived Widgets on every screen
+ *
+ * The packages are broken down by: folder items, workspace items, hotseat items, and widgets.
+ * Package installers only receive data for items that they are installing or have installed.
+ */
+object FirstScreenBroadcastHelper {
+    @VisibleForTesting const val MAX_BROADCAST_SIZE = 70
+
+    private const val TAG = "FirstScreenBroadcastHelper"
+    private const val DEBUG = true
+    private const val ACTION_FIRST_SCREEN_ACTIVE_INSTALLS =
+        "com.android.launcher3.action.FIRST_SCREEN_ACTIVE_INSTALLS"
+    // String retained as "folderItem" for back-compatibility reasons.
+    private const val PENDING_COLLECTION_ITEM_EXTRA = "folderItem"
+    private const val PENDING_WORKSPACE_ITEM_EXTRA = "workspaceItem"
+    private const val PENDING_HOTSEAT_ITEM_EXTRA = "hotseatItem"
+    private const val PENDING_WIDGET_ITEM_EXTRA = "widgetItem"
+    // Extras containing all installed items, including Archived Apps.
+    private const val INSTALLED_WORKSPACE_ITEMS_EXTRA = "workspaceInstalledItems"
+    private const val INSTALLED_HOTSEAT_ITEMS_EXTRA = "hotseatInstalledItems"
+    // This includes installed widgets on all screens, not just first.
+    private const val ALL_INSTALLED_WIDGETS_ITEM_EXTRA = "widgetInstalledItems"
+    private const val VERIFICATION_TOKEN_EXTRA = "verificationToken"
+
+    /**
+     * Return list of [FirstScreenBroadcastModel] for each installer and their
+     * installing/installed/archived items. If the FirstScreenBroadcastModel data is greater in size
+     * than [MAX_BROADCAST_SIZE], then we will truncate the data until it meets the size limit to
+     * avoid overloading the broadcast.
+     *
+     * @param packageManagerHelper helper for querying PackageManager
+     * @param firstScreenItems every ItemInfo on first screen
+     * @param userKeyToSessionMap map of pending SessionInfo's for installing items
+     * @param allWidgets list of all Widgets added to every screen
+     */
+    @WorkerThread
+    @JvmStatic
+    fun createModelsForFirstScreenBroadcast(
+        packageManagerHelper: PackageManagerHelper,
+        firstScreenItems: List<ItemInfo>,
+        userKeyToSessionMap: Map<PackageUserKey, SessionInfo>,
+        allWidgets: List<LauncherAppWidgetInfo>
+    ): List<FirstScreenBroadcastModel> {
+
+        // installers for installing items
+        val pendingItemInstallerMap: Map<String, MutableSet<String>> =
+            createPendingItemsMap(userKeyToSessionMap)
+        val installingPackages = pendingItemInstallerMap.values.flatten().toSet()
+
+        // installers for installed items on first screen
+        val installedItemInstallerMap: Map<String, MutableSet<ItemInfo>> =
+            createInstalledItemsMap(firstScreenItems, installingPackages, packageManagerHelper)
+
+        // installers for widgets on all screens
+        val allInstalledWidgetsMap: Map<String, MutableSet<LauncherAppWidgetInfo>> =
+            createAllInstalledWidgetsMap(allWidgets, installingPackages, packageManagerHelper)
+
+        val allInstallers: Set<String> =
+            pendingItemInstallerMap.keys +
+                installedItemInstallerMap.keys +
+                allInstalledWidgetsMap.keys
+        val models = mutableListOf<FirstScreenBroadcastModel>()
+        // create broadcast for each installer, with extras for each item category
+        allInstallers.forEach { installer ->
+            val installingItems = pendingItemInstallerMap[installer]
+            val broadcastModel =
+                FirstScreenBroadcastModel(installerPackage = installer).apply {
+                    addPendingItems(installingItems, firstScreenItems)
+                    addInstalledItems(installer, installedItemInstallerMap)
+                    addAllScreenWidgets(installer, allInstalledWidgetsMap)
+                }
+            broadcastModel.truncateModelForBroadcast()
+            models.add(broadcastModel)
+        }
+        return models
+    }
+
+    /** From the model data, create Intents to send broadcasts and fire them. */
+    @WorkerThread
+    @JvmStatic
+    fun sendBroadcastsForModels(context: Context, models: List<FirstScreenBroadcastModel>) {
+        for (model in models) {
+            model.printDebugInfo()
+            val intent =
+                Intent(ACTION_FIRST_SCREEN_ACTIVE_INSTALLS)
+                    .setPackage(model.installerPackage)
+                    .putExtra(
+                        VERIFICATION_TOKEN_EXTRA,
+                        PendingIntent.getActivity(
+                            context,
+                            0 /* requestCode */,
+                            Intent(),
+                            PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
+                        )
+                    )
+                    .putStringArrayListExtra(
+                        PENDING_COLLECTION_ITEM_EXTRA,
+                        ArrayList(model.pendingCollectionItems)
+                    )
+                    .putStringArrayListExtra(
+                        PENDING_WORKSPACE_ITEM_EXTRA,
+                        ArrayList(model.pendingWorkspaceItems)
+                    )
+                    .putStringArrayListExtra(
+                        PENDING_HOTSEAT_ITEM_EXTRA,
+                        ArrayList(model.pendingHotseatItems)
+                    )
+                    .putStringArrayListExtra(
+                        PENDING_WIDGET_ITEM_EXTRA,
+                        ArrayList(model.pendingWidgetItems)
+                    )
+                    .putStringArrayListExtra(
+                        INSTALLED_WORKSPACE_ITEMS_EXTRA,
+                        ArrayList(model.installedWorkspaceItems)
+                    )
+                    .putStringArrayListExtra(
+                        INSTALLED_HOTSEAT_ITEMS_EXTRA,
+                        ArrayList(model.installedHotseatItems)
+                    )
+                    .putStringArrayListExtra(
+                        ALL_INSTALLED_WIDGETS_ITEM_EXTRA,
+                        ArrayList(
+                            model.firstScreenInstalledWidgets +
+                                model.secondaryScreenInstalledWidgets
+                        )
+                    )
+            context.sendBroadcast(intent)
+        }
+    }
+
+    /** Maps Installer packages to Set of app packages from install sessions */
+    private fun createPendingItemsMap(
+        userKeyToSessionMap: Map<PackageUserKey, SessionInfo>
+    ): Map<String, MutableSet<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 result
+    }
+
+    /**
+     * Add first screen Pending Items from Map to [FirstScreenBroadcastModel] for given installer
+     */
+    private fun FirstScreenBroadcastModel.addPendingItems(
+        installingItems: Set<String>?,
+        firstScreenItems: List<ItemInfo>
+    ) {
+        if (installingItems == null) return
+        for (info in firstScreenItems) {
+            addCollectionItems(info, installingItems)
+            val packageName = getPackageName(info) ?: continue
+            if (!installingItems.contains(packageName)) continue
+            when {
+                info is LauncherAppWidgetInfo -> pendingWidgetItems.add(packageName)
+                info.container == CONTAINER_HOTSEAT -> pendingHotseatItems.add(packageName)
+                info.container == CONTAINER_DESKTOP -> pendingWorkspaceItems.add(packageName)
+            }
+        }
+    }
+
+    /**
+     * Add first screen installed Items from Map to [FirstScreenBroadcastModel] for given installer
+     */
+    private fun FirstScreenBroadcastModel.addInstalledItems(
+        installer: String,
+        installedItemInstallerMap: Map<String, Set<ItemInfo>>,
+    ) {
+        installedItemInstallerMap[installer]?.forEach { info ->
+            val packageName: String = getPackageName(info) ?: return@forEach
+            when (info.container) {
+                CONTAINER_HOTSEAT -> installedHotseatItems.add(packageName)
+                CONTAINER_DESKTOP -> installedWorkspaceItems.add(packageName)
+            }
+        }
+    }
+
+    /** Add Widgets on every screen from Map to [FirstScreenBroadcastModel] for given installer */
+    private fun FirstScreenBroadcastModel.addAllScreenWidgets(
+        installer: String,
+        allInstalledWidgetsMap: Map<String, Set<LauncherAppWidgetInfo>>
+    ) {
+        allInstalledWidgetsMap[installer]?.forEach { widget ->
+            val packageName: String = getPackageName(widget) ?: return@forEach
+            if (widget.screenId == 0) {
+                firstScreenInstalledWidgets.add(packageName)
+            } else {
+                secondaryScreenInstalledWidgets.add(packageName)
+            }
+        }
+    }
+
+    private fun FirstScreenBroadcastModel.addCollectionItems(
+        info: ItemInfo,
+        installingPackages: Set<String>
+    ) {
+        if (info !is CollectionInfo) return
+        pendingCollectionItems.addAll(
+            cloneOnMainThread(info.getAppContents())
+                .mapNotNull { getPackageName(it) }
+                .filter { installingPackages.contains(it) }
+        )
+    }
+
+    /**
+     * Creates a copy of [FirstScreenBroadcastModel] with items truncated to meet
+     * [MAX_BROADCAST_SIZE] in a prioritized order.
+     */
+    @VisibleForTesting
+    fun FirstScreenBroadcastModel.truncateModelForBroadcast() {
+        val totalItemCount = getTotalItemCount()
+        if (totalItemCount <= MAX_BROADCAST_SIZE) return
+        var extraItemCount = totalItemCount - MAX_BROADCAST_SIZE
+
+        while (extraItemCount > 0) {
+            // In this order, remove items until we meet the max size limit.
+            when {
+                pendingCollectionItems.isNotEmpty() ->
+                    pendingCollectionItems.apply { remove(last()) }
+                pendingHotseatItems.isNotEmpty() -> pendingHotseatItems.apply { remove(last()) }
+                installedHotseatItems.isNotEmpty() -> installedHotseatItems.apply { remove(last()) }
+                secondaryScreenInstalledWidgets.isNotEmpty() ->
+                    secondaryScreenInstalledWidgets.apply { remove(last()) }
+                pendingWidgetItems.isNotEmpty() -> pendingWidgetItems.apply { remove(last()) }
+                firstScreenInstalledWidgets.isNotEmpty() ->
+                    firstScreenInstalledWidgets.apply { remove(last()) }
+                pendingWorkspaceItems.isNotEmpty() -> pendingWorkspaceItems.apply { remove(last()) }
+                installedWorkspaceItems.isNotEmpty() ->
+                    installedWorkspaceItems.apply { remove(last()) }
+            }
+            extraItemCount--
+        }
+    }
+
+    /** Returns count of all Items held by [FirstScreenBroadcastModel]. */
+    @VisibleForTesting
+    fun FirstScreenBroadcastModel.getTotalItemCount() =
+        pendingCollectionItems.size +
+            pendingWorkspaceItems.size +
+            pendingHotseatItems.size +
+            pendingWidgetItems.size +
+            installedWorkspaceItems.size +
+            installedHotseatItems.size +
+            firstScreenInstalledWidgets.size +
+            secondaryScreenInstalledWidgets.size
+
+    private fun FirstScreenBroadcastModel.printDebugInfo() {
+        if (DEBUG) {
+            Log.d(
+                TAG,
+                "Sending First Screen Broadcast for installer=$installerPackage" +
+                    ", total packages=${getTotalItemCount()}"
+            )
+            pendingCollectionItems.forEach {
+                Log.d(TAG, "$installerPackage:Pending Collection item:$it")
+            }
+            pendingWorkspaceItems.forEach {
+                Log.d(TAG, "$installerPackage:Pending Workspace item:$it")
+            }
+            pendingHotseatItems.forEach { Log.d(TAG, "$installerPackage:Pending Hotseat item:$it") }
+            pendingWidgetItems.forEach { Log.d(TAG, "$installerPackage:Pending Widget item:$it") }
+            installedWorkspaceItems.forEach {
+                Log.d(TAG, "$installerPackage:Installed Workspace item:$it")
+            }
+            installedHotseatItems.forEach {
+                Log.d(TAG, "$installerPackage:Installed Hotseat item:$it")
+            }
+            firstScreenInstalledWidgets.forEach {
+                Log.d(TAG, "$installerPackage:Installed Widget item (first screen):$it")
+            }
+            secondaryScreenInstalledWidgets.forEach {
+                Log.d(TAG, "$installerPackage:Installed Widget item (secondary screens):$it")
+            }
+        }
+    }
+
+    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
+    }
+
+    /**
+     * Clone the provided list on UI thread. This is used for [FolderInfo.getContents] which is
+     * always modified on UI thread.
+     */
+    @AnyThread
+    private fun cloneOnMainThread(list: ArrayList<WorkspaceItemInfo>): List<WorkspaceItemInfo> {
+        return try {
+            return Executors.MAIN_EXECUTOR.submit<ArrayList<WorkspaceItemInfo>> { ArrayList(list) }
+                .get()
+        } catch (e: Exception) {
+            emptyList()
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcastModel.kt b/src/com/android/launcher3/model/FirstScreenBroadcastModel.kt
new file mode 100644
index 0000000..ba5c526
--- /dev/null
+++ b/src/com/android/launcher3/model/FirstScreenBroadcastModel.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 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.model
+
+/** Data model for the information used for [FirstScreenBroadcastHelper] Broadcast Extras */
+data class FirstScreenBroadcastModel(
+    // Package name of the installer for all items
+    val installerPackage: String,
+    // Installing items in Folders
+    val pendingCollectionItems: MutableSet<String> = mutableSetOf(),
+    // Installing items on first screen
+    val pendingWorkspaceItems: MutableSet<String> = mutableSetOf(),
+    // Installing items on hotseat
+    val pendingHotseatItems: MutableSet<String> = mutableSetOf(),
+    // Installing widgets on first screen
+    val pendingWidgetItems: MutableSet<String> = mutableSetOf(),
+    // Installed/Archived Items on first screen
+    val installedWorkspaceItems: MutableSet<String> = mutableSetOf(),
+    // Installed/Archived items on hotseat
+    val installedHotseatItems: MutableSet<String> = mutableSetOf(),
+    // Installed/Archived Widgets on first screen
+    val firstScreenInstalledWidgets: MutableSet<String> = mutableSetOf(),
+    // Installed Archived Widgets on secondary screens
+    val secondaryScreenInstalledWidgets: MutableSet<String> = mutableSetOf()
+)
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 0d40a24..312c6f4 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -47,6 +47,7 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.LongSparseArray;
@@ -195,17 +196,32 @@
     }
 
     private void sendFirstScreenActiveInstallsBroadcast() {
-        ArrayList<ItemInfo> firstScreenItems = new ArrayList<>();
-        ArrayList<ItemInfo> allItems = mBgDataModel.getAllWorkspaceItems();
-
         // Screen set is never empty
         IntArray allScreens = mBgDataModel.collectWorkspaceScreens();
         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 */);
-        mFirstScreenBroadcast.sendBroadcasts(mApp.getContext(), firstScreenItems);
+        final int launcherBroadcastInstalledApps = Settings.Secure.getInt(
+                mApp.getContext().getContentResolver(),
+                "launcher_broadcast_installed_apps",
+                /* def= */ 0);
+        if (launcherBroadcastInstalledApps == 1 && mIsRestoreFromBackup) {
+            List<FirstScreenBroadcastModel> broadcastModels =
+                    FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                            mPmHelper,
+                            firstScreenItems,
+                            mInstallingPkgsCached,
+                            mBgDataModel.appWidgets
+                    );
+            logASplit("Sending first screen broadcast with additional archiving Extras");
+            FirstScreenBroadcastHelper.sendBroadcastsForModels(mApp.getContext(), broadcastModels);
+        } else {
+            mFirstScreenBroadcast.sendBroadcasts(mApp.getContext(), firstScreenItems);
+        }
     }
 
     public void run() {
@@ -249,7 +265,7 @@
             mModelDelegate.workspaceLoadComplete();
             // Notify the installer packages of packages with active installs on the first screen.
             sendFirstScreenActiveInstallsBroadcast();
-            logASplit("sendFirstScreenActiveInstallsBroadcast");
+            logASplit("sendFirstScreenBroadcast");
 
             // Take a break
             waitForIdle();
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index 4cba0b5..454ae96 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -42,6 +42,7 @@
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
+import com.android.wm.shell.Flags;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -218,6 +219,7 @@
         // add and update.
         mWidgetsList.putAll(rawWidgetsShortcuts.stream()
                 .filter(new WidgetValidityCheck(app))
+                .filter(new WidgetFlagCheck())
                 .flatMap(widgetItem -> getPackageUserKeys(app.getContext(), widgetItem).stream()
                         .map(key -> new Pair<>(packageItemInfoCache.getOrCreate(key), widgetItem)))
                 .collect(groupingBy(pair -> pair.first, mapping(pair -> pair.second, toList()))));
@@ -377,6 +379,21 @@
         }
     }
 
+    private static class WidgetFlagCheck implements Predicate<WidgetItem> {
+
+        private static final String BUBBLES_SHORTCUT_WIDGET =
+                "com.android.systemui/com.android.wm.shell.bubbles.shortcut"
+                        + ".CreateBubbleShortcutActivity";
+
+        @Override
+        public boolean test(WidgetItem widgetItem) {
+            if (BUBBLES_SHORTCUT_WIDGET.equals(widgetItem.componentName.flattenToString())) {
+                return Flags.enableRetrievableBubbles();
+            }
+            return true;
+        }
+    }
+
     private static final class PackageItemInfoCache {
         private final Map<PackageUserKey, PackageItemInfo> mMap = new ArrayMap<>();
 
diff --git a/src/com/android/launcher3/notification/NotificationKeyData.java b/src/com/android/launcher3/notification/NotificationKeyData.java
index 4115b3d..e38824c 100644
--- a/src/com/android/launcher3/notification/NotificationKeyData.java
+++ b/src/com/android/launcher3/notification/NotificationKeyData.java
@@ -22,6 +22,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.Utilities;
 
@@ -38,6 +39,11 @@
     public final String[] personKeysFromNotification;
     public int count;
 
+    @VisibleForTesting
+    public NotificationKeyData(String notificationKey) {
+        this(notificationKey, null, 1, new String[]{});
+    }
+
     private NotificationKeyData(String notificationKey, String shortcutId, int count,
             String[] personKeysFromNotification) {
         this.notificationKey = notificationKey;
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index ed25186..cf03462 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -93,12 +93,12 @@
 
     @Override
     public void close() {
-        MODEL_EXECUTOR.execute(() -> mUserChangeReceiver.unregisterReceiverSafely(mContext));
+        MODEL_EXECUTOR.execute(() -> mUserChangeReceiver.unregisterReceiverSafelySync(mContext));
     }
 
     @WorkerThread
     private void initAsync() {
-        mUserChangeReceiver.register(mContext,
+        mUserChangeReceiver.registerSync(mContext,
                 Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
                 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
                 Intent.ACTION_MANAGED_PROFILE_REMOVED,
diff --git a/src/com/android/launcher3/popup/RoundedArrowDrawable.java b/src/com/android/launcher3/popup/RoundedArrowDrawable.java
index 575052c..2610bd6 100644
--- a/src/com/android/launcher3/popup/RoundedArrowDrawable.java
+++ b/src/com/android/launcher3/popup/RoundedArrowDrawable.java
@@ -175,7 +175,10 @@
         mPaint.setShadowLayer(shadowBlur, dx, dy, shadowColor);
     }
 
-    private static void addDownPointingRoundedTriangleToPath(float width, float height,
+    /**
+     * Adds rounded triangle pointing down to the provided {@link Path path} argument
+     */
+    public static void addDownPointingRoundedTriangleToPath(float width, float height,
             float radius, Path path) {
         // Calculated for the arrow pointing down, will be flipped later if needed.
 
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index a4ff29f..21897bf 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -418,9 +418,7 @@
         DeviceGridState deviceGridState = new DeviceGridState(context);
         FileLog.d(TAG, "restore initiated from backup: DeviceGridState=" + deviceGridState);
         LauncherPrefs.get(context).putSync(RESTORE_DEVICE.to(deviceGridState.getDeviceType()));
-        if (enableLauncherBrMetricsFixed()) {
-            LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(true));
-        }
+        LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(true));
     }
 
     @WorkerThread
diff --git a/src/com/android/launcher3/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java
index a01d402..b81729a 100644
--- a/src/com/android/launcher3/statemanager/BaseState.java
+++ b/src/com/android/launcher3/statemanager/BaseState.java
@@ -21,7 +21,7 @@
 import com.android.launcher3.views.ActivityContext;
 
 /**
- * Interface representing a state of a StatefulActivity
+ * Interface representing a state of a StatefulContainer
  */
 public interface BaseState<T extends BaseState> {
 
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index eea1a7d..ac07c0f 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -26,6 +26,7 @@
 import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
+import android.content.Context;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Log;
@@ -47,8 +48,11 @@
 /**
  * Class to manage transitions between different states for a StatefulActivity based on different
  * states
+ * @param STATE_TYPE Basestate used by the state manager
+ * @param STATEFUL_CONTAINER container object used to manage state
  */
-public class StateManager<STATE_TYPE extends BaseState<STATE_TYPE>> {
+public class StateManager<STATE_TYPE extends BaseState<STATE_TYPE>,
+        STATEFUL_CONTAINER extends Context & StatefulContainer<STATE_TYPE>> {
 
     public static final String TAG = "StateManager";
     // b/279059025, b/325463989
@@ -56,7 +60,7 @@
 
     private final AnimationState mConfig = new AnimationState();
     private final Handler mUiHandler;
-    private final StatefulActivity<STATE_TYPE> mActivity;
+    private final STATEFUL_CONTAINER mStatefulContainer;
     private final ArrayList<StateListener<STATE_TYPE>> mListeners = new ArrayList<>();
     private final STATE_TYPE mBaseState;
 
@@ -71,12 +75,12 @@
 
     private STATE_TYPE mRestState;
 
-    public StateManager(StatefulActivity<STATE_TYPE> l, STATE_TYPE baseState) {
+    public StateManager(STATEFUL_CONTAINER container, STATE_TYPE baseState) {
         mUiHandler = new Handler(Looper.getMainLooper());
-        mActivity = l;
+        mStatefulContainer = container;
         mBaseState = baseState;
         mState = mLastStableState = mCurrentStableState = baseState;
-        mAtomicAnimationFactory = l.createAtomicAnimationFactory();
+        mAtomicAnimationFactory = container.createAtomicAnimationFactory();
     }
 
     public STATE_TYPE getState() {
@@ -109,10 +113,10 @@
         writer.println(prefix + "\tisInTransition:" + isInTransition());
     }
 
-    public StateHandler[] getStateHandlers() {
+    public StateHandler<STATE_TYPE>[] getStateHandlers() {
         if (mStateHandlers == null) {
-            ArrayList<StateHandler> handlers = new ArrayList<>();
-            mActivity.collectStateHandlers(handlers);
+            ArrayList<StateHandler<STATE_TYPE>> handlers = new ArrayList<>();
+            mStatefulContainer.collectStateHandlers(handlers);
             mStateHandlers = handlers.toArray(new StateHandler[handlers.size()]);
         }
         return mStateHandlers;
@@ -130,7 +134,7 @@
      * Returns true if the state changes should be animated.
      */
     public boolean shouldAnimateStateChange() {
-        return !mActivity.isForceInvisible() && mActivity.isStarted();
+        return mStatefulContainer.shouldAnimateStateChange();
     }
 
     /**
@@ -245,7 +249,7 @@
         }
 
         animated &= areAnimatorsEnabled();
-        if (mActivity.isInState(state)) {
+        if (mStatefulContainer.isInState(state)) {
             if (mConfig.currentAnimation == null) {
                 // Run any queued runnable
                 if (listener != null) {
@@ -302,8 +306,8 @@
         // Since state mBaseState can be reached from multiple states, just assume that the
         // transition plays in reverse and use the same duration as previous state.
         mConfig.duration = state == mBaseState
-                ? fromState.getTransitionDuration(mActivity, false /* isToState */)
-                : state.getTransitionDuration(mActivity, true /* isToState */);
+                ? fromState.getTransitionDuration(mStatefulContainer, false /* isToState */)
+                : state.getTransitionDuration(mStatefulContainer, true /* isToState */);
         prepareForAtomicAnimation(fromState, state, mConfig);
         AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).buildAnim();
         if (listener != null) {
@@ -336,7 +340,7 @@
         PendingAnimation builder = new PendingAnimation(config.duration);
         prepareForAtomicAnimation(fromState, toState, config);
 
-        for (StateHandler handler : mActivity.getStateManager().getStateHandlers()) {
+        for (StateHandler handler : mStatefulContainer.getStateManager().getStateHandlers()) {
             handler.setStateWithAnimation(toState, config, builder);
         }
         return builder.buildAnim();
@@ -402,7 +406,7 @@
 
     private void onStateTransitionStart(STATE_TYPE state) {
         mState = state;
-        mActivity.onStateSetStart(mState);
+        mStatefulContainer.onStateSetStart(mState);
 
         if (DEBUG) {
             Log.d(TAG, "onStateTransitionStart - state: " + state);
@@ -419,7 +423,7 @@
             mCurrentStableState = state;
         }
 
-        mActivity.onStateSetEnd(state);
+        mStatefulContainer.onStateSetEnd(state);
         if (state == mBaseState) {
             setRestState(null);
         }
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index 30ba703..28f2def 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -18,7 +18,6 @@
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 
-import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
 import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
 
 import android.content.res.Configuration;
@@ -29,11 +28,9 @@
 
 import androidx.annotation.CallSuper;
 
-import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.LauncherRootView;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.launcher3.views.BaseDragLayer;
@@ -45,7 +42,7 @@
  * @param <STATE_TYPE> Type of state object
  */
 public abstract class StatefulActivity<STATE_TYPE extends BaseState<STATE_TYPE>>
-        extends BaseDraggingActivity {
+        extends BaseDraggingActivity implements StatefulContainer<STATE_TYPE> {
 
     public final Handler mHandler = new Handler();
     private final Runnable mHandleDeferredResume = this::handleDeferredResume;
@@ -67,19 +64,9 @@
     /**
      * Create handlers to control the property changes for this activity
      */
-    protected abstract void collectStateHandlers(List<StateHandler> out);
 
-    /**
-     * Returns true if the activity is in the provided state
-     */
-    public boolean isInState(STATE_TYPE state) {
-        return getStateManager().getState() == state;
-    }
-
-    /**
-     * Returns the state manager for this activity
-     */
-    public abstract StateManager<STATE_TYPE> getStateManager();
+    @Override
+    public abstract void collectStateHandlers(List<StateHandler<STATE_TYPE>> out);
 
     protected void inflateRootView(int layoutId) {
         mRootView = (LauncherRootView) LayoutInflater.from(this).inflate(layoutId, null);
@@ -106,22 +93,12 @@
         if (mDeferredResumePending) {
             handleDeferredResume();
         }
-
-        if (state.hasFlag(FLAG_CLOSE_POPUPS)) {
-            AbstractFloatingView.closeAllOpenViews(this, !state.hasFlag(FLAG_NON_INTERACTIVE));
-        }
+        StatefulContainer.super.onStateSetStart(state);
     }
 
-    /**
-     * Called when transition to state ends
-     */
-    public void onStateSetEnd(STATE_TYPE state) { }
-
-    /**
-     * Creates a factory for atomic state animations
-     */
-    public AtomicAnimationFactory<STATE_TYPE> createAtomicAnimationFactory() {
-        return new AtomicAnimationFactory(0);
+    @Override
+    public boolean shouldAnimateStateChange() {
+        return !isForceInvisible() && isStarted();
     }
 
     @Override
diff --git a/src/com/android/launcher3/statemanager/StatefulContainer.java b/src/com/android/launcher3/statemanager/StatefulContainer.java
new file mode 100644
index 0000000..0cf0a27
--- /dev/null
+++ b/src/com/android/launcher3/statemanager/StatefulContainer.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2024 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.statemanager;
+
+
+import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
+import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
+
+import androidx.annotation.CallSuper;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.views.ActivityContext;
+
+import java.util.List;
+
+/**
+ * Interface for a container that can be managed by a state manager.
+ *
+ * @param <STATE_TYPE> The type of state that the container can be in.
+ */
+public interface StatefulContainer<STATE_TYPE extends BaseState<STATE_TYPE>> extends
+        ActivityContext {
+
+    /**
+     * Creates a factory for atomic state animations
+     */
+    default StateManager.AtomicAnimationFactory<STATE_TYPE> createAtomicAnimationFactory() {
+        return new StateManager.AtomicAnimationFactory<>(0);
+    }
+
+    /**
+     * Create handlers to control the property changes for this activity
+     */
+    void collectStateHandlers(List<StateManager.StateHandler<STATE_TYPE>> out);
+
+    /**
+     * Retrieves state manager for given container
+     */
+    StateManager<STATE_TYPE, ?> getStateManager();
+
+    /**
+     * Called when transition to state ends
+     * @param state current state of State_Type
+     */
+    default void onStateSetEnd(STATE_TYPE state) { }
+
+    /**
+     * Called when transition to state starts
+     * @param state current state of State_Type
+     */
+    @CallSuper
+    default void onStateSetStart(STATE_TYPE state) {
+        if (state.hasFlag(FLAG_CLOSE_POPUPS)) {
+            AbstractFloatingView.closeAllOpenViews(this, !state.hasFlag(FLAG_NON_INTERACTIVE));
+        }
+    }
+
+    /**
+     * Returns true if the activity is in the provided state
+     * @param state current state of State_Type
+     */
+    default boolean isInState(STATE_TYPE state) {
+        return getStateManager().getState() == state;
+    }
+
+    /**
+     * Returns true if state change should transition with animation
+     */
+    boolean shouldAnimateStateChange();
+}
diff --git a/src/com/android/launcher3/util/ApiWrapper.java b/src/com/android/launcher3/util/ApiWrapper.java
index 6429a43..095518c 100644
--- a/src/com/android/launcher3/util/ApiWrapper.java
+++ b/src/com/android/launcher3/util/ApiWrapper.java
@@ -16,10 +16,12 @@
 
 package com.android.launcher3.util;
 
+import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_HOME_ROLE;
 import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 
 import android.app.ActivityOptions;
 import android.app.Person;
+import android.app.role.RoleManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherActivityInfo;
@@ -33,6 +35,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.BuildConfig;
+import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 
@@ -136,6 +139,23 @@
         return false;
     }
 
+    /**
+     * Starts an Activity which can be used to set this Launcher as the HOME app, via a consent
+     * screen. In case the consent screen cannot be shown, or the user does not set current Launcher
+     * as HOME app, a toast asking the user to do the latter is shown.
+     */
+    public void assignDefaultHomeRole(Context context) {
+        RoleManager roleManager = context.getSystemService(RoleManager.class);
+        assert roleManager != null;
+        if (roleManager.isRoleAvailable(RoleManager.ROLE_HOME)
+                && !roleManager.isRoleHeld(RoleManager.ROLE_HOME)) {
+            Intent roleRequestIntent = roleManager.createRequestRoleIntent(
+                    RoleManager.ROLE_HOME);
+            Launcher launcher = Launcher.getLauncher(context);
+            launcher.startActivityForResult(roleRequestIntent, REQUEST_HOME_ROLE);
+        }
+    }
+
     @Override
     public void close() { }
 
diff --git a/src/com/android/launcher3/util/CannedAnimationCoordinator.kt b/src/com/android/launcher3/util/CannedAnimationCoordinator.kt
index 85f81f5..749caac 100644
--- a/src/com/android/launcher3/util/CannedAnimationCoordinator.kt
+++ b/src/com/android/launcher3/util/CannedAnimationCoordinator.kt
@@ -113,10 +113,9 @@
         // flags accordingly
         animationController =
             controller.apply {
-                activity.stateManager.setCurrentAnimation(
-                    this,
-                    USER_CONTROLLED or HANDLE_STATE_APPLY
-                )
+                activity
+                    .stateManager
+                    .setCurrentAnimation(this, USER_CONTROLLED or HANDLE_STATE_APPLY)
             }
         recreateAnimation(provider)
     }
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 16fabe2..3dcc663 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -132,11 +132,11 @@
             mWindowContext.registerComponentCallbacks(this);
         } else {
             mWindowContext = null;
-            mReceiver.register(mContext, ACTION_CONFIGURATION_CHANGED);
+            mReceiver.registerAsync(mContext, ACTION_CONFIGURATION_CHANGED);
         }
 
         // Initialize navigation mode change listener
-        mReceiver.registerPkgActions(mContext, TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED);
+        mReceiver.registerPkgActionsAsync(mContext, TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED);
 
         WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(context);
         Context displayInfoContext = getDisplayInfoContext(display);
@@ -182,6 +182,11 @@
         return INSTANCE.get(context).getInfo().isTransientTaskbar();
     }
 
+    /** Returns whether we are currently in Desktop mode. */
+    public static boolean isInDesktopMode(Context context) {
+        return INSTANCE.get(context).getInfo().isInDesktopMode();
+    }
+
     /**
      * Handles info change for desktop mode.
      */
@@ -218,6 +223,7 @@
         } else {
             // TODO: unregister broadcast receiver
         }
+        mReceiver.unregisterReceiverSafelyAsync(mContext);
     }
 
     /**
diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt
index 94f9e4f..2737249 100644
--- a/src/com/android/launcher3/util/LockedUserState.kt
+++ b/src/com/android/launcher3/util/LockedUserState.kt
@@ -25,6 +25,7 @@
     val isUserUnlockedAtLauncherStartup: Boolean
     var isUserUnlocked: Boolean
         private set
+
     private val mUserUnlockedActions: RunnableList = RunnableList()
 
     @VisibleForTesting
@@ -50,22 +51,18 @@
         if (isUserUnlocked) {
             notifyUserUnlocked()
         } else {
-            mUserUnlockedReceiver.register(mContext, Intent.ACTION_USER_UNLOCKED)
+            mUserUnlockedReceiver.registerAsync(mContext, Intent.ACTION_USER_UNLOCKED)
         }
     }
 
     private fun notifyUserUnlocked() {
         mUserUnlockedActions.executeAllAndDestroy()
-        Executors.THREAD_POOL_EXECUTOR.execute {
-            mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
-        }
+        mUserUnlockedReceiver.unregisterReceiverSafelyAsync(mContext)
     }
 
     /** Stops the receiver from listening for ACTION_USER_UNLOCK broadcasts. */
     override fun close() {
-        Executors.THREAD_POOL_EXECUTOR.execute {
-            mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
-        }
+        mUserUnlockedReceiver.unregisterReceiverSafelyAsync(mContext)
     }
 
     /**
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index f7c4df4..8c5a76e 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -16,9 +16,10 @@
 
 package com.android.launcher3.util;
 
-import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
 import static android.view.WindowManager.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI;
 
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
+
 import android.content.ActivityNotFoundException;
 import android.content.ComponentName;
 import android.content.Context;
@@ -151,6 +152,18 @@
     }
 
     /**
+     * Returns the installing app package for the given package
+     */
+    public String getAppInstallerPackage(@NonNull final String packageName) {
+        try {
+            return mPm.getInstallSourceInfo(packageName).getInstallingPackageName();
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Failed to get installer package for app package:" + packageName, e);
+            return null;
+        }
+    }
+
+    /**
      * Returns the application info for the provided package or null
      */
     @Nullable
diff --git a/src/com/android/launcher3/util/ScreenOnTracker.java b/src/com/android/launcher3/util/ScreenOnTracker.java
index e16e477..c1d192c 100644
--- a/src/com/android/launcher3/util/ScreenOnTracker.java
+++ b/src/com/android/launcher3/util/ScreenOnTracker.java
@@ -42,12 +42,12 @@
         // Assume that the screen is on to begin with
         mContext = context;
         mIsScreenOn = true;
-        mReceiver.register(context, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT);
+        mReceiver.registerAsync(context, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT);
     }
 
     @Override
     public void close() {
-        mReceiver.unregisterReceiverSafely(mContext);
+        mReceiver.unregisterReceiverSafelyAsync(mContext);
     }
 
     private void onReceive(Intent intent) {
diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java
index ccd154a..cd6701d 100644
--- a/src/com/android/launcher3/util/SettingsCache.java
+++ b/src/com/android/launcher3/util/SettingsCache.java
@@ -18,6 +18,8 @@
 
 import static android.provider.Settings.System.ACCELEROMETER_ROTATION;
 
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -87,7 +89,7 @@
 
     @Override
     public void close() {
-        mResolver.unregisterContentObserver(this);
+        UI_HELPER_EXECUTOR.execute(() -> mResolver.unregisterContentObserver(this));
     }
 
     @Override
@@ -135,7 +137,8 @@
             CopyOnWriteArrayList<OnChangeListener> l = new CopyOnWriteArrayList<>();
             l.add(changeListener);
             mListenerMap.put(uri, l);
-            mResolver.registerContentObserver(uri, false, this);
+            UI_HELPER_EXECUTOR.execute(
+                    () -> mResolver.registerContentObserver(uri, false, this));
         }
     }
 
diff --git a/src/com/android/launcher3/util/SimpleBroadcastReceiver.java b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
index 064bcd0..5f39cce 100644
--- a/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
+++ b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
@@ -15,14 +15,21 @@
  */
 package com.android.launcher3.util;
 
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
+
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.Looper;
 import android.os.PatternMatcher;
 import android.text.TextUtils;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+import androidx.annotation.WorkerThread;
+
+import com.android.launcher3.BuildConfig;
 
 import java.util.function.Consumer;
 
@@ -39,21 +46,63 @@
         mIntentConsumer.accept(intent);
     }
 
-    /**
-     * Helper method to register multiple actions
-     */
-    public void register(Context context, String... actions) {
+    /** Helper method to register multiple actions. Caller should be on main thread. */
+    @UiThread
+    public void registerAsync(Context context, String... actions) {
+        assertOnMainThread();
+        UI_HELPER_EXECUTOR.execute(() -> registerSync(context, actions));
+    }
+
+    /** Helper method to register multiple actions. Caller should be on main thread. */
+    @WorkerThread
+    public void registerSync(Context context, String... actions) {
+        assertOnBgThread();
         context.registerReceiver(this, getFilter(actions));
     }
 
     /**
-     * Helper method to register multiple actions associated with a paction
+     * Helper method to register multiple actions associated with a action. Caller should be from
+     * main thread.
      */
-    public void registerPkgActions(Context context, @Nullable String pkg, String... actions) {
+    @UiThread
+    public void registerPkgActionsAsync(Context context, @Nullable String pkg, String... actions) {
+        assertOnMainThread();
+        UI_HELPER_EXECUTOR.execute(() -> registerPkgActionsSync(context, pkg, actions));
+    }
+
+    /**
+     * Helper method to register multiple actions associated with a action. Caller should be from
+     * bg thread.
+     */
+    @WorkerThread
+    public void registerPkgActionsSync(Context context, @Nullable String pkg, String... actions) {
+        assertOnBgThread();
         context.registerReceiver(this, getPackageFilter(pkg, actions));
     }
 
     /**
+     * Unregisters the receiver ignoring any errors on bg thread. Caller should be on main thread.
+     */
+    @UiThread
+    public void unregisterReceiverSafelyAsync(Context context) {
+        assertOnMainThread();
+        UI_HELPER_EXECUTOR.execute(() -> unregisterReceiverSafelySync(context));
+    }
+
+    /**
+     * Unregisters the receiver ignoring any errors on bg thread. Caller should be on bg thread.
+     */
+    @WorkerThread
+    public void unregisterReceiverSafelySync(Context context) {
+        assertOnBgThread();
+        try {
+            context.unregisterReceiver(this);
+        } catch (IllegalArgumentException e) {
+            // It was probably never registered or already unregistered. Ignore.
+        }
+    }
+
+    /**
      * Creates an intent filter to listen for actions with a specific package in the data field.
      */
     public static IntentFilter getPackageFilter(String pkg, String... actions) {
@@ -73,14 +122,19 @@
         return filter;
     }
 
-    /**
-     * Unregisters the receiver ignoring any errors
-     */
-    public void unregisterReceiverSafely(Context context) {
-        try {
-            context.unregisterReceiver(this);
-        } catch (IllegalArgumentException e) {
-            // It was probably never registered or already unregistered. Ignore.
+    private static void assertOnBgThread() {
+        if (BuildConfig.IS_STUDIO_BUILD && isMainThread()) {
+            throw new IllegalStateException("Should not be called from main thread!");
         }
     }
+
+    private static void assertOnMainThread() {
+        if (BuildConfig.IS_STUDIO_BUILD && !isMainThread()) {
+            throw new IllegalStateException("Should not be called from bg thread!");
+        }
+    }
+
+    private static boolean isMainThread() {
+        return Thread.currentThread() == Looper.getMainLooper().getThread();
+    }
 }
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index b97b889..a2277a0 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -198,10 +198,11 @@
     public void setWindowToken(IBinder token) {
         mWindowToken = token;
         if (mWindowToken == null && mRegistered) {
-            mWallpaperChangeReceiver.unregisterReceiverSafely(mWorkspace.getContext());
+            mWallpaperChangeReceiver.unregisterReceiverSafelyAsync(mWorkspace.getContext());
             mRegistered = false;
         } else if (mWindowToken != null && !mRegistered) {
-            mWallpaperChangeReceiver.register(mWorkspace.getContext(), ACTION_WALLPAPER_CHANGED);
+            mWallpaperChangeReceiver.registerAsync(
+                    mWorkspace.getContext(), ACTION_WALLPAPER_CHANGED);
             onWallpaperChanged();
             mRegistered = true;
         }
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
index 325c1cd..f90a3e4 100644
--- a/src/com/android/launcher3/views/ClipIconView.java
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.views;
 
 import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
 import static com.android.launcher3.Utilities.boundToRange;
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
@@ -97,6 +98,9 @@
      * within the clip bounds of this view.
      */
     public void setTaskViewArtist(TaskViewArtist taskViewArtist) {
+        if (!enableAdditionalHomeAnimations()) {
+            return;
+        }
         mTaskViewArtist = taskViewArtist;
         invalidate();
     }
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 1d5a9dc..1e577be 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -18,6 +18,7 @@
 import static android.view.Gravity.LEFT;
 
 import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
 import static com.android.launcher3.Utilities.getFullDrawable;
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -164,7 +165,12 @@
      */
     public void update(float alpha, RectF rect, float progress, float shapeProgressStart,
             float cornerRadius, boolean isOpening, int taskViewDrawAlpha) {
-        setAlpha(isLaidOut() ? alpha : 0f);
+        // The non-running task home animation has some very funky first few frames because this
+        // FIV hasn't fully laid out. During those frames, hide this FIV and continue drawing the
+        // TaskView directly while transforming it in the place of this FIV. However, if we fade
+        // the TaskView at all, we need to display this FIV regardless.
+        setAlpha(!enableAdditionalHomeAnimations() || isLaidOut() || taskViewDrawAlpha < 255
+                ? alpha : 0f);
         mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening, this,
                 mLauncher.getDeviceProfile(), taskViewDrawAlpha);
 
diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
index df8f635..fa17b7b 100644
--- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java
+++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java
@@ -109,6 +109,13 @@
 
     private float mLastTouchY;
     private boolean mIsDragging;
+    /**
+     * Tracks whether a keyboard hide request has been sent due to downward scrolling.
+     * <p>
+     * Set to true when scrolling down and reset when scrolling up to prevents redundant hide
+     * requests during continuous downward scrolls.
+     */
+    private boolean mRequestedHideKeyboard;
     private boolean mIsThumbDetached;
     private final boolean mCanThumbDetach;
     private boolean mIgnoreDragGesture;
@@ -127,6 +134,7 @@
 
     protected FastScrollRecyclerView mRv;
     private RecyclerView.OnScrollListener mOnScrollListener;
+    private final ActivityContext mActivityContext;
 
     private int mDownX;
     private int mDownY;
@@ -163,7 +171,7 @@
         mDeltaThreshold = res.getDisplayMetrics().density * SCROLL_DELTA_THRESHOLD_DP;
         mScrollbarLeftOffsetTouchDelegate = res.getDisplayMetrics().density
                 * SCROLLBAR_LEFT_OFFSET_TOUCH_DELEGATE_DP;
-
+        mActivityContext = ActivityContext.lookupContext(context);
         TypedArray ta =
                 context.obtainStyledAttributes(attrs, R.styleable.RecyclerViewFastScroller, defStyleAttr, 0);
         mCanThumbDetach = ta.getBoolean(R.styleable.RecyclerViewFastScroller_canThumbDetach, false);
@@ -248,6 +256,7 @@
                 mDownX = x;
                 mDownY = mLastY = y;
                 mDownTimeStampMillis = ev.getDownTime();
+                mRequestedHideKeyboard = false;
 
                 if ((Math.abs(mDy) < mDeltaThreshold &&
                         mRv.getScrollState() != SCROLL_STATE_IDLE)) {
@@ -260,6 +269,7 @@
                 }
                 break;
             case MotionEvent.ACTION_MOVE:
+                boolean isScrollingDown = y > mLastY;
                 mLastY = y;
                 int absDeltaY = Math.abs(y - mDownY);
                 int absDeltaX = Math.abs(x - mDownX);
@@ -275,6 +285,14 @@
                     }
                 }
                 if (mIsDragging) {
+                    if (isScrollingDown) {
+                        if (!mRequestedHideKeyboard) {
+                            mActivityContext.hideKeyboard();
+                        }
+                        mRequestedHideKeyboard = true;
+                    } else {
+                        mRequestedHideKeyboard = false;
+                    }
                     updateFastScrollSectionNameAndThumbOffset(y);
                 }
                 break;
@@ -294,7 +312,6 @@
     }
 
     private void calcTouchOffsetAndPrepToFastScroll(int downY, int lastY) {
-        ActivityContext.lookupContext(getContext()).hideKeyboard();
         mIsDragging = true;
         if (mCanThumbDetach) {
             mIsThumbDetached = true;
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index a5e22c5..1fb8c83 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
 import static com.android.launcher3.Flags.enableWorkspaceInflation;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.widget.LauncherAppWidgetProviderInfo.fromProviderInfo;
 
 import android.appwidget.AppWidgetHost;
@@ -36,6 +37,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BaseDraggingActivity;
@@ -44,6 +46,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.ResourceBasedOverride;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.widget.LauncherAppWidgetHost.ListenableHostView;
@@ -51,6 +54,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.IntConsumer;
 
 /**
@@ -77,7 +81,7 @@
     protected final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
     protected final List<ProviderChangedListener> mProviderChangedListeners = new ArrayList<>();
 
-    protected int mFlags = FLAG_STATE_IS_NORMAL;
+    protected AtomicInteger mFlags = new AtomicInteger(FLAG_STATE_IS_NORMAL);
 
     // TODO(b/191735836): Replace with ActivityOptions.KEY_SPLASH_SCREEN_STYLE when un-hidden
     private static final String KEY_SPLASH_SCREEN_STYLE = "android.activity.splashScreenStyle";
@@ -96,6 +100,10 @@
                 context, appWidgetRemovedCallback, mProviderChangedListeners);
     }
 
+    protected LooperExecutor getWidgetHolderExecutor() {
+        return UI_HELPER_EXECUTOR;
+    }
+
     /**
      * Starts listening to the widget updates from the server side
      */
@@ -104,21 +112,23 @@
             return;
         }
 
-        try {
-            mWidgetHost.startListening();
-        } catch (Exception e) {
-            if (!Utilities.isBinderSizeError(e)) {
-                throw new RuntimeException(e);
+        getWidgetHolderExecutor().execute(() -> {
+            try {
+                mWidgetHost.startListening();
+            } catch (Exception e) {
+                if (!Utilities.isBinderSizeError(e)) {
+                    throw new RuntimeException(e);
+                }
+                // We're willing to let this slide. The exception is being caused by the list of
+                // RemoteViews which is being passed back. The startListening relationship will
+                // have been established by this point, and we will end up populating the
+                // widgets upon bind anyway. See issue 14255011 for more context.
             }
-            // We're willing to let this slide. The exception is being caused by the list of
-            // RemoteViews which is being passed back. The startListening relationship will
-            // have been established by this point, and we will end up populating the
-            // widgets upon bind anyway. See issue 14255011 for more context.
-        }
-        // TODO: Investigate why widgetHost.startListening() always return non-empty updates
-        setListeningFlag(true);
+            // TODO: Investigate why widgetHost.startListening() always return non-empty updates
+            setListeningFlag(true);
 
-        updateDeferredView();
+            MAIN_EXECUTOR.execute(() -> updateDeferredView());
+        });
     }
 
     /**
@@ -282,16 +292,23 @@
         if (!WIDGETS_ENABLED) {
             return;
         }
-        mWidgetHost.stopListening();
-        setListeningFlag(false);
+        getWidgetHolderExecutor().execute(() -> {
+            mWidgetHost.stopListening();
+            setListeningFlag(false);
+        });
     }
 
+    /**
+     * Update {@link FLAG_LISTENING} on {@link mFlags} after making binder calls from
+     * {@link sWidgetHost}.
+     */
+    @WorkerThread
     protected void setListeningFlag(final boolean isListening) {
         if (isListening) {
-            mFlags |= FLAG_LISTENING;
+            mFlags.updateAndGet(old -> old | FLAG_LISTENING);
             return;
         }
-        mFlags &= ~FLAG_LISTENING;
+        mFlags.updateAndGet(old -> old & ~FLAG_LISTENING);
     }
 
     /**
@@ -373,7 +390,7 @@
      *      as a result of using the same flow.
      */
     protected LauncherAppWidgetHostView recycleExistingView(LauncherAppWidgetHostView view) {
-        if ((mFlags & FLAG_LISTENING) == 0) {
+        if ((mFlags.get() & FLAG_LISTENING) == 0) {
             if (view instanceof PendingAppWidgetHostView pv && pv.isDeferredWidget()) {
                 return view;
             } else {
@@ -395,7 +412,7 @@
     @NonNull
     protected LauncherAppWidgetHostView createViewInternal(
             int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
-        if ((mFlags & FLAG_LISTENING) == 0) {
+        if ((mFlags.get() & FLAG_LISTENING) == 0) {
             // Since the launcher hasn't started listening to widget updates, we can't simply call
             // host.createView here because the later will make a binder call to retrieve
             // RemoteViews from system process.
@@ -460,7 +477,7 @@
      * @return True if the host is listening to the updates, false otherwise
      */
     public boolean isListening() {
-        return (mFlags & FLAG_LISTENING) != 0;
+        return (mFlags.get() & FLAG_LISTENING) != 0;
     }
 
     /**
@@ -469,16 +486,17 @@
      */
     private void setShouldListenFlag(int flag, boolean on) {
         if (on) {
-            mFlags |= flag;
+            mFlags.updateAndGet(old -> old | flag);
         } else {
-            mFlags &= ~flag;
+            mFlags.updateAndGet(old -> old & ~flag);
         }
 
         final boolean listening = isListening();
-        if (!listening && shouldListen(mFlags)) {
+        int currentFlag = mFlags.get();
+        if (!listening && shouldListen(currentFlag)) {
             // Postpone starting listening until all flags are on.
             startListening();
-        } else if (listening && (mFlags & FLAG_ACTIVITY_STARTED) == 0) {
+        } else if (listening && (currentFlag & FLAG_ACTIVITY_STARTED) == 0) {
             // Postpone stopping listening until the activity is stopped.
             stopListening();
         }
diff --git a/tests/Android.bp b/tests/Android.bp
index 1dcb2a6..e51242f 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -211,6 +211,7 @@
         "Launcher3TestResources",
         "SystemUISharedLib",
         "launcher-testing-shared",
+        "android.appwidget.flags-aconfig-java",
     ],
     libs: [
         "android.test.runner",
diff --git a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index 3405635..8770859 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -57,7 +57,7 @@
  *
  * For an implementation that mocks InvariantDeviceProfile, use [FakeInvariantDeviceProfileTest]
  */
-@AllowedDevices(allowed = [DeviceProduct.CF_PHONE, DeviceProduct.HOST_SIDE_X86_64])
+@AllowedDevices(allowed = [DeviceProduct.CF_PHONE, DeviceProduct.ROBOLECTRIC])
 @IgnoreLimit(ignoreLimit = BuildConfig.IS_STUDIO_BUILD)
 abstract class AbstractDeviceProfileTest {
     protected val testContext: Context = InstrumentationRegistry.getInstrumentation().context
@@ -341,4 +341,9 @@
     protected fun Int.dpToPx(): Int {
         return ResourceUtils.pxFromDp(this.toFloat(), context!!.resources.displayMetrics)
     }
+
+    protected fun String.xmlToId(): Int {
+        val context = InstrumentationRegistry.getInstrumentation().context
+        return context.resources.getIdentifier(this, "xml", context.packageName)
+    }
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/RoboObjectInitializer.kt b/tests/multivalentTests/src/com/android/launcher3/RoboObjectInitializer.kt
new file mode 100644
index 0000000..c5f9f86
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/RoboObjectInitializer.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 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
+
+import com.android.launcher3.util.MainThreadInitializedObject
+import com.android.launcher3.util.MainThreadInitializedObject.SandboxApplication
+import com.android.launcher3.util.SafeCloseable
+
+/**
+ * Initializes [MainThreadInitializedObject] instances for Robolectric tests.
+ *
+ * Unlike instrumentation tests, Robolectric creates a new application instance for each test, which
+ * could cause the various static objects defined in [MainThreadInitializedObject] to leak. Thus, a
+ * [SandboxApplication] for Robolectric tests can implement this interface to limit the lifecycle of
+ * these objects to a single test.
+ */
+interface RoboObjectInitializer {
+
+    /** Overrides an object with [type] to [value]. */
+    fun <T : SafeCloseable> initializeObject(type: MainThreadInitializedObject<T>, value: T)
+}
diff --git a/tests/src/com/android/launcher3/UtilitiesKtTest.kt b/tests/multivalentTests/src/com/android/launcher3/UtilitiesKtTest.kt
similarity index 86%
rename from tests/src/com/android/launcher3/UtilitiesKtTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/UtilitiesKtTest.kt
index 9aa0369..0d13e77 100644
--- a/tests/src/com/android/launcher3/UtilitiesKtTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/UtilitiesKtTest.kt
@@ -17,9 +17,8 @@
 package com.android.launcher3
 
 import android.content.Context
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
+import android.widget.LinearLayout
+import android.widget.TextView
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
@@ -27,28 +26,20 @@
 import com.android.launcher3.UtilitiesKt.CLIP_TO_PADDING_FALSE_MODIFIER
 import com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree
 import com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree
-import com.android.launcher3.tests.R
 import com.google.common.truth.Truth.assertThat
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class UtilitiesKtTest {
-
     val context: Context = InstrumentationRegistry.getInstrumentation().context
 
-    private lateinit var rootView: ViewGroup
-    private lateinit var midView: ViewGroup
-    private lateinit var childView: View
-    @Before
-    fun setup() {
-        rootView =
-            LayoutInflater.from(context).inflate(R.layout.utilities_test_view, null) as ViewGroup
-        midView = rootView.requireViewById(R.id.mid_view)
-        childView = rootView.requireViewById(R.id.child_view)
-    }
+    private val childView = TextView(context)
+
+    private val midView = LinearLayout(context).apply { addView(childView) }
+
+    private val rootView = LinearLayout(context).apply { addView(midView) }
 
     @Test
     fun set_clipChildren_false() {
diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceSettingsButtonTest.java b/tests/multivalentTests/src/com/android/launcher3/allapps/PrivateSpaceSettingsButtonTest.java
similarity index 96%
rename from tests/src/com/android/launcher3/allapps/PrivateSpaceSettingsButtonTest.java
rename to tests/multivalentTests/src/com/android/launcher3/allapps/PrivateSpaceSettingsButtonTest.java
index 9537e1c..1eb4173 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateSpaceSettingsButtonTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/allapps/PrivateSpaceSettingsButtonTest.java
@@ -24,7 +24,7 @@
 
 import android.content.Context;
 
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.util.ActivityContextWrapper;
diff --git a/tests/src/com/android/launcher3/folder/FolderNameProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/folder/FolderNameProviderTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/folder/FolderNameProviderTest.java
rename to tests/multivalentTests/src/com/android/launcher3/folder/FolderNameProviderTest.java
diff --git a/tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java b/tests/multivalentTests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
rename to tests/multivalentTests/src/com/android/launcher3/model/DbDowngradeHelperTest.java
diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/multivalentTests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
rename to tests/multivalentTests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
diff --git a/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
similarity index 93%
rename from tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
index c99da96..3dca35e 100644
--- a/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
@@ -22,7 +22,6 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.AbstractDeviceProfileTest
 import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
-import com.android.launcher3.tests.R as TestR
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -45,7 +44,7 @@
     fun parseValidFile() {
         val allAppsSpecs =
             ResponsiveSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_all_apps_file),
+                TestResourceHelper(context, "valid_all_apps_file".xmlToId()),
                 ResponsiveSpecType.AllApps
             )
 
@@ -114,7 +113,7 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_missingTag_throwsError() {
         ResponsiveSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_all_apps_file_case_1),
+            TestResourceHelper(context, "invalid_all_apps_file_case_1".xmlToId()),
             ResponsiveSpecType.AllApps
         )
     }
@@ -122,7 +121,7 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
         ResponsiveSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_all_apps_file_case_2),
+            TestResourceHelper(context, "invalid_all_apps_file_case_2".xmlToId()),
             ResponsiveSpecType.AllApps
         )
     }
@@ -130,7 +129,7 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_valueBiggerThan1_throwsError() {
         ResponsiveSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_all_apps_file_case_3),
+            TestResourceHelper(context, "invalid_all_apps_file_case_3".xmlToId()),
             ResponsiveSpecType.AllApps
         )
     }
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
similarity index 94%
rename from tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
index 1cc5ed2..8346492 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
@@ -23,7 +23,6 @@
 import com.android.launcher3.AbstractDeviceProfileTest
 import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
 import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
-import com.android.launcher3.tests.R as TestR
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
@@ -52,7 +51,7 @@
 
         val workspaceSpecs =
             ResponsiveSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_workspace_file),
+                TestResourceHelper(context, "valid_workspace_file".xmlToId()),
                 ResponsiveSpecType.Workspace
             )
         val widthSpec =
@@ -62,7 +61,7 @@
 
         val allAppsSpecs =
             ResponsiveSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_all_apps_file),
+                TestResourceHelper(context, "valid_all_apps_file".xmlToId()),
                 ResponsiveSpecType.AllApps
             )
 
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
similarity index 95%
rename from tests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
index c4e2d2a..46d6cb9 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedFolderSpecTest.kt
@@ -23,7 +23,6 @@
 import com.android.launcher3.AbstractDeviceProfileTest
 import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
 import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
-import com.android.launcher3.tests.R
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -47,12 +46,12 @@
         val columns = 6
 
         // Loading workspace specs
-        val resourceHelperWorkspace = TestResourceHelper(context, R.xml.valid_workspace_file)
+        val resourceHelperWorkspace = TestResourceHelper(context, "valid_workspace_file".xmlToId())
         val workspaceSpecs =
             ResponsiveSpecsProvider.create(resourceHelperWorkspace, ResponsiveSpecType.Workspace)
 
         // Loading folders specs
-        val resourceHelperFolder = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val resourceHelperFolder = TestResourceHelper(context, "valid_folders_specs".xmlToId())
         val folderSpecs =
             ResponsiveSpecsProvider.create(resourceHelperFolder, ResponsiveSpecType.Folder)
         val specs = folderSpecs.getSpecsByAspectRatio(aspectRatio)
@@ -123,12 +122,12 @@
         val rows = 5
 
         // Loading workspace specs
-        val resourceHelperWorkspace = TestResourceHelper(context, R.xml.valid_workspace_file)
+        val resourceHelperWorkspace = TestResourceHelper(context, "valid_workspace_file".xmlToId())
         val workspaceSpecs =
             ResponsiveSpecsProvider.create(resourceHelperWorkspace, ResponsiveSpecType.Workspace)
 
         // Loading folders specs
-        val resourceHelperFolder = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val resourceHelperFolder = TestResourceHelper(context, "valid_folders_specs".xmlToId())
         val folderSpecs =
             ResponsiveSpecsProvider.create(resourceHelperFolder, ResponsiveSpecType.Folder)
         val specs = folderSpecs.getSpecsByAspectRatio(aspectRatio)
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
similarity index 95%
rename from tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
index 1a564ac..5ab44f3 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedHotseatSpecTest.kt
@@ -22,7 +22,6 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.AbstractDeviceProfileTest
 import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
-import com.android.launcher3.tests.R as TestR
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
@@ -47,7 +46,7 @@
         val availableHeight = deviceSpec.naturalSize.second
 
         val hotseatSpecsProvider =
-            HotseatSpecsProvider.create(TestResourceHelper(context, TestR.xml.valid_hotseat_file))
+            HotseatSpecsProvider.create(TestResourceHelper(context, "valid_hotseat_file".xmlToId()))
         val heightSpec =
             hotseatSpecsProvider.getCalculatedSpec(
                 aspectRatio,
@@ -73,7 +72,7 @@
         val availableHeight = deviceSpec.naturalSize.second
 
         val hotseatSpecsProvider =
-            HotseatSpecsProvider.create(TestResourceHelper(context, TestR.xml.valid_hotseat_file))
+            HotseatSpecsProvider.create(TestResourceHelper(context, "valid_hotseat_file".xmlToId()))
         val heightSpec =
             hotseatSpecsProvider.getCalculatedSpec(
                 aspectRatio,
@@ -99,7 +98,7 @@
 
         val hotseatSpecsProvider =
             HotseatSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_hotseat_land_file)
+                TestResourceHelper(context, "valid_hotseat_land_file".xmlToId())
             )
         val widthSpec =
             hotseatSpecsProvider.getCalculatedSpec(aspectRatio, DimensionType.WIDTH, availableWidth)
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
similarity index 95%
rename from tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
index 0c5d347..dea98b6 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
@@ -23,7 +23,6 @@
 import com.android.launcher3.AbstractDeviceProfileTest
 import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
 import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
-import com.android.launcher3.tests.R as TestR
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
@@ -53,7 +52,7 @@
 
         val workspaceSpecs =
             ResponsiveSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_workspace_file),
+                TestResourceHelper(context, "valid_workspace_file".xmlToId()),
                 ResponsiveSpecType.Workspace
             )
         val widthSpec =
@@ -96,7 +95,7 @@
 
         val workspaceSpecs =
             ResponsiveSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_workspace_file),
+                TestResourceHelper(context, "valid_workspace_file".xmlToId()),
                 ResponsiveSpecType.Workspace
             )
         val widthSpec =
@@ -138,7 +137,7 @@
         val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 640
         val workspaceSpecs =
             ResponsiveSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_workspace_unsorted_file),
+                TestResourceHelper(context, "valid_workspace_unsorted_file".xmlToId()),
                 ResponsiveSpecType.Workspace
             )
         val widthSpec =
diff --git a/tests/src/com/android/launcher3/responsive/FolderSpecTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/FolderSpecTest.kt
similarity index 91%
rename from tests/src/com/android/launcher3/responsive/FolderSpecTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/FolderSpecTest.kt
index 5cfa49f..d2b264b 100644
--- a/tests/src/com/android/launcher3/responsive/FolderSpecTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/FolderSpecTest.kt
@@ -23,7 +23,6 @@
 import com.android.launcher3.AbstractDeviceProfileTest
 import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
 import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
-import com.android.launcher3.tests.R
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -44,7 +43,7 @@
 
     @Test
     fun parseValidFile() {
-        val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val resourceHelper = TestResourceHelper(context, "valid_folders_specs".xmlToId())
         val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
         val specs = folderSpecs.getSpecsByAspectRatio(aspectRatio)
 
@@ -92,25 +91,25 @@
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_missingTag_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_1)
+        val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_1".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_2)
+        val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_2".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_valueBiggerThan1_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_3)
+        val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_3".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_missingSpecs_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_4)
+        val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_4".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
     }
 
@@ -132,7 +131,7 @@
         val calculatedWorkspaceSpec =
             CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
 
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_5)
+        val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_5".xmlToId())
         val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
         folderSpecs.getCalculatedSpec(
             aspectRatio,
@@ -161,7 +160,7 @@
         val calculatedWorkspaceSpec =
             CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
 
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_folders_specs_5)
+        val resourceHelper = TestResourceHelper(context, "invalid_folders_specs_5".xmlToId())
         val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
         folderSpecs.getCalculatedSpec(
             aspectRatio,
@@ -190,7 +189,7 @@
         val calculatedWorkspaceSpec =
             CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
 
-        val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val resourceHelper = TestResourceHelper(context, "valid_folders_specs".xmlToId())
         val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
         val calculatedWidthSpec =
             folderSpecs.getCalculatedSpec(
@@ -227,7 +226,7 @@
         val calculatedWorkspaceSpec =
             CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
 
-        val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val resourceHelper = TestResourceHelper(context, "valid_folders_specs".xmlToId())
         val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
         folderSpecs.getCalculatedSpec(
             aspectRatio,
@@ -256,7 +255,7 @@
         val calculatedWorkspaceSpec =
             CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
 
-        val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val resourceHelper = TestResourceHelper(context, "valid_folders_specs".xmlToId())
         val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
         val calculatedHeightSpec =
             folderSpecs.getCalculatedSpec(
@@ -293,7 +292,7 @@
         val calculatedWorkspaceSpec =
             CalculatedResponsiveSpec(aspectRatio, availableSpace, cells, workspaceSpec)
 
-        val resourceHelper = TestResourceHelper(context, R.xml.valid_folders_specs)
+        val resourceHelper = TestResourceHelper(context, "valid_folders_specs".xmlToId())
         val folderSpecs = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Folder)
         folderSpecs.getCalculatedSpec(
             aspectRatio,
diff --git a/tests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
similarity index 93%
rename from tests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
index 78cb1ac..58324e1 100644
--- a/tests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/HotseatSpecsProviderTest.kt
@@ -22,7 +22,6 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.AbstractDeviceProfileTest
 import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
-import com.android.launcher3.tests.R as TestR
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -44,7 +43,7 @@
     @Test
     fun parseValidFile() {
         val hotseatSpecsProvider =
-            HotseatSpecsProvider.create(TestResourceHelper(context, TestR.xml.valid_hotseat_file))
+            HotseatSpecsProvider.create(TestResourceHelper(context, "valid_hotseat_file".xmlToId()))
         val specs = hotseatSpecsProvider.getSpecsByAspectRatio(aspectRatio)
 
         val expectedHeightSpecs =
@@ -76,7 +75,7 @@
     fun parseValidLandscapeFile() {
         val hotseatSpecsProvider =
             HotseatSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_hotseat_land_file)
+                TestResourceHelper(context, "valid_hotseat_land_file".xmlToId())
             )
         val specs = hotseatSpecsProvider.getSpecsByAspectRatio(aspectRatio)
         assertThat(specs.heightSpecs.size).isEqualTo(0)
@@ -107,14 +106,14 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_spaceIsNotFixedSize_throwsError() {
         HotseatSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_hotseat_file_case_1)
+            TestResourceHelper(context, "invalid_hotseat_file_case_1".xmlToId())
         )
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_invalidFixedSize_throwsError() {
         HotseatSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_hotseat_file_case_2)
+            TestResourceHelper(context, "invalid_hotseat_file_case_2".xmlToId())
         )
     }
 }
diff --git a/tests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
similarity index 92%
rename from tests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
index 50cd358..11161bd 100644
--- a/tests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveCellSpecsProviderTest.kt
@@ -22,7 +22,6 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.AbstractDeviceProfileTest
 import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
-import com.android.launcher3.tests.R as TestR
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -43,7 +42,7 @@
 
     @Test
     fun parseValidFile() {
-        val testResourceHelper = TestResourceHelper(context, TestR.xml.valid_cell_specs_file)
+        val testResourceHelper = TestResourceHelper(context, "valid_cell_specs_file".xmlToId())
         val provider = ResponsiveCellSpecsProvider.create(testResourceHelper)
 
         // Validate Portrait
@@ -98,21 +97,21 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_IsNotFixedSizeOrMatchWorkspace_throwsError() {
         ResponsiveCellSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_cell_specs_1)
+            TestResourceHelper(context, "invalid_cell_specs_1".xmlToId())
         )
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_dimensionTypeIsNotHeight_throwsError() {
         ResponsiveCellSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_cell_specs_2)
+            TestResourceHelper(context, "invalid_cell_specs_2".xmlToId())
         )
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_invalidFixedSize_throwsError() {
         ResponsiveCellSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_cell_specs_3)
+            TestResourceHelper(context, "invalid_cell_specs_3".xmlToId())
         )
     }
 }
diff --git a/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
similarity index 93%
rename from tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
index 54a1dc5..c74f7a8 100644
--- a/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
@@ -23,7 +23,6 @@
 import com.android.launcher3.AbstractDeviceProfileTest
 import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
 import com.android.launcher3.responsive.ResponsiveSpec.DimensionType
-import com.android.launcher3.tests.R
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -44,7 +43,7 @@
 
     @Test
     fun parseValidFile() {
-        val resourceHelper = TestResourceHelper(context, R.xml.valid_responsive_spec_unsorted)
+        val resourceHelper = TestResourceHelper(context, "valid_responsive_spec_unsorted".xmlToId())
         val provider = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
 
         // Validate Portrait
@@ -111,44 +110,44 @@
 
     @Test(expected = IllegalStateException::class)
     fun parseValidFile_invalidAspectRatio_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.valid_responsive_spec_unsorted)
+        val resourceHelper = TestResourceHelper(context, "valid_responsive_spec_unsorted".xmlToId())
         val provider = ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
         provider.getSpecsByAspectRatio(0f)
     }
 
     @Test(expected = InvalidResponsiveGridSpec::class)
     fun parseInvalidFile_missingGroups_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_1)
+        val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_1".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
     }
 
     @Test(expected = InvalidResponsiveGridSpec::class)
     fun parseInvalidFile_partialGroups_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_2)
+        val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_2".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_invalidAspectRatio_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_3)
+        val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_3".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_invalidRemainderSpace_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_4)
+        val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_4".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_invalidAvailableSpace_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_5)
+        val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_5".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
     }
 
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_invalidFixedSize_throwsError() {
-        val resourceHelper = TestResourceHelper(context, R.xml.invalid_responsive_spec_6)
+        val resourceHelper = TestResourceHelper(context, "invalid_responsive_spec_6".xmlToId())
         ResponsiveSpecsProvider.create(resourceHelper, ResponsiveSpecType.Workspace)
     }
 
diff --git a/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt b/tests/multivalentTests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
similarity index 94%
rename from tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
index 17b0ee4..bc133ba 100644
--- a/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
@@ -22,7 +22,6 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.AbstractDeviceProfileTest
 import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType
-import com.android.launcher3.tests.R as TestR
 import com.android.launcher3.util.TestResourceHelper
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -45,7 +44,7 @@
     fun parseValidFile() {
         val workspaceSpecs =
             ResponsiveSpecsProvider.create(
-                TestResourceHelper(context, TestR.xml.valid_workspace_file),
+                TestResourceHelper(context, "valid_workspace_file".xmlToId()),
                 ResponsiveSpecType.Workspace
             )
 
@@ -169,7 +168,7 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_missingTag_throwsError() {
         ResponsiveSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_workspace_file_case_1),
+            TestResourceHelper(context, "invalid_workspace_file_case_1".xmlToId()),
             ResponsiveSpecType.Workspace
         )
     }
@@ -177,7 +176,7 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
         ResponsiveSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_workspace_file_case_2),
+            TestResourceHelper(context, "invalid_workspace_file_case_2".xmlToId()),
             ResponsiveSpecType.Workspace
         )
     }
@@ -185,7 +184,7 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_valueBiggerThan1_throwsError() {
         ResponsiveSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_workspace_file_case_3),
+            TestResourceHelper(context, "invalid_workspace_file_case_3".xmlToId()),
             ResponsiveSpecType.Workspace
         )
     }
@@ -193,7 +192,7 @@
     @Test(expected = IllegalStateException::class)
     fun parseInvalidFile_matchWorkspace_true_throwsError() {
         ResponsiveSpecsProvider.create(
-            TestResourceHelper(context, TestR.xml.invalid_workspace_file_case_4),
+            TestResourceHelper(context, "invalid_workspace_file_case_4".xmlToId()),
             ResponsiveSpecType.Workspace
         )
     }
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestResourceHelper.kt b/tests/multivalentTests/src/com/android/launcher3/util/TestResourceHelper.kt
new file mode 100644
index 0000000..d95c6f8
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestResourceHelper.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.Context
+import android.content.res.TypedArray
+import android.util.AttributeSet
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.R
+import kotlin.IntArray
+
+class TestResourceHelper(private val context: Context, specsFileId: Int) :
+    ResourceHelper(context, specsFileId) {
+
+    val responsiveStyleables = listOf(
+            R.styleable.SizeSpec,
+            R.styleable.WorkspaceSpec,
+            R.styleable.FolderSpec,
+            R.styleable.AllAppsSpec,
+            R.styleable.ResponsiveSpecGroup
+    )
+
+    override fun obtainStyledAttributes(attrs: AttributeSet, styleId: IntArray): TypedArray {
+        val clone =
+                if (responsiveStyleables.any { styleId.contentEquals(it) }) {
+                    convertStyleId(styleId)
+                } else {
+                    styleId.clone()
+                }
+
+        return context.obtainStyledAttributes(attrs, clone)
+    }
+
+    private fun convertStyleId(styleableArr: IntArray): IntArray {
+        val targetContextRes = getInstrumentation().targetContext.resources
+        val context = getInstrumentation().context
+        return styleableArr
+            .map { attrId -> targetContextRes.getResourceName(attrId).split(":").last() }
+            .map { attrName ->
+                // Get required attr from context instead of targetContext
+                context.resources.getIdentifier(attrName, null, context.packageName)
+            }
+            .toIntArray()
+    }
+}
diff --git a/tests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
similarity index 94%
rename from tests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
index 460058b..b239aed 100644
--- a/tests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
@@ -18,12 +18,12 @@
 import androidx.test.core.app.ApplicationProvider.getApplicationContext
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import com.android.launcher3.Flags.FLAG_ENABLE_GENERATED_PREVIEWS
 import com.android.launcher3.InvariantDeviceProfile
 import com.android.launcher3.icons.IconCache
 import com.android.launcher3.icons.IconProvider
 import com.android.launcher3.model.WidgetItem
-import com.android.launcher3.tests.R
 import com.android.launcher3.util.ActivityContextWrapper
 import com.android.launcher3.util.Executors
 import com.google.common.truth.Truth.assertThat
@@ -41,7 +41,10 @@
             "com.android.launcher3.tests",
             "com.android.launcher3.testcomponent.AppWidgetNoConfig"
         )
-    private val generatedPreviewLayout = R.layout.test_layout_appwidget_blue
+    private val generatedPreviewLayout =
+        getInstrumentation().context.run {
+            resources.getIdentifier("test_layout_appwidget_blue", "layout", packageName)
+        }
     private lateinit var context: Context
     private lateinit var generatedPreview: RemoteViews
     private lateinit var widgetCell: WidgetCell
@@ -137,6 +140,7 @@
         assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_KEYGUARD)).isFalse()
         assertThat(widgetItem.hasGeneratedPreview(WIDGET_CATEGORY_SEARCHBOX)).isFalse()
     }
+
     @Test
     @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
     fun widgetItem_getGeneratedPreview() {
@@ -148,6 +152,7 @@
     @RequiresFlagsEnabled(FLAG_ENABLE_GENERATED_PREVIEWS)
     fun widgetCell_showGeneratedPreview() {
         widgetCell.applyFromCellItem(widgetItem)
+        DatabaseWidgetPreviewLoader.getLoaderExecutor().submit {}.get()
         assertThat(widgetCell.appWidgetHostViewPreview).isNotNull()
         assertThat(widgetCell.appWidgetHostViewPreview?.appWidgetInfo)
             .isEqualTo(appWidgetProviderInfo)
@@ -157,6 +162,7 @@
     @RequiresFlagsDisabled(FLAG_ENABLE_GENERATED_PREVIEWS)
     fun widgetCell_showGeneratedPreview_flagDisabled() {
         widgetCell.applyFromCellItem(widgetItem)
+        DatabaseWidgetPreviewLoader.getLoaderExecutor().submit {}.get()
         assertThat(widgetCell.appWidgetHostViewPreview).isNull()
     }
 }
diff --git a/tests/res/layout/utilities_test_view.xml b/tests/res/layout/utilities_test_view.xml
deleted file mode 100644
index dc2a515..0000000
--- a/tests/res/layout/utilities_test_view.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  ~ Copyright (C) 2024 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.
-  -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:id="@+id/root_view">
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:id="@+id/mid_view">
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:id="@+id/child_view" />
-    </LinearLayout>
-
-</LinearLayout>
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
index 362596c..405dae7 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
@@ -159,13 +159,13 @@
                     mLauncher.getWorkspace().getWorkspaceIconsPositions();
             assertThat(initialPositions.keySet()).containsAtLeastElementsIn(appNames);
 
-            mLauncher.getWorkspace().getWorkspaceAppIcon(DUMMY_APP_NAME).uninstall();
-            mLauncher.getWorkspace().verifyWorkspaceAppIconIsGone(
+            final Workspace workspace = mLauncher.getWorkspace().getWorkspaceAppIcon(
+                    DUMMY_APP_NAME).uninstall();
+            workspace.verifyWorkspaceAppIconIsGone(
                     DUMMY_APP_NAME + " was expected to disappear after uninstall.", DUMMY_APP_NAME);
 
             Log.d(UIOBJECT_STALE_ELEMENT, "second getWorkspaceIconsPositions()");
-            Map<String, Point> finalPositions =
-                    mLauncher.getWorkspace().getWorkspaceIconsPositions();
+            Map<String, Point> finalPositions = workspace.getWorkspaceIconsPositions();
             assertThat(finalPositions).doesNotContainKey(DUMMY_APP_NAME);
         } finally {
             TestUtil.uninstallDummyApp();
diff --git a/tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt b/tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
new file mode 100644
index 0000000..aadf72e
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/FirstScreenBroadcastHelperTest.kt
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2024 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.model
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.PackageInstaller.SessionInfo
+import android.os.UserHandle
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
+import com.android.launcher3.model.FirstScreenBroadcastHelper.MAX_BROADCAST_SIZE
+import com.android.launcher3.model.FirstScreenBroadcastHelper.getTotalItemCount
+import com.android.launcher3.model.FirstScreenBroadcastHelper.truncateModelForBroadcast
+import com.android.launcher3.model.data.FolderInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.PackageManagerHelper
+import com.android.launcher3.util.PackageUserKey
+import junit.framework.Assert.assertEquals
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+class FirstScreenBroadcastHelperTest {
+    private val context = spy(InstrumentationRegistry.getInstrumentation().targetContext)
+    private val mockPmHelper = mock<PackageManagerHelper>()
+    private val expectedAppPackage = "appPackageExpected"
+    private val expectedComponentName = ComponentName(expectedAppPackage, "expectedClass")
+    private val expectedInstallerPackage = "installerPackage"
+    private val expectedIntent =
+        Intent().apply {
+            component = expectedComponentName
+            setPackage(expectedAppPackage)
+        }
+    private val unexpectedAppPackage = "appPackageUnexpected"
+    private val unexpectedComponentName = ComponentName(expectedAppPackage, "unexpectedClass")
+    private val firstScreenItems =
+        listOf(
+            WorkspaceItemInfo().apply {
+                container = CONTAINER_DESKTOP
+                intent = expectedIntent
+            },
+            WorkspaceItemInfo().apply {
+                container = CONTAINER_HOTSEAT
+                intent = expectedIntent
+            },
+            LauncherAppWidgetInfo().apply { providerName = expectedComponentName }
+        )
+
+    @Test
+    fun `Broadcast Models are created with Pending Items from first screen`() {
+        // Given
+        val sessionInfoExpected =
+            SessionInfo().apply {
+                installerPackageName = expectedInstallerPackage
+                appPackageName = expectedAppPackage
+            }
+        val sessionInfoUnexpected =
+            SessionInfo().apply {
+                installerPackageName = expectedInstallerPackage
+                appPackageName = unexpectedAppPackage
+            }
+        val sessionInfoMap: HashMap<PackageUserKey, SessionInfo> =
+            hashMapOf(
+                PackageUserKey(unexpectedAppPackage, UserHandle(0)) to sessionInfoExpected,
+                PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected
+            )
+
+        // When
+        val actualResult =
+            FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                packageManagerHelper = mockPmHelper,
+                firstScreenItems = firstScreenItems,
+                userKeyToSessionMap = sessionInfoMap,
+                allWidgets = listOf()
+            )
+
+        // Then
+        val expectedResult =
+            listOf(
+                FirstScreenBroadcastModel(
+                    installerPackage = expectedInstallerPackage,
+                    pendingWorkspaceItems = mutableSetOf(expectedAppPackage),
+                    pendingHotseatItems = mutableSetOf(expectedAppPackage),
+                    pendingWidgetItems = mutableSetOf(expectedAppPackage)
+                )
+            )
+
+        assertEquals(expectedResult, actualResult)
+    }
+
+    @Test
+    fun `Broadcast Models are created with Installed Items from first screen`() {
+        // Given
+        whenever(mockPmHelper.getAppInstallerPackage(expectedAppPackage))
+            .thenReturn(expectedInstallerPackage)
+
+        // When
+        val actualResult =
+            FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                packageManagerHelper = mockPmHelper,
+                firstScreenItems = firstScreenItems,
+                userKeyToSessionMap = hashMapOf(),
+                allWidgets =
+                    listOf(
+                        LauncherAppWidgetInfo().apply {
+                            providerName = expectedComponentName
+                            screenId = 0
+                        }
+                    )
+            )
+
+        // Then
+        val expectedResult =
+            listOf(
+                FirstScreenBroadcastModel(
+                    installerPackage = expectedInstallerPackage,
+                    installedHotseatItems = mutableSetOf(expectedAppPackage),
+                    installedWorkspaceItems = mutableSetOf(expectedAppPackage),
+                    firstScreenInstalledWidgets = mutableSetOf(expectedAppPackage)
+                )
+            )
+        assertEquals(expectedResult, actualResult)
+    }
+
+    @Test
+    fun `Broadcast Models are created with Installed Widgets from every screen`() {
+        // Given
+        val expectedAppPackage2 = "appPackageExpected2"
+        val expectedComponentName2 = ComponentName(expectedAppPackage2, "expectedClass2")
+        whenever(mockPmHelper.getAppInstallerPackage(expectedAppPackage))
+            .thenReturn(expectedInstallerPackage)
+        whenever(mockPmHelper.getAppInstallerPackage(expectedAppPackage2))
+            .thenReturn(expectedInstallerPackage)
+
+        // When
+        val actualResult =
+            FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                packageManagerHelper = mockPmHelper,
+                firstScreenItems = listOf(),
+                userKeyToSessionMap = hashMapOf(),
+                allWidgets =
+                    listOf(
+                        LauncherAppWidgetInfo().apply {
+                            providerName = expectedComponentName
+                            screenId = 0
+                        },
+                        LauncherAppWidgetInfo().apply {
+                            providerName = expectedComponentName2
+                            screenId = 1
+                        },
+                        LauncherAppWidgetInfo().apply {
+                            providerName = unexpectedComponentName
+                            screenId = 0
+                        }
+                    )
+            )
+
+        // Then
+        val expectedResult =
+            listOf(
+                FirstScreenBroadcastModel(
+                    installerPackage = expectedInstallerPackage,
+                    installedHotseatItems = mutableSetOf(),
+                    installedWorkspaceItems = mutableSetOf(),
+                    firstScreenInstalledWidgets = mutableSetOf(expectedAppPackage),
+                    secondaryScreenInstalledWidgets = mutableSetOf(expectedAppPackage2)
+                )
+            )
+        assertEquals(expectedResult, actualResult)
+    }
+
+    @Test
+    fun `Broadcast Models are created with Pending Items in Collections from the first screen`() {
+        // Given
+        val sessionInfoExpected =
+            SessionInfo().apply {
+                installerPackageName = expectedInstallerPackage
+                appPackageName = expectedAppPackage
+            }
+        val sessionInfoUnexpected =
+            SessionInfo().apply {
+                installerPackageName = expectedInstallerPackage
+                appPackageName = unexpectedAppPackage
+            }
+        val sessionInfoMap: HashMap<PackageUserKey, SessionInfo> =
+            hashMapOf(
+                PackageUserKey(unexpectedAppPackage, UserHandle(0)) to sessionInfoExpected,
+                PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected,
+            )
+        val expectedItemInfo = WorkspaceItemInfo().apply { intent = expectedIntent }
+        val expectedFolderInfo = FolderInfo().apply { add(expectedItemInfo) }
+        val firstScreenItems = listOf(expectedFolderInfo)
+
+        // When
+        val actualResult =
+            FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                packageManagerHelper = mockPmHelper,
+                firstScreenItems = firstScreenItems,
+                userKeyToSessionMap = sessionInfoMap,
+                allWidgets = listOf()
+            )
+
+        // Then
+        val expectedResult =
+            listOf(
+                FirstScreenBroadcastModel(
+                    installerPackage = expectedInstallerPackage,
+                    pendingCollectionItems = mutableSetOf(expectedAppPackage)
+                )
+            )
+        assertEquals(expectedResult, actualResult)
+    }
+
+    @Test
+    fun `Models with too many items get truncated to max Broadcast size`() {
+        // given
+        val broadcastModel =
+            FirstScreenBroadcastModel(
+                installerPackage = expectedInstallerPackage,
+                pendingCollectionItems =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+                pendingWorkspaceItems =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+                pendingHotseatItems =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+                pendingWidgetItems =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+                installedWorkspaceItems =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+                installedHotseatItems =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+                firstScreenInstalledWidgets =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+                secondaryScreenInstalledWidgets =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }
+            )
+
+        // When
+        broadcastModel.truncateModelForBroadcast()
+
+        // Then
+        assertEquals(MAX_BROADCAST_SIZE, broadcastModel.getTotalItemCount())
+    }
+
+    @Test
+    fun `Broadcast truncates installed Hotseat items before other installed items`() {
+        // Given
+        val broadcastModel =
+            FirstScreenBroadcastModel(
+                installerPackage = expectedInstallerPackage,
+                installedWorkspaceItems =
+                    mutableSetOf<String>().apply { repeat(50) { add(it.toString()) } },
+                firstScreenInstalledWidgets =
+                    mutableSetOf<String>().apply { repeat(10) { add(it.toString()) } },
+                secondaryScreenInstalledWidgets =
+                    mutableSetOf<String>().apply { repeat(10) { add((it + 10).toString()) } },
+                installedHotseatItems =
+                    mutableSetOf<String>().apply { repeat(10) { add(it.toString()) } },
+            )
+
+        // When
+        broadcastModel.truncateModelForBroadcast()
+
+        // Then
+        assertEquals(MAX_BROADCAST_SIZE, broadcastModel.getTotalItemCount())
+        assertEquals(50, broadcastModel.installedWorkspaceItems.size)
+        assertEquals(10, broadcastModel.firstScreenInstalledWidgets.size)
+        assertEquals(10, broadcastModel.secondaryScreenInstalledWidgets.size)
+        assertEquals(0, broadcastModel.installedHotseatItems.size)
+    }
+
+    @Test
+    fun `Broadcast truncates Widgets before the rest of the first screen items`() {
+        // Given
+        val broadcastModel =
+            FirstScreenBroadcastModel(
+                installerPackage = expectedInstallerPackage,
+                installedWorkspaceItems =
+                    mutableSetOf<String>().apply { repeat(70) { add(it.toString()) } },
+                firstScreenInstalledWidgets =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+                secondaryScreenInstalledWidgets =
+                    mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } },
+            )
+
+        // When
+        broadcastModel.truncateModelForBroadcast()
+
+        // Then
+        assertEquals(MAX_BROADCAST_SIZE, broadcastModel.getTotalItemCount())
+        assertEquals(70, broadcastModel.installedWorkspaceItems.size)
+        assertEquals(0, broadcastModel.firstScreenInstalledWidgets.size)
+        assertEquals(0, broadcastModel.secondaryScreenInstalledWidgets.size)
+    }
+
+    @Test
+    fun `Broadcasts are correctly formed with Extras for each Installer`() {
+        // Given
+        val broadcastModels: List<FirstScreenBroadcastModel> =
+            listOf(
+                FirstScreenBroadcastModel(
+                    installerPackage = expectedInstallerPackage,
+                    pendingCollectionItems = mutableSetOf("pendingCollectionItem"),
+                    pendingWorkspaceItems = mutableSetOf("pendingWorkspaceItem"),
+                    pendingHotseatItems = mutableSetOf("pendingHotseatItems"),
+                    pendingWidgetItems = mutableSetOf("pendingWidgetItems"),
+                    installedWorkspaceItems = mutableSetOf("installedWorkspaceItems"),
+                    installedHotseatItems = mutableSetOf("installedHotseatItems"),
+                    firstScreenInstalledWidgets = mutableSetOf("firstScreenInstalledWidgetItems"),
+                    secondaryScreenInstalledWidgets = mutableSetOf("secondaryInstalledWidgetItems")
+                )
+            )
+        val expectedPendingIntent =
+            PendingIntent.getActivity(
+                context,
+                0 /* requestCode */,
+                Intent(),
+                PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
+            )
+
+        // When
+        FirstScreenBroadcastHelper.sendBroadcastsForModels(context, broadcastModels)
+
+        // Then
+        val argumentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(context).sendBroadcast(argumentCaptor.capture())
+
+        assertEquals(
+            "com.android.launcher3.action.FIRST_SCREEN_ACTIVE_INSTALLS",
+            argumentCaptor.value.action
+        )
+        assertEquals(expectedInstallerPackage, argumentCaptor.value.`package`)
+        assertEquals(
+            expectedPendingIntent,
+            argumentCaptor.value.getParcelableExtra("verificationToken")
+        )
+        assertEquals(
+            arrayListOf("pendingCollectionItem"),
+            argumentCaptor.value.getStringArrayListExtra("folderItem")
+        )
+        assertEquals(
+            arrayListOf("pendingWorkspaceItem"),
+            argumentCaptor.value.getStringArrayListExtra("workspaceItem")
+        )
+        assertEquals(
+            arrayListOf("pendingHotseatItems"),
+            argumentCaptor.value.getStringArrayListExtra("hotseatItem")
+        )
+        assertEquals(
+            arrayListOf("pendingWidgetItems"),
+            argumentCaptor.value.getStringArrayListExtra("widgetItem")
+        )
+        assertEquals(
+            arrayListOf("installedWorkspaceItems"),
+            argumentCaptor.value.getStringArrayListExtra("workspaceInstalledItems")
+        )
+        assertEquals(
+            arrayListOf("installedHotseatItems"),
+            argumentCaptor.value.getStringArrayListExtra("hotseatInstalledItems")
+        )
+        assertEquals(
+            arrayListOf("firstScreenInstalledWidgetItems", "secondaryInstalledWidgetItems"),
+            argumentCaptor.value.getStringArrayListExtra("widgetInstalledItems")
+        )
+    }
+}
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index 28a001f..d16674c 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -1,10 +1,13 @@
 package com.android.launcher3.model
 
 import android.appwidget.AppWidgetManager
+import android.content.Intent
 import android.os.UserHandle
 import android.platform.test.flag.junit.SetFlagsRule
+import android.provider.Settings
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
 import com.android.launcher3.Flags
 import com.android.launcher3.InvariantDeviceProfile
 import com.android.launcher3.LauncherAppState
@@ -14,6 +17,7 @@
 import com.android.launcher3.icons.cache.CachingLogic
 import com.android.launcher3.icons.cache.IconCacheUpdateHandler
 import com.android.launcher3.pm.UserCache
+import com.android.launcher3.provider.RestoreDbTask
 import com.android.launcher3.ui.TestViewHelpers
 import com.android.launcher3.util.Executors.MODEL_EXECUTOR
 import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
@@ -21,21 +25,30 @@
 import com.android.launcher3.util.UserIconInfo
 import com.google.common.truth.Truth
 import java.util.concurrent.CountDownLatch
+import junit.framework.Assert.assertEquals
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyList
+import org.mockito.ArgumentMatchers.anyMap
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import org.mockito.MockitoSession
 import org.mockito.Spy
+import org.mockito.kotlin.anyOrNull
 import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
 
 private const val INSERTION_STATEMENT_FILE = "databases/workspace_items.sql"
 
@@ -43,6 +56,20 @@
 @RunWith(AndroidJUnit4::class)
 class LoaderTaskTest {
     private var context = SandboxModelContext()
+    private val expectedBroadcastModel =
+        FirstScreenBroadcastModel(
+            installerPackage = "installerPackage",
+            pendingCollectionItems = mutableSetOf("pendingCollectionItem"),
+            pendingWidgetItems = mutableSetOf("pendingWidgetItem"),
+            pendingHotseatItems = mutableSetOf("pendingHotseatItem"),
+            pendingWorkspaceItems = mutableSetOf("pendingWorkspaceItem"),
+            installedHotseatItems = mutableSetOf("installedHotseatItem"),
+            installedWorkspaceItems = mutableSetOf("installedWorkspaceItem"),
+            firstScreenInstalledWidgets = mutableSetOf("installedFirstScreenWidget"),
+            secondaryScreenInstalledWidgets = mutableSetOf("installedSecondaryScreenWidget")
+        )
+    private lateinit var mockitoSession: MockitoSession
+
     @Mock private lateinit var app: LauncherAppState
     @Mock private lateinit var bgAllAppsList: AllAppsList
     @Mock private lateinit var modelDelegate: ModelDelegate
@@ -61,7 +88,11 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-
+        mockitoSession =
+            ExtendedMockito.mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .mockStatic(FirstScreenBroadcastHelper::class.java)
+                .startMocking()
         val idp =
             InvariantDeviceProfile().apply {
                 numRows = 5
@@ -90,6 +121,7 @@
     @After
     fun tearDown() {
         context.onDestroy()
+        mockitoSession.finishMocking()
     }
 
     @Test
@@ -166,6 +198,141 @@
             verify(bgAllAppsList, Mockito.never())
                 .setFlags(BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED, true)
         }
+
+    @Test
+    fun `When launcher_broadcast_installed_apps and is restore then send installed item broadcast`() {
+        // Given
+        val spyContext = spy(context)
+        `when`(app.context).thenReturn(spyContext)
+        whenever(
+                FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                    anyOrNull(),
+                    anyList(),
+                    anyMap(),
+                    anyList()
+                )
+            )
+            .thenReturn(listOf(expectedBroadcastModel))
+
+        whenever(
+                FirstScreenBroadcastHelper.sendBroadcastsForModels(
+                    spyContext,
+                    listOf(expectedBroadcastModel)
+                )
+            )
+            .thenCallRealMethod()
+
+        Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 1)
+        RestoreDbTask.setPending(spyContext)
+
+        // When
+        LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
+            .runSyncOnBackgroundThread()
+
+        // Then
+        val argumentCaptor = ArgumentCaptor.forClass(Intent::class.java)
+        verify(spyContext).sendBroadcast(argumentCaptor.capture())
+        val actualBroadcastIntent = argumentCaptor.value
+        assertEquals(expectedBroadcastModel.installerPackage, actualBroadcastIntent.`package`)
+        assertEquals(
+            ArrayList(expectedBroadcastModel.installedWorkspaceItems),
+            actualBroadcastIntent.getStringArrayListExtra("workspaceInstalledItems")
+        )
+        assertEquals(
+            ArrayList(expectedBroadcastModel.installedHotseatItems),
+            actualBroadcastIntent.getStringArrayListExtra("hotseatInstalledItems")
+        )
+        assertEquals(
+            ArrayList(
+                expectedBroadcastModel.firstScreenInstalledWidgets +
+                    expectedBroadcastModel.secondaryScreenInstalledWidgets
+            ),
+            actualBroadcastIntent.getStringArrayListExtra("widgetInstalledItems")
+        )
+        assertEquals(
+            ArrayList(expectedBroadcastModel.pendingCollectionItems),
+            actualBroadcastIntent.getStringArrayListExtra("folderItem")
+        )
+        assertEquals(
+            ArrayList(expectedBroadcastModel.pendingWorkspaceItems),
+            actualBroadcastIntent.getStringArrayListExtra("workspaceItem")
+        )
+        assertEquals(
+            ArrayList(expectedBroadcastModel.pendingHotseatItems),
+            actualBroadcastIntent.getStringArrayListExtra("hotseatItem")
+        )
+        assertEquals(
+            ArrayList(expectedBroadcastModel.pendingWidgetItems),
+            actualBroadcastIntent.getStringArrayListExtra("widgetItem")
+        )
+    }
+
+    @Test
+    fun `When not a restore then installed item broadcast not sent`() {
+        // Given
+        val spyContext = spy(context)
+        `when`(app.context).thenReturn(spyContext)
+        whenever(
+                FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                    anyOrNull(),
+                    anyList(),
+                    anyMap(),
+                    anyList()
+                )
+            )
+            .thenReturn(listOf(expectedBroadcastModel))
+
+        whenever(
+                FirstScreenBroadcastHelper.sendBroadcastsForModels(
+                    spyContext,
+                    listOf(expectedBroadcastModel)
+                )
+            )
+            .thenCallRealMethod()
+
+        Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 1)
+
+        // When
+        LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
+            .runSyncOnBackgroundThread()
+
+        // Then
+        verify(spyContext, times(0)).sendBroadcast(any(Intent::class.java))
+    }
+
+    @Test
+    fun `When launcher_broadcast_installed_apps false then installed item broadcast not sent`() {
+        // Given
+        val spyContext = spy(context)
+        `when`(app.context).thenReturn(spyContext)
+        whenever(
+                FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                    anyOrNull(),
+                    anyList(),
+                    anyMap(),
+                    anyList()
+                )
+            )
+            .thenReturn(listOf(expectedBroadcastModel))
+
+        whenever(
+                FirstScreenBroadcastHelper.sendBroadcastsForModels(
+                    spyContext,
+                    listOf(expectedBroadcastModel)
+                )
+            )
+            .thenCallRealMethod()
+
+        Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 0)
+        RestoreDbTask.setPending(spyContext)
+
+        // When
+        LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
+            .runSyncOnBackgroundThread()
+
+        // Then
+        verify(spyContext, times(0)).sendBroadcast(any(Intent::class.java))
+    }
 }
 
 private fun LoaderTask.runSyncOnBackgroundThread() {
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 115a6e6..6e01f9e 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -239,7 +239,8 @@
         final CountDownLatch count = new CountDownLatch(2);
         final SimpleBroadcastReceiver broadcastReceiver =
                 new SimpleBroadcastReceiver(i -> count.countDown());
-        broadcastReceiver.registerPkgActions(mTargetContext, pkg,
+        // We OK to make binder calls on main thread in test.
+        broadcastReceiver.registerPkgActionsSync(mTargetContext, pkg,
                 Intent.ACTION_PACKAGE_RESTARTED, Intent.ACTION_PACKAGE_DATA_CLEARED);
 
         mDevice.executeShellCommand("pm clear " + pkg);
@@ -699,4 +700,11 @@
         UiDevice.getInstance(getInstrumentation()).pressHome();
         mLauncher.waitForLauncherInitialized();
     }
+
+    /** Clears all recent tasks */
+    protected void clearAllRecentTasks() {
+        if (!mLauncher.getRecentTasks().isEmpty()) {
+            mLauncher.goHome().switchToOverview().dismissAllTasks();
+        }
+    }
 }
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
index 4a67600..342eedf 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
@@ -21,9 +21,8 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.launcher3.Launcher;
-import com.android.launcher3.util.rule.ScreenRecordRule.KeepRecordOnSuccess;
 import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
+import com.android.launcher3.Launcher;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -32,7 +31,6 @@
 @RunWith(AndroidJUnit4.class)
 public class TaplTestsLauncher3Test extends AbstractLauncherUiTest<Launcher> {
 
-    @KeepRecordOnSuccess
     @ScreenRecord // b/322823478
     @Test
     public void testDevicePressMenu() throws Exception {
diff --git a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
index 69b42cb..b2e413d 100644
--- a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
@@ -45,7 +45,6 @@
 import com.android.launcher3.allapps.WorkProfileManager;
 import com.android.launcher3.tapl.LauncherInstrumentation;
 import com.android.launcher3.util.TestUtil;
-import com.android.launcher3.util.rule.ScreenRecordRule.KeepRecordOnSuccess;
 import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 
@@ -196,7 +195,6 @@
 
     }
 
-    @KeepRecordOnSuccess
     @ScreenRecord // b/322823478
     @Test
     public void testEdu() {
diff --git a/tests/src/com/android/launcher3/util/TestResourceHelper.kt b/tests/src/com/android/launcher3/util/TestResourceHelper.kt
deleted file mode 100644
index b4d3ba8..0000000
--- a/tests/src/com/android/launcher3/util/TestResourceHelper.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.util
-
-import android.content.Context
-import android.content.res.TypedArray
-import android.util.AttributeSet
-import com.android.launcher3.R
-import com.android.launcher3.tests.R as TestR
-import kotlin.IntArray
-
-class TestResourceHelper(private val context: Context, specsFileId: Int) :
-    ResourceHelper(context, specsFileId) {
-    override fun obtainStyledAttributes(attrs: AttributeSet, styleId: IntArray): TypedArray {
-        val clone =
-            when {
-                styleId.contentEquals(R.styleable.SizeSpec) -> TestR.styleable.SizeSpec
-                styleId.contentEquals(R.styleable.WorkspaceSpec) -> TestR.styleable.WorkspaceSpec
-                styleId.contentEquals(R.styleable.FolderSpec) -> TestR.styleable.FolderSpec
-                styleId.contentEquals(R.styleable.AllAppsSpec) -> TestR.styleable.AllAppsSpec
-                styleId.contentEquals(R.styleable.ResponsiveSpecGroup) ->
-                    TestR.styleable.ResponsiveSpecGroup
-                else -> styleId.clone()
-            }
-
-        return context.obtainStyledAttributes(attrs, clone)
-    }
-}
diff --git a/tests/src/com/android/launcher3/util/rule/ScreenRecordRule.java b/tests/src/com/android/launcher3/util/rule/ScreenRecordRule.java
index ff96afb..7a5cf2c 100644
--- a/tests/src/com/android/launcher3/util/rule/ScreenRecordRule.java
+++ b/tests/src/com/android/launcher3/util/rule/ScreenRecordRule.java
@@ -49,9 +49,6 @@
             return base;
         }
 
-        final Boolean keepRecordOnSuccess = description.getAnnotation(KeepRecordOnSuccess.class)
-                != null;
-
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
@@ -73,7 +70,7 @@
                     device.executeShellCommand("kill -INT " + screenRecordPid);
                     Log.e(TAG, "Screenrecord captured at: " + outputFile);
                     output.close();
-                    if (success && !keepRecordOnSuccess) {
+                    if (success) {
                         automation.executeShellCommand("rm " + outputFile);
                     }
                 }
@@ -88,14 +85,4 @@
     @Target(ElementType.METHOD)
     public @interface ScreenRecord {
     }
-
-
-    /**
-     * Interface to indicate that we should keep the screen record even if the test succeeds, use
-     * sparingly since it uses a lof of memory.
-     */
-    @Retention(RetentionPolicy.RUNTIME)
-    @Target(ElementType.METHOD)
-    public @interface KeepRecordOnSuccess {
-    }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index e10893e..27f6c16 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -23,6 +23,7 @@
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 
 import android.graphics.Rect;
+import android.util.Log;
 import android.view.KeyEvent;
 
 import androidx.annotation.NonNull;
@@ -383,26 +384,39 @@
     }
 
     protected boolean isActionsViewVisible() {
-        if (!hasTasks() || isClearAllVisible()) {
+        boolean hasTasks = hasTasks();
+        if (!hasTasks || isClearAllVisible()) {
+            LauncherInstrumentation.log("Not expecting an actions bar:"
+                    + (!hasTasks ? "no recent tasks" : "clear all button is visible"));
             return false;
         }
         boolean isTablet = mLauncher.isTablet();
         if (isTablet && mLauncher.isGridOnlyOverviewEnabled()) {
+            LauncherInstrumentation.log("Not expecting an actions bar: "
+                    + "device is tablet with grid-only Overview");
             return false;
         }
         OverviewTask task = isTablet ? getFocusedTaskForTablet() : getCurrentTask();
         if (task == null) {
+            LauncherInstrumentation.log("Not expecting an actions bar: no focused task");
             return false;
         }
+        float centerOffset = Math.abs(task.getExactCenterX() - mLauncher.getExactScreenCenterX());
         // In tablets, if focused task is not in center, overview actions aren't visible.
-        if (isTablet && Math.abs(task.getExactCenterX() - mLauncher.getExactScreenCenterX()) >= 1) {
+        if (isTablet && centerOffset >= 1) {
+            LauncherInstrumentation.log("Not expecting an actions bar: "
+                    + "device is tablet and task is not centered; center offset by "
+                    + centerOffset + "px");
             return false;
         }
         if (task.isTaskSplit() && (!mLauncher.isAppPairsEnabled() || !isTablet)) {
+            LauncherInstrumentation.log("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.
             return false;
         }
+        LauncherInstrumentation.log("Expecting an actions bar");
         return true;
     }
 
@@ -447,10 +461,20 @@
     }
 
     private void verifyActionsViewVisibility() {
+        // If no running tasks, no need to verify actions view visibility.
+        if (getTasks().isEmpty()) {
+            return;
+        }
+
+        boolean isTablet = mLauncher.isTablet();
+        OverviewTask task = isTablet ? getFocusedTaskForTablet() : getCurrentTask();
+
         try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
-                "want to assert overview actions view visibility")) {
-            boolean isTablet = mLauncher.isTablet();
-            OverviewTask task = isTablet ? getFocusedTaskForTablet() : getCurrentTask();
+                "want to assert overview actions view visibility="
+                        + isActionsViewVisible()
+                        + ", focused task is "
+                        + (task == null ? "null" : (task.isTaskSplit() ? "split" : "not split"))
+                )) {
 
             if (isActionsViewVisible()) {
                 if (task.isTaskSplit()) {
@@ -475,13 +499,18 @@
             throw new IllegalStateException("Must be run on tablet device.");
         }
         final List<UiObject2> taskViews = getTasks();
-        if (taskViews.size() == 0) {
+        if (!hasTasks()) {
+            LauncherInstrumentation.log("no recent tasks");
             return null;
         }
         int focusedTaskHeight = mLauncher.getFocusedTaskHeightForTablet();
         for (UiObject2 task : taskViews) {
             OverviewTask overviewTask = new OverviewTask(mLauncher, task, this);
 
+            LauncherInstrumentation.log("checking task height ("
+                    + overviewTask.getVisibleHeight()
+                    + ") against defined focused task height ("
+                    + focusedTaskHeight + ")");
             if (overviewTask.getVisibleHeight() == focusedTaskHeight) {
                 return overviewTask;
             }
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
index 4be46ab..c58d16e 100644
--- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
@@ -17,10 +17,12 @@
 
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 
-import android.text.TextUtils;
+import android.graphics.Rect;
 import android.widget.TextView;
 
 import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
 import java.util.ArrayList;
@@ -34,10 +36,12 @@
     // This particular ID change should happen with caution
     private static final String SEARCH_CONTAINER_RES_ID = "search_results_list_view";
     protected final LauncherInstrumentation mLauncher;
+    private final UiObject2 mSearchContainer;
 
     SearchResultFromQsb(LauncherInstrumentation launcher) {
         mLauncher = launcher;
         mLauncher.waitForLauncherObject("search_container_all_apps");
+        mSearchContainer = mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID);
     }
 
     /** Find the app from search results with app name. */
@@ -52,18 +56,9 @@
 
     /** Find the web suggestion from search suggestion's title text */
     public SearchWebSuggestion findWebSuggestion(String text) {
-        ArrayList<UiObject2> webSuggestions =
-                new ArrayList<>(mLauncher.waitForObjectsInContainer(
-                        mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID),
-                        By.clazz(TextView.class)));
-        for (UiObject2 uiObject: webSuggestions) {
-            String currentString = uiObject.getText();
-            if (currentString.equals(text)) {
-                return createWebSuggestion(uiObject);
-            }
-        }
-        mLauncher.fail("Web suggestion title: " + text + " not found");
-        return null;
+        UiObject2 webSuggestion = mLauncher.waitForObjectInContainer(mSearchContainer,
+                getSearchResultSelector(text));
+        return createWebSuggestion(webSuggestion);
     }
 
     protected SearchWebSuggestion createWebSuggestion(UiObject2 webSuggestion) {
@@ -73,13 +68,25 @@
     /** Find the total amount of views being displayed and return the size */
     public int getSearchResultItemSize() {
         ArrayList<UiObject2> searchResultItems =
-                new ArrayList<>(mLauncher.waitForObjectsInContainer(
-                        mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID),
+                new ArrayList<>(mLauncher.waitForObjectsInContainer(mSearchContainer,
                         By.clazz(TextView.class)));
         return searchResultItems.size();
     }
 
     /**
+     * Scroll down to make next page search results rendered.
+     */
+    public void getNextPageSearchResults() {
+        final int searchContainerHeight = mLauncher.getVisibleBounds(mSearchContainer).height();
+        // Start scrolling from center of the screen to top of the screen.
+        mLauncher.scroll(mSearchContainer,
+                Direction.DOWN,
+                new Rect(0, 0, 0, searchContainerHeight / 2),
+                /* steps= */ 10,
+                /*slowDown= */ false);
+    }
+
+    /**
      * Taps outside bottom sheet to dismiss and return to workspace. Available on tablets only.
      * @param tapRight Tap on the right of bottom sheet if true, or left otherwise.
      */
@@ -121,25 +128,13 @@
 
     /** Verify a tile is present by checking its title and subtitle. */
     public void verifyTileIsPresent(String title, String subtitle) {
-        ArrayList<UiObject2> searchResults =
-                new ArrayList<>(mLauncher.waitForObjectsInContainer(
-                        mLauncher.waitForSystemLauncherObject(SEARCH_CONTAINER_RES_ID),
-                        By.clazz(TextView.class)));
-        boolean foundTitle = false;
-        boolean foundSubtitle = false;
-        for (UiObject2 uiObject: searchResults) {
-            String currentString = uiObject.getText();
-            if (TextUtils.equals(currentString, title)) {
-                foundTitle = true;
-            } else if (TextUtils.equals(currentString, subtitle)) {
-                foundSubtitle = true;
-            }
-        }
-        if (!foundTitle) {
-            mLauncher.fail("Tile not found for title: " + title);
-        }
-        if (!foundSubtitle) {
-            mLauncher.fail("Tile not found for subtitle: " + subtitle);
-        }
+        mLauncher.waitForObjectInContainer(mSearchContainer,
+                getSearchResultSelector(title));
+        mLauncher.waitForObjectInContainer(mSearchContainer,
+                getSearchResultSelector(subtitle));
+    }
+
+    private BySelector getSearchResultSelector(String text) {
+        return By.clazz(TextView.class).text(text);
     }
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 4fa93ef..9ac6768 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -444,6 +444,8 @@
             Runnable expectLongClickEvents) {
         try (LauncherInstrumentation.Closable c = launcher.addContextLayer(
                 "uninstalling app icon")) {
+
+            final String appNameToUninstall = homeAppIcon.getAppName();
             dragIconToWorkspace(
                     launcher,
                     homeAppIcon,
@@ -468,7 +470,10 @@
 
             try (LauncherInstrumentation.Closable c1 = launcher.addContextLayer(
                     "uninstalled app by dragging to the drop bar")) {
-                return new Workspace(launcher);
+                final Workspace newWorkspace = new Workspace(launcher);
+                launcher.waitUntilLauncherObjectGone(
+                        AppIcon.getAppIconSelector(appNameToUninstall));
+                return newWorkspace;
             }
         }
     }