[automerger skipped] Merge "Merge 25Q1 (ab/BP1A.250305.020) to AOSP main" into main am: 2663cf0a41 am: 95d656756a -s ours

am skip reason: Merged-In I4b758e6ce103c5201ef05ab824dd4e02f98c40b6 with SHA-1 a315410ea3 is already in history

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/31529512

Change-Id: I9f2de18005fafdcd42c40cf1b5f682b5ef270716
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 73d0fce..85632b2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -25,6 +25,7 @@
     name: "launcher-non-platform-apis-defaults",
     static_libs: [
         "android.os.flags-aconfig-java",
+        "android.multiuser.flags-aconfig-java",
         "android.appwidget.flags-aconfig-java",
         "com.android.window.flags.window-aconfig-java",
     ],
@@ -389,6 +390,7 @@
         "//frameworks/libs/systemui:contextualeducationlib",
         "//frameworks/libs/systemui:msdl",
         "SystemUI-statsd",
+        "WindowManager-Shell-shared-AOSP",
         "launcher-testing-shared",
         "androidx.lifecycle_lifecycle-common-java8",
         "androidx.lifecycle_lifecycle-extensions",
@@ -451,8 +453,12 @@
         "AndroidManifest-common.xml",
     ],
     lint: {
+        extra_check_modules: ["Launcher3LintChecker"],
         baseline_filename: "lint-baseline.xml",
     },
+    kotlincflags: [
+        "-Xjvm-default=all",
+    ],
 }
 
 // Library with all the dependencies for building quickstep
@@ -475,6 +481,9 @@
     ],
     manifest: "quickstep/AndroidManifest.xml",
     min_sdk_version: "current",
+    lint: {
+        disabled_checks: ["MissingClass"],
+    },
 }
 
 // Library with all the source code and dependencies for building Launcher Go
@@ -514,6 +523,9 @@
     min_sdk_version: "current",
     // TODO(b/319712088): re-enable use_resource_processor
     use_resource_processor: false,
+    kotlincflags: [
+        "-Xjvm-default=all",
+    ],
 }
 
 // Library with all the source code and dependencies for building Quickstep
@@ -547,6 +559,9 @@
     min_sdk_version: "current",
     // TODO(b/319712088): re-enable use_resource_processor
     use_resource_processor: false,
+    kotlincflags: [
+        "-Xjvm-default=all",
+    ],
 }
 
 // Build rule for Quickstep app.
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 80d2eac..46f0e41 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -131,13 +131,11 @@
             android:writePermission="${applicationId}.permission.WRITE_SETTINGS"
             android:readPermission="${applicationId}.permission.READ_SETTINGS" />
 
-        <!--
-        The content provider for exposing various launcher grid options.
-        TODO: Add proper permissions
-        -->
+        <!-- The content provider for exposing various launcher grid options. -->
         <provider
-            android:name="com.android.launcher3.graphics.GridCustomizationsProvider"
+            android:name="com.android.launcher3.graphics.LauncherCustomizationProvider"
             android:authorities="${applicationId}.grid_control"
+            android:permission="android.permission.BIND_WALLPAPER"
             android:exported="true" />
 
         <!--
diff --git a/OWNERS b/OWNERS
index c2df914..3f7a780 100644
--- a/OWNERS
+++ b/OWNERS
@@ -6,10 +6,8 @@
 
 adamcohen@google.com
 hyunyoungs@google.com
-twickham@google.com
 vadimt@google.com
 winsonc@google.com
-jonmiranda@google.com
 agvard@google.com
 
 # Launcher workspace eng team
@@ -22,13 +20,13 @@
 pinyaoting@google.com
 andonian@google.com
 sihua@google.com
+abegovic@google.com
 
 # Multitasking eng team
 tracyzhou@google.com
 peanutbutter@google.com
 jeremysim@google.com
 atsjenk@google.com
-brianji@google.com
 hwwang@google.com
 
 # Overview eng team
@@ -36,6 +34,8 @@
 samcackett@google.com
 silvajordan@google.com
 uwaisashraf@google.com
+vinayjoglekar@google.com
+willosborn@google.com
 
 # Physical Keyboard & Trackpad eng team
 patmanning@google.com
@@ -45,6 +45,16 @@
 shamalip@google.com
 zakcohen@google.com
 
+# System Navigation team
+brianji@google.com
+jonmiranda@google.com
+jagrutdesai@google.com
+randypfohl@google.com
+saumyaprakash@google.com
+sukeshram@google.com
+twickham@google.com
+victortulias@google.com
+
 ## Note: some of the below overlap and also work on other integrations like Circle to Search.
 
 # All Apps / QSB team
@@ -60,10 +70,10 @@
 jiuyu@google.com
 
 per-file FeatureFlags.java, globs = set noparent
-per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com
+per-file FeatureFlags.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, captaincole@google.com, abegovic@google.com
 
 per-file DeviceConfigWrapper.java, globs = set noparent
-per-file DeviceConfigWrapper.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com
+per-file DeviceConfigWrapper.java = sunnygoyal@google.com, winsonc@google.com, adamcohen@google.com, hyunyoungs@google.com, abegovic@google.com
 
 # Predictive Back
 per-file LauncherBackAnimationController.java = shanh@google.com, gallmann@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 9051ca8..6948133 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -10,4 +10,3 @@
 [Hook Scripts]
 checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --config_xml tools/checkstyle.xml --sha ${PREUPLOAD_COMMIT}
 
-flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PATH}
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index ac59c35..c5774bb 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -65,13 +65,6 @@
 }
 
 flag {
-    name: "enable_taskbar_connected_displays"
-    namespace: "launcher"
-    description: "Enables connected displays in taskbar."
-    bug: "362720616"
-}
-
-flag {
     name: "enable_taskbar_customization"
     namespace: "launcher"
     description: "Enables taskbar customization framework."
@@ -242,7 +235,7 @@
 
 flag {
     name: "enable_fallback_overview_in_window"
-    namespace: "launcher"
+    namespace: "lse_desktop_experience"
     description: "Enables fallback recents opening inside of a window instead of an activity."
     bug: "292269949"
 }
@@ -457,14 +450,14 @@
 
 flag {
     name: "enable_recents_window_proto_log"
-    namespace: "launcher"
+    namespace: "lse_desktop_experience"
     description: "Enables tracking recents window logs in ProtoLog"
     bug: "292269949"
 }
 
 flag {
     name: "enable_state_manager_proto_log"
-    namespace: "launcher"
+    namespace: "lse_desktop_experience"
     description: "Enables tracking state manager logs in ProtoLog"
     bug: "292269949"
 }
@@ -495,7 +488,7 @@
 
 flag {
     name: "enable_launcher_overview_in_window"
-    namespace: "launcher"
+    namespace: "lse_desktop_experience"
     description: "Enables launcher recents opening inside of a window instead of being hosted in launcher activity."
     bug: "292269949"
 }
@@ -525,13 +518,6 @@
 }
 
 flag {
-    name: "taskbar_recents_layout_transition"
-    namespace: "launcher"
-    description: "Enable Taskbar LayoutTransition for Recent Apps"
-    bug: "343521765"
-}
-
-flag {
     name: "enable_pinning_app_with_context_menu"
     namespace: "launcher"
     description: "Add options to pin/unpin to taskbar to app context menus."
@@ -543,4 +529,130 @@
   namespace: "launcher"
   description: "Enable launcher icon shape customizations"
   bug: "348708061"
-}
\ No newline at end of file
+}
+
+flag {
+  name: "predictive_back_to_home_polish"
+  namespace: "launcher"
+  description: "Enables workspace reveal animation for predictive back-to-home"
+  bug: "382453424"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "predictive_back_to_home_blur"
+  namespace: "launcher"
+  description: "Adds blur for predictive back-to-home"
+  bug: "342178850"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "enable_launcher_visual_refresh"
+  namespace: "launcher"
+  description: "Adds refresh for font family, app longpress menu icons, and pagination dots"
+  bug: "395145453"
+}
+
+flag {
+  name: "restore_archived_shortcuts"
+  namespace: "launcher"
+  description: "Makes sure pre-archived pinned shortcuts also get restored"
+  bug: "375414891"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "restore_archived_app_icons_from_db"
+  namespace: "launcher"
+  description: "Restores pre-archived icons from db when available, mimicing promise icons"
+  bug: "391913214"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "enable_mouse_interaction_changes"
+  namespace: "launcher"
+  description: "Changes mouse interaction behavior"
+  bug: "388897603"
+}
+
+flag {
+  name: "enable_alt_tab_kqs_on_connected_displays"
+  namespace: "lse_desktop_experience"
+  description: "Enable Alt + Tab KQS support on connected displays"
+  bug: "394007677"
+}
+
+flag {
+  name: "expressive_theme_in_taskbar_and_navigation"
+  namespace: "launcher"
+  description: "Enables the expressive theme and GSF font styles for Taskbar and Gesture Navigation"
+  bug: "394613212"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "enable_strict_mode"
+  namespace: "launcher"
+  description: "Enable Strict Mode for the Launcher app"
+  bug: "394651876"
+}
+
+flag {
+  name: "extendible_theme_manager"
+  namespace: "launcher"
+  description: "Enables custom theme manager in Launcher"
+  bug: "381897614"
+}
+
+flag {
+  name: "enable_alt_tab_kqs_flatenning"
+  namespace: "lse_desktop_experience"
+  description: "Enable Alt + Tab KQS view to show apps in flattened structure"
+  bug: "382769617"
+}
+
+flag {
+  name: "enable_gesture_nav_on_connected_displays"
+  namespace: "lse_desktop_experience"
+  description: "Enables gesture navigation handling on connected displays"
+  bug: "382130680"
+}
+
+flag {
+  name: "enable_taskbar_behind_shade"
+  namespace: "lse_desktop_experience"
+  description: "Keeps taskbar behind notification shade when its pulled down"
+  bug: "343194358"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "enable_scalability_for_desktop_experience"
+  namespace: "launcher"
+  description: "Enable more grid scale options on the launcher for desktop experience"
+  bug: "375491272"
+}
+
+flag {
+  name: "enable_gesture_nav_horizontal_touch_slop"
+  namespace: "launcher"
+  description: "Enables horizontal touch slop checking in non-vertical fling navigation gestures"
+  bug: "394364217"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/aconfig/launcher_accessibility.aconfig b/aconfig/launcher_accessibility.aconfig
new file mode 100644
index 0000000..13e1127
--- /dev/null
+++ b/aconfig/launcher_accessibility.aconfig
@@ -0,0 +1,12 @@
+package: "com.android.launcher3"
+container: "system_ext"
+
+flag {
+    name: "remove_exclude_from_screen_magnification_flag_usage"
+    namespace: "accessibility"
+    description: "Remove the WindowManager PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION flag usage in Launcher"
+    bug: "369019568"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/aconfig/launcher_growth.aconfig b/aconfig/launcher_growth.aconfig
new file mode 100644
index 0000000..a880538
--- /dev/null
+++ b/aconfig/launcher_growth.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.launcher3"
+container: "system_ext"
+
+flag {
+    name: "enable_growth_nudge"
+    namespace: "desktop_oobe"
+    description: "Add growth nudge in launcher"
+    bug: "396165728"
+}
diff --git a/aconfig/launcher_overview.aconfig b/aconfig/launcher_overview.aconfig
index 86e41df..d27a214 100644
--- a/aconfig/launcher_overview.aconfig
+++ b/aconfig/launcher_overview.aconfig
@@ -5,14 +5,14 @@
     name: "enable_grid_only_overview"
     namespace: "launcher_overview"
     description: "Enable a grid-only overview without a focused task."
-    bug: "257950105"
+    bug: "360204325"
 }
 
 flag {
     name: "enable_overview_icon_menu"
     namespace: "launcher_overview"
     description: "Enable updated overview icon and menu within task."
-    bug: "257950105"
+    bug: "360205084"
 }
 
 flag {
@@ -71,4 +71,52 @@
     metadata {
       purpose: PURPOSE_BUGFIX
     }
-}
\ No newline at end of file
+}
+
+flag {
+    name: "enable_expressive_dismiss_task_motion"
+    namespace: "launcher_overview"
+    description: "Enables expressive motion and animations for dismissing a task in Overview."
+    bug: "381239462"
+}
+
+flag {
+    name: "enable_separate_external_display_tasks"
+    namespace: "launcher_overview"
+    description: "Enables separating external display tasks in Overview."
+    bug: "391311008"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
+    name: "enable_overview_on_connected_displays"
+    namespace: "launcher_overview"
+    description: "Enable overview on connected displays."
+    bug: "363251602"
+}
+
+flag {
+    name: "enable_overview_background_wallpaper_blur"
+    namespace: "launcher_overview"
+    description: "Enable wallpaper blur in overview."
+    bug: "369975912"
+}
+
+flag {
+    name: "enable_overview_desktop_tile_wallpaper_background"
+    namespace: "launcher_overview"
+    description: "Enable wallpaper background for desktop tasks in overview."
+    bug: "363257721"
+}
+
+flag {
+    name: "enable_show_enabled_shortcuts_in_accessibility_menu"
+    namespace: "launcher_overview"
+    description: "Enables showing the same shortcuts in the Task menu as well as the accessibility actions menu"
+    bug: "383662632"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/checks/Android.bp b/checks/Android.bp
new file mode 100644
index 0000000..dfd701e
--- /dev/null
+++ b/checks/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2025 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library_host {
+    name: "Launcher3LintChecker",
+    srcs: ["src/**/*.kt"],
+    plugins: ["auto_service_plugin"],
+    libs: [
+        "auto_service_annotations",
+        "lint_api",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+}
+
+java_test_host {
+    name: "Launcher3LintCheckerTest",
+    defaults: ["AndroidLintCheckerTestDefaults"],
+    srcs: ["tests/**/*.kt"],
+    data: [
+        ":androidx.annotation_annotation",
+        ":dagger2",
+        ":kotlinx-coroutines-core",
+    ],
+    device_common_data: [
+        ":framework",
+    ],
+    static_libs: [
+        "Launcher3LintChecker",
+    ],
+}
diff --git a/checks/src/com/android/internal/launcher3/lint/CustomDialogDetector.kt b/checks/src/com/android/internal/launcher3/lint/CustomDialogDetector.kt
new file mode 100644
index 0000000..37358bb
--- /dev/null
+++ b/checks/src/com/android/internal/launcher3/lint/CustomDialogDetector.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import org.jetbrains.uast.UClass
+
+/** Detector to identify custom usage of Android's Dialog within the Launcher3 codebase. */
+class CustomDialogDetector : Detector(), SourceCodeScanner {
+
+    override fun applicableSuperClasses(): List<String> {
+        return listOf(DIALOG_CLASS_NAME)
+    }
+
+    override fun visitClass(context: JavaContext, declaration: UClass) {
+        val superTypeClassNames = declaration.superTypes.mapNotNull { it.resolve()?.qualifiedName }
+        if (superTypeClassNames.contains(DIALOG_CLASS_NAME)) {
+            context.report(
+                ISSUE,
+                declaration,
+                context.getNameLocation(declaration),
+                "Class implements Dialog",
+            )
+        }
+    }
+
+    companion object {
+        private const val DIALOG_CLASS_NAME = "android.app.Dialog"
+
+        @JvmField
+        val ISSUE =
+            Issue.create(
+                id = "IllegalUseOfCustomDialog",
+                briefDescription = "dialogs should not be used in Launcher",
+                explanation =
+                    """
+                Don't use custom Dialogs within the launcher code base, instead consider utilizing
+                AbstractFloatingView to display content that should float above the launcher where
+                it can be correctly managed for dismissal.
+            """
+                        .trimIndent(),
+                category = Category.CORRECTNESS,
+                priority = 10,
+                severity = Severity.ERROR,
+                implementation =
+                    Implementation(CustomDialogDetector::class.java, Scope.JAVA_FILE_SCOPE),
+            )
+    }
+}
diff --git a/checks/src/com/android/internal/launcher3/lint/Launcher3IssueRegistry.kt b/checks/src/com/android/internal/launcher3/lint/Launcher3IssueRegistry.kt
new file mode 100644
index 0000000..c77c42b
--- /dev/null
+++ b/checks/src/com/android/internal/launcher3/lint/Launcher3IssueRegistry.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.launcher3.lint
+
+import CustomDialogDetector
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.android.tools.lint.detector.api.Issue
+import com.google.auto.service.AutoService
+
+@AutoService(IssueRegistry::class)
+@Suppress("UnstableApiUsage")
+class Launcher3IssueRegistry : IssueRegistry() {
+    override val issues: List<Issue>
+        get() = listOf(CustomDialogDetector.ISSUE)
+
+    override val api: Int
+        get() = CURRENT_API
+
+    override val minApi: Int
+        get() = 8
+
+    override val vendor: Vendor =
+        Vendor(
+            vendorName = "Android",
+            feedbackUrl = "http://b/issues/new?component=78010",
+            contact = "abegovic@google.com",
+        )
+}
diff --git a/checks/tests/com/android/internal/launcher3/lint/CustomDialogDetectorTest.kt b/checks/tests/com/android/internal/launcher3/lint/CustomDialogDetectorTest.kt
new file mode 100644
index 0000000..2a37953
--- /dev/null
+++ b/checks/tests/com/android/internal/launcher3/lint/CustomDialogDetectorTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.launcher3.lint
+
+import CustomDialogDetector
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+import org.junit.Test
+
+/** Test for [CustomDialogDetector]. */
+class CustomDialogDetectorTest : Launcher3LintDetectorTest() {
+    override fun getDetector(): Detector = CustomDialogDetector()
+
+    override fun getIssues(): List<Issue> = listOf(CustomDialogDetector.ISSUE)
+
+    @Test
+    fun classDoesNotExtendDialog_noViolation() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                    package test.pkg
+
+                    class SomeClass
+                """
+                        .trimIndent()
+                ),
+                *androidStubs,
+            )
+            .issues(CustomDialogDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun classDoesExtendDialog_violation() {
+        lint()
+            .files(
+                TestFiles.kotlin(
+                    """
+                    package test.pkg
+
+                    import android.app.Dialog
+
+                    class SomeClass(context: Context) : Dialog(context)
+                """
+                        .trimIndent()
+                ),
+                *androidStubs,
+            )
+            .issues(CustomDialogDetector.ISSUE)
+            .run()
+            .expect(
+                ("""
+                src/test/pkg/SomeClass.kt:5: Error: Class implements Dialog [IllegalUseOfCustomDialog]
+                class SomeClass(context: Context) : Dialog(context)
+                      ~~~~~~~~~
+                1 errors, 0 warnings
+                """)
+                    .trimIndent()
+            )
+    }
+}
diff --git a/checks/tests/com/android/internal/launcher3/lint/Launcher3LintDetectorTest.kt b/checks/tests/com/android/internal/launcher3/lint/Launcher3LintDetectorTest.kt
new file mode 100644
index 0000000..09085c7
--- /dev/null
+++ b/checks/tests/com/android/internal/launcher3/lint/Launcher3LintDetectorTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.launcher3.lint
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import java.io.File
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+/**
+ * Abstract class that should be used by any test for launcher 3 lint detectors.
+ *
+ * When you write your test, ensure that you pass [androidStubs] as part of your [TestFiles]
+ * definition.
+ */
+@RunWith(JUnit4::class)
+abstract class Launcher3LintDetectorTest : LintDetectorTest() {
+
+    /**
+     * Customize the lint task to disable SDK usage completely. This ensures that running the tests
+     * in Android Studio has the same result as running the tests in atest
+     */
+    override fun lint(): TestLintTask =
+        super.lint().allowMissingSdk(true).sdkHome(File("/dev/null"))
+
+    companion object {
+        private val libraryNames =
+            arrayOf(
+                "androidx.annotation_annotation.jar",
+                "dagger2.jar",
+                "framework.jar",
+                "kotlinx-coroutines-core.jar",
+            )
+
+        /**
+         * This file contains stubs of framework APIs and System UI classes for testing purposes
+         * only. The stubs are not used in the lint detectors themselves.
+         */
+        val androidStubs =
+            libraryNames
+                .map { TestFiles.LibraryReferenceTestFile(File(it).canonicalFile) }
+                .toTypedArray()
+    }
+}
diff --git a/go/quickstep/src/com/android/launcher3/util/MainThreadInitializedObject.java b/go/quickstep/src/com/android/launcher3/util/MainThreadInitializedObject.java
new file mode 100644
index 0000000..e1f3508
--- /dev/null
+++ b/go/quickstep/src/com/android/launcher3/util/MainThreadInitializedObject.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.util;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.os.Looper;
+
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Utility class for defining singletons which are initiated on main thread.
+ */
+public class MainThreadInitializedObject<T extends SafeCloseable> {
+
+    private final ObjectProvider<T> mProvider;
+    private T mValue;
+
+    public MainThreadInitializedObject(ObjectProvider<T> provider) {
+        mProvider = provider;
+    }
+
+    public T get(Context context) {
+        Context app = context.getApplicationContext();
+        if (app instanceof ObjectSandbox sc) {
+            return sc.getObject(this);
+        }
+
+        if (mValue == null) {
+            if (Looper.myLooper() == Looper.getMainLooper()) {
+                mValue = TraceHelper.allowIpcs("main.thread.object", () -> mProvider.get(app));
+            } else {
+                try {
+                    return MAIN_EXECUTOR.submit(() -> get(context)).get();
+                } catch (InterruptedException | ExecutionException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+        return mValue;
+    }
+
+    public interface ObjectProvider<T> {
+
+        T get(Context context);
+    }
+
+    /** Sandbox for isolating {@link MainThreadInitializedObject} instances from Launcher. */
+    public interface ObjectSandbox {
+
+        /**
+         * Find a cached object from mObjectMap if we have already created one. If not, generate
+         * an object using the provider.
+         */
+        <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object);
+    }
+}
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index 201c5f6..5ca7143 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -119,6 +119,7 @@
             android:autoRemoveFromRecents="true"
             android:excludeFromRecents="true"
             android:theme="@style/GestureTutorialActivity"
+            android:label="@string/gesture_tutorial_title"
             android:exported="true"
             android:configChanges="orientation">
             <intent-filter>
diff --git a/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt b/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt
new file mode 100644
index 0000000..d88fc94
--- /dev/null
+++ b/quickstep/dagger/com/android/launcher3/dagger/AppModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.dagger
+
+import com.android.launcher3.model.ModelDelegate
+import com.android.launcher3.model.QuickstepModelDelegate
+import dagger.Binds
+import dagger.Module
+
+/**
+ * Module containing bindings for the final derivative app, an implementation of this module should
+ * be included in the final app code.
+ */
+@Module
+abstract class AppModule {
+
+    @Binds abstract fun bindModelDelegate(impl: QuickstepModelDelegate): ModelDelegate
+}
diff --git a/quickstep/dagger/LauncherAppComponent.java b/quickstep/dagger/com/android/launcher3/dagger/LauncherAppComponent.java
similarity index 91%
rename from quickstep/dagger/LauncherAppComponent.java
rename to quickstep/dagger/com/android/launcher3/dagger/LauncherAppComponent.java
index 068f01c..a4cb420 100644
--- a/quickstep/dagger/LauncherAppComponent.java
+++ b/quickstep/dagger/com/android/launcher3/dagger/LauncherAppComponent.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.dagger;
 
 
-import com.android.quickstep.dagger.QuickStepModule;
 import com.android.quickstep.dagger.QuickstepBaseAppComponent;
 
 import dagger.Component;
@@ -26,7 +25,7 @@
  * Root component for Dagger injection for Launcher Quickstep.
  */
 @LauncherAppSingleton
-@Component(modules = QuickStepModule.class)
+@Component(modules = LauncherAppModule.class)
 public interface LauncherAppComponent extends QuickstepBaseAppComponent {
     /** Builder for quickstep LauncherAppComponent. */
     @Component.Builder
diff --git a/quickstep/res/drawable/bg_bubble_dismiss_circle.xml b/quickstep/res/color/keyboard_quick_switch_scroll_button_bg.xml
similarity index 60%
copy from quickstep/res/drawable/bg_bubble_dismiss_circle.xml
copy to quickstep/res/color/keyboard_quick_switch_scroll_button_bg.xml
index b793eec..1592055 100644
--- a/quickstep/res/drawable/bg_bubble_dismiss_circle.xml
+++ b/quickstep/res/color/keyboard_quick_switch_scroll_button_bg.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2025 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -15,13 +14,9 @@
   ~ limitations under the License.
   -->
 
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-    <stroke
-        android:width="2dp"
-        android:color="@android:color/system_accent1_600" />
-
-    <solid android:color="@android:color/system_accent1_600" />
-</shape>
\ No newline at end of file
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+        android:color="@color/materialColorPrimary"
+        android:alpha="0.38"/>
+    <item android:color="@color/materialColorPrimary"/>
+</selector>
\ No newline at end of file
diff --git a/quickstep/res/color/keyboard_quick_switch_scroll_button_fg.xml b/quickstep/res/color/keyboard_quick_switch_scroll_button_fg.xml
new file mode 100644
index 0000000..051c18f
--- /dev/null
+++ b/quickstep/res/color/keyboard_quick_switch_scroll_button_fg.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2025 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="true"
+        android:state_pressed="true"
+        android:color="@color/materialColorOnPrimary"
+        android:alpha="0.15"/>
+    <item android:state_enabled="true"
+        android:state_hovered="true"
+        android:color="@color/materialColorOnPrimary"
+        android:alpha="0.11" />
+    <item android:color="@android:color/transparent" />
+</selector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/bg_bubble_dismiss_circle.xml b/quickstep/res/color/keyboard_quick_switch_scroll_button_icon.xml
similarity index 65%
rename from quickstep/res/drawable/bg_bubble_dismiss_circle.xml
rename to quickstep/res/color/keyboard_quick_switch_scroll_button_icon.xml
index b793eec..74df84b 100644
--- a/quickstep/res/drawable/bg_bubble_dismiss_circle.xml
+++ b/quickstep/res/color/keyboard_quick_switch_scroll_button_icon.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2023 The Android Open Source Project
+  ~ Copyright (C) 2025 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -15,13 +15,9 @@
   ~ limitations under the License.
   -->
 
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-    <stroke
-        android:width="2dp"
-        android:color="@android:color/system_accent1_600" />
-
-    <solid android:color="@android:color/system_accent1_600" />
-</shape>
\ No newline at end of file
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_enabled="false"
+        android:color="@color/materialColorOnPrimary"
+        android:alpha="0.38"/>
+    <item android:color="@color/materialColorOnPrimary"/>
+</selector>
diff --git a/quickstep/res/drawable/app_chip_menu_bg.xml b/quickstep/res/drawable/app_chip_menu_bg.xml
new file mode 100644
index 0000000..499056e
--- /dev/null
+++ b/quickstep/res/drawable/app_chip_menu_bg.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/materialColorSurfaceBright"/>
+    <corners android:radius="@dimen/task_menu_corner_radius"/>
+</shape>
diff --git a/quickstep/res/drawable/bg_bubble_dismiss_circle.xml b/quickstep/res/drawable/bg_keyboard_quick_switch_scroll_button.xml
similarity index 61%
copy from quickstep/res/drawable/bg_bubble_dismiss_circle.xml
copy to quickstep/res/drawable/bg_keyboard_quick_switch_scroll_button.xml
index b793eec..7067f13 100644
--- a/quickstep/res/drawable/bg_bubble_dismiss_circle.xml
+++ b/quickstep/res/drawable/bg_keyboard_quick_switch_scroll_button.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2025 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -15,13 +14,7 @@
   ~ limitations under the License.
   -->
 
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-    <stroke
-        android:width="2dp"
-        android:color="@android:color/system_accent1_600" />
-
-    <solid android:color="@android:color/system_accent1_600" />
-</shape>
\ No newline at end of file
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <solid android:color="@color/keyboard_quick_switch_scroll_button_bg" />
+    <corners android:radius="@dimen/keyboard_quick_switch_scroll_button_corner_radius" />
+</shape>
diff --git a/quickstep/res/drawable/bg_overview_add_desktop_button.xml b/quickstep/res/drawable/bg_overview_add_desktop_button.xml
new file mode 100644
index 0000000..12581bf
--- /dev/null
+++ b/quickstep/res/drawable/bg_overview_add_desktop_button.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2025 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<ripple android:color="?android:attr/colorControlHighlight"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+    <item>
+        <shape
+            android:shape="rectangle"
+            android:tint="?colorButtonNormal">
+            <corners android:radius="@dimen/add_desktop_button_size" />
+            <solid android:color="@color/materialColorSurfaceBright"/>
+        </shape>
+    </item>
+</ripple>
\ No newline at end of file
diff --git a/quickstep/res/drawable/bg_overview_clear_all_button.xml b/quickstep/res/drawable/bg_overview_clear_all_button.xml
index 7f58cf8..2f28689 100644
--- a/quickstep/res/drawable/bg_overview_clear_all_button.xml
+++ b/quickstep/res/drawable/bg_overview_clear_all_button.xml
@@ -15,8 +15,7 @@
      limitations under the License.
 -->
 <ripple android:color="?android:attr/colorControlHighlight"
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+    xmlns:android="http://schemas.android.com/apk/res/android">
     <item>
         <shape android:shape="rectangle"
             android:tint="?colorButtonNormal">
diff --git a/quickstep/res/drawable/bg_bubble_dismiss_circle.xml b/quickstep/res/drawable/fg_keyboard_quick_switch_scroll_button.xml
similarity index 61%
copy from quickstep/res/drawable/bg_bubble_dismiss_circle.xml
copy to quickstep/res/drawable/fg_keyboard_quick_switch_scroll_button.xml
index b793eec..dd63f54 100644
--- a/quickstep/res/drawable/bg_bubble_dismiss_circle.xml
+++ b/quickstep/res/drawable/fg_keyboard_quick_switch_scroll_button.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2025 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -15,13 +14,7 @@
   ~ limitations under the License.
   -->
 
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-    <stroke
-        android:width="2dp"
-        android:color="@android:color/system_accent1_600" />
-
-    <solid android:color="@android:color/system_accent1_600" />
-</shape>
\ No newline at end of file
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <solid android:color="@color/keyboard_quick_switch_scroll_button_fg" />
+    <corners android:radius="@dimen/keyboard_quick_switch_scroll_button_corner_radius" />
+</shape>
diff --git a/quickstep/res/drawable/ic_bubble_dismiss_white.xml b/quickstep/res/drawable/ic_bubble_dismiss_white.xml
deleted file mode 100644
index b15111b..0000000
--- a/quickstep/res/drawable/ic_bubble_dismiss_white.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-    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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="20dp"
-    android:height="20dp"
-    android:viewportWidth="24.0"
-    android:viewportHeight="24.0">
-    <path
-        android:pathData="M19.000000,6.400000l-1.400000,-1.400000 -5.600000,5.600000 -5.600000,-5.600000 -1.400000,1.400000 5.600000,5.600000 -5.600000,5.600000 1.400000,1.400000 5.600000,-5.600000 5.600000,5.600000 1.400000,-1.400000 -5.600000,-5.600000z"
-        android:fillColor="@android:color/system_neutral1_50"/>
-</vector>
diff --git a/quickstep/res/drawable/ic_chevron_end.xml b/quickstep/res/drawable/ic_chevron_end.xml
new file mode 100644
index 0000000..9ca4f3a
--- /dev/null
+++ b/quickstep/res/drawable/ic_chevron_end.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:autoMirrored="true"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M504,480L320,296L376,240L616,480L376,720L320,664L504,480Z" />
+</vector>
diff --git a/quickstep/res/drawable/ic_chevron_start.xml b/quickstep/res/drawable/ic_chevron_start.xml
new file mode 100644
index 0000000..913da02
--- /dev/null
+++ b/quickstep/res/drawable/ic_chevron_start.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:autoMirrored="true"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M560,720L320,480L560,240L616,296L432,480L616,664L560,720Z" />
+</vector>
diff --git a/quickstep/res/drawable/ic_close_option.xml b/quickstep/res/drawable/ic_close_option.xml
new file mode 100644
index 0000000..5681cb5
--- /dev/null
+++ b/quickstep/res/drawable/ic_close_option.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z" />
+</vector>
diff --git a/quickstep/res/drawable/task_header_close_button.xml b/quickstep/res/drawable/task_header_close_button.xml
new file mode 100644
index 0000000..b409158
--- /dev/null
+++ b/quickstep/res/drawable/task_header_close_button.xml
@@ -0,0 +1,24 @@
+<!-- Copyright (C) 2025 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="18dp"
+    android:height="18dp"
+    android:viewportWidth="960"
+    android:viewportHeight="960"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/>
+</vector>
diff --git a/quickstep/res/drawable/task_thumbnail_header_bg.xml b/quickstep/res/drawable/task_thumbnail_header_bg.xml
new file mode 100644
index 0000000..52ac1ae
--- /dev/null
+++ b/quickstep/res/drawable/task_thumbnail_header_bg.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2025 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <solid android:color="@color/materialColorSurfaceBright" />
+    <corners android:topLeftRadius="@dimen/task_thumbnail_header_round_corner_radius"
+        android:topRightRadius="@dimen/task_thumbnail_header_round_corner_radius"/>
+</shape>
diff --git a/quickstep/res/layout/activity_allset.xml b/quickstep/res/layout/activity_allset.xml
index 625d9b3..3d68dfb 100644
--- a/quickstep/res/layout/activity_allset.xml
+++ b/quickstep/res/layout/activity_allset.xml
@@ -29,7 +29,6 @@
         android:layout_height="match_parent"
         android:gravity="center"
         android:scaleType="centerCrop"
-        app:lottie_autoPlay="true"
         app:lottie_loop="true"
 
         app:layout_constraintTop_toTopOf="parent"
@@ -49,11 +48,10 @@
         app:layout_constraintEnd_toEndOf="parent">
 
         <androidx.constraintlayout.widget.ConstraintLayout
-            android:id="@+id/text_content_view"
+            android:id="@+id/content"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_marginStart="@dimen/allset_page_margin_horizontal"
-            android:layout_marginEnd="@dimen/allset_page_margin_horizontal"
+            android:paddingHorizontal="@dimen/allset_page_padding_horizontal"
             android:layoutDirection="locale"
             android:textDirection="locale"
             android:forceHasOverlappingRendering="false"
diff --git a/quickstep/res/layout/floating_header_content.xml b/quickstep/res/layout/floating_header_content.xml
index b21c34b..0021e22 100644
--- a/quickstep/res/layout/floating_header_content.xml
+++ b/quickstep/res/layout/floating_header_content.xml
@@ -3,8 +3,8 @@
 
     <com.android.launcher3.appprediction.PredictionRowView
         android:id="@+id/prediction_row"
-        android:accessibilityPaneTitle="@string/title_app_suggestions"
         android:layout_width="match_parent"
+        android:importantForAccessibility="yes"
         android:layout_height="wrap_content" />
 
     <com.android.launcher3.appprediction.AppsDividerView
diff --git a/quickstep/res/layout/gesture_tutorial_fragment.xml b/quickstep/res/layout/gesture_tutorial_fragment.xml
index 64ad1f7..6e7ff86 100644
--- a/quickstep/res/layout/gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/gesture_tutorial_fragment.xml
@@ -153,16 +153,6 @@
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_fragment_feedback_title"/>
 
-        <com.android.quickstep.interaction.TutorialStepIndicator
-            android:id="@+id/gesture_tutorial_fragment_feedback_tutorial_step"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintTop_toTopOf="@id/gesture_tutorial_fragment_action_button"
-            app:layout_constraintBottom_toBottomOf="@id/gesture_tutorial_fragment_action_button"/>
-
         <Button
             android:id="@+id/gesture_tutorial_fragment_action_button"
             style="@style/TextAppearance.GestureTutorial.ButtonLabel"
diff --git a/quickstep/res/layout/icon_app_chip_view.xml b/quickstep/res/layout/icon_app_chip_view.xml
index 00b5392..0972be1 100644
--- a/quickstep/res/layout/icon_app_chip_view.xml
+++ b/quickstep/res/layout/icon_app_chip_view.xml
@@ -51,7 +51,7 @@
     <TextView
         android:id="@+id/icon_text_collapsed"
         android:layout_width="@dimen/task_thumbnail_icon_menu_text_collapsed_max_width"
-        android:layout_height="@dimen/task_thumbnail_icon_menu_app_icon_collapsed_size"
+        android:layout_height="wrap_content"
         android:gravity="start|center_vertical"
         android:maxLines="1"
         android:ellipsize="end"
@@ -62,7 +62,7 @@
     <TextView
         android:id="@+id/icon_text_expanded"
         android:layout_width="@dimen/task_thumbnail_icon_menu_text_expanded_max_width"
-        android:layout_height="@dimen/task_thumbnail_icon_menu_app_icon_collapsed_size"
+        android:layout_height="wrap_content"
         android:gravity="start|center_vertical"
         android:maxLines="1"
         android:ellipsize="end"
diff --git a/quickstep/res/layout/keyboard_quick_switch_desktop_taskview.xml b/quickstep/res/layout/keyboard_quick_switch_desktop_taskview.xml
index 71c782d..db47ff0 100644
--- a/quickstep/res/layout/keyboard_quick_switch_desktop_taskview.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_desktop_taskview.xml
@@ -48,13 +48,13 @@
 
             app:layout_constraintVertical_chainStyle="packed"
             app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintBottom_toTopOf="@id/text"
+            app:layout_constraintBottom_toTopOf="@id/small_text"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintEnd_toEndOf="parent"/>
 
         <TextView
             style="@style/KeyboardQuickSwitchText.OnTaskView"
-            android:id="@+id/text"
+            android:id="@+id/small_text"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:textAlignment="center"
diff --git a/quickstep/res/layout/keyboard_quick_switch_view.xml b/quickstep/res/layout/keyboard_quick_switch_view.xml
index 345b97c..2dea79c 100644
--- a/quickstep/res/layout/keyboard_quick_switch_view.xml
+++ b/quickstep/res/layout/keyboard_quick_switch_view.xml
@@ -18,6 +18,8 @@
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/keyboard_quick_switch_view"
+    android:contentDescription="@string/quick_switch_content_description"
+    android:accessibilityPaneTitle="@string/quick_switch_pane_title"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_marginTop="@dimen/keyboard_quick_switch_margin_top"
@@ -67,6 +69,24 @@
 
     </androidx.constraintlayout.widget.ConstraintLayout>
 
+    <ImageButton
+        android:id="@+id/scroll_button_start"
+        android:src="@drawable/ic_chevron_start"
+        android:contentDescription="@string/quick_switch_scroll_arrow_left"
+        android:background="@drawable/bg_keyboard_quick_switch_scroll_button"
+        android:foreground="@drawable/fg_keyboard_quick_switch_scroll_button"
+        android:tint="@color/keyboard_quick_switch_scroll_button_icon"
+        android:layout_width="@dimen/keyboard_quick_switch_scroll_button_width"
+        android:layout_height="@dimen/keyboard_quick_switch_scroll_button_height"
+        android:paddingHorizontal="@dimen/keyboard_quick_switch_scroll_button_horizontal_padding"
+        android:paddingVertical="@dimen/keyboard_quick_switch_scroll_button_vertical_padding"
+        android:layout_marginStart="@dimen/keyboard_quick_switch_view_spacing"
+        android:visibility="gone"
+
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"/>
+
     <HorizontalScrollView
         android:id="@+id/scroll_view"
         android:layout_width="wrap_content"
@@ -76,9 +96,12 @@
         android:alpha="0"
         android:visibility="gone"
 
+        app:layout_constraintHorizontal_bias="0"
+        app:layout_constrainedWidth="true"
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toStartOf="parent">
+        app:layout_constraintStart_toEndOf="@id/scroll_button_start"
+        app:layout_constraintEnd_toStartOf="@id/scroll_button_end">
 
         <androidx.constraintlayout.widget.ConstraintLayout
             android:id="@+id/content"
@@ -89,4 +112,22 @@
 
     </HorizontalScrollView>
 
+    <ImageButton
+        android:id="@+id/scroll_button_end"
+        android:src="@drawable/ic_chevron_end"
+        android:contentDescription="@string/quick_switch_scroll_arrow_right"
+        android:background="@drawable/bg_keyboard_quick_switch_scroll_button"
+        android:foreground="@drawable/fg_keyboard_quick_switch_scroll_button"
+        android:tint="@color/keyboard_quick_switch_scroll_button_icon"
+        android:layout_width="@dimen/keyboard_quick_switch_scroll_button_width"
+        android:layout_height="@dimen/keyboard_quick_switch_scroll_button_height"
+        android:paddingHorizontal="@dimen/keyboard_quick_switch_scroll_button_horizontal_padding"
+        android:paddingVertical="@dimen/keyboard_quick_switch_scroll_button_vertical_padding"
+        android:layout_marginEnd="@dimen/keyboard_quick_switch_view_spacing"
+        android:visibility="gone"
+
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintEnd_toEndOf="parent" />
+
 </com.android.launcher3.taskbar.KeyboardQuickSwitchView>
diff --git a/quickstep/res/layout/overview_add_desktop_button.xml b/quickstep/res/layout/overview_add_desktop_button.xml
index 2333dd1..a1c64f3 100644
--- a/quickstep/res/layout/overview_add_desktop_button.xml
+++ b/quickstep/res/layout/overview_add_desktop_button.xml
@@ -16,9 +16,11 @@
 -->
 <com.android.quickstep.views.AddDesktopButton
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apgk/res-auto"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:id="@+id/add_desktop_button"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
+    android:layout_width="@dimen/add_desktop_button_size"
+    android:layout_height="@dimen/add_desktop_button_size"
     android:src="@drawable/ic_desktop_add"
-    android:padding="10dp" />
\ No newline at end of file
+    android:background="@drawable/bg_overview_add_desktop_button"
+    launcher:focusBorderColor="@color/materialColorOutline"
+    android:padding="10dp" />
diff --git a/quickstep/res/layout/overview_clear_all_button.xml b/quickstep/res/layout/overview_clear_all_button.xml
index 18a6240..034c3c2 100644
--- a/quickstep/res/layout/overview_clear_all_button.xml
+++ b/quickstep/res/layout/overview_clear_all_button.xml
@@ -16,7 +16,6 @@
 -->
 <com.android.quickstep.views.ClearAllButton
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     xmlns:launcher="http://schemas.android.com/apk/res-auto"
     style="@style/OverviewClearAllButton"
     android:id="@+id/clear_all"
@@ -25,4 +24,4 @@
     android:text="@string/recents_clear_all"
     android:textColor="@color/materialColorOnSurface"
     launcher:focusBorderColor="@color/materialColorOutline"
-    android:textSize="14sp" />
\ No newline at end of file
+    android:textSize="14sp" />
diff --git a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
index b004dfd..55fe2b8 100644
--- a/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
+++ b/quickstep/res/layout/redesigned_gesture_tutorial_fragment.xml
@@ -137,6 +137,7 @@
         android:layout_above="@id/gesture_tutorial_fragment_action_button"
         android:layout_centerHorizontal="true"
         android:background="@android:color/transparent"
+        android:screenReaderFocusable="true"
         android:paddingTop="24dp"
         android:paddingHorizontal="24dp"
         android:layout_marginBottom="16dp">
@@ -146,7 +147,6 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginTop="104dp"
-            android:accessibilityHeading="true"
             android:gravity="top"
             android:lineSpacingExtra="-1sp"
             android:textAppearance="@style/TextAppearance.GestureTutorial.MainTitle"
@@ -165,16 +165,6 @@
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@id/gesture_tutorial_fragment_feedback_title" />
 
-        <com.android.quickstep.interaction.TutorialStepIndicator
-            android:id="@+id/gesture_tutorial_fragment_feedback_tutorial_step"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toTopOf="parent" />
-
         <Button
             android:id="@+id/gesture_tutorial_fragment_close_button"
             style="@style/TextAppearance.GestureTutorial.Feedback.Subtext"
@@ -224,6 +214,9 @@
         android:layout_marginBottom="@dimen/gesture_tutorial_done_button_bottom_margin"
         android:layout_alignParentBottom="true"
         android:layout_alignParentEnd="true"
+        android:clickable="true"
+        android:accessibilityTraversalAfter="@id/gesture_tutorial_fragment_feedback_subtitle"
+        android:contentDescription="@string/gesture_tutorial_action_button_label"
         android:background="@drawable/gesture_tutorial_action_button_background"
         android:stateListAnimator="@null"
         android:text="@string/gesture_tutorial_action_button_label"
diff --git a/quickstep/res/layout/split_instructions_view.xml b/quickstep/res/layout/split_instructions_view.xml
index a11974c..b433a59 100644
--- a/quickstep/res/layout/split_instructions_view.xml
+++ b/quickstep/res/layout/split_instructions_view.xml
@@ -29,9 +29,9 @@
         android:id="@+id/split_instructions_text"
         android:layout_height="wrap_content"
         android:layout_width="wrap_content"
-        android:maxWidth="@dimen/split_instructions_view_max_width"
         android:textColor="?androidprv:attr/textColorOnAccent"
-        android:text="@string/toast_split_select_app" />
+        android:text="@string/toast_split_select_app"
+        android:layout_weight="1" />
 
     <androidx.appcompat.widget.AppCompatTextView
         android:id="@+id/split_instructions_text_cancel"
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index a7f6b36..4abfbbe 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -28,7 +28,11 @@
     launcher:focusBorderColor="@color/materialColorOutline"
     launcher:hoverBorderColor="@color/materialColorPrimary">
 
-    <include layout="@layout/task_thumbnail_deprecated" />
+    <ViewStub
+        android:id="@+id/task_content_view"
+        android:inflatedId="@id/task_content_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
 
     <!-- Filtering affects only alpha instead of the visibility since visibility can be altered
          separately through RecentsView#resetFromSplitSelectionState() -->
diff --git a/quickstep/res/drawable/bg_bubble_dismiss_circle.xml b/quickstep/res/layout/task_content_view.xml
similarity index 60%
copy from quickstep/res/drawable/bg_bubble_dismiss_circle.xml
copy to quickstep/res/layout/task_content_view.xml
index b793eec..9055ccd 100644
--- a/quickstep/res/drawable/bg_bubble_dismiss_circle.xml
+++ b/quickstep/res/layout/task_content_view.xml
@@ -1,6 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2023 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2025 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -14,14 +13,8 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
-<shape
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="oval">
-
-    <stroke
-        android:width="2dp"
-        android:color="@android:color/system_accent1_600" />
-
-    <solid android:color="@android:color/system_accent1_600" />
-</shape>
\ No newline at end of file
+<com.android.quickstep.task.thumbnail.TaskContentView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/task_content_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical" />
\ No newline at end of file
diff --git a/quickstep/res/layout/task_grouped.xml b/quickstep/res/layout/task_grouped.xml
index 4c650b9..a7c4856 100644
--- a/quickstep/res/layout/task_grouped.xml
+++ b/quickstep/res/layout/task_grouped.xml
@@ -33,10 +33,17 @@
     launcher:focusBorderColor="@color/materialColorOutline"
     launcher:hoverBorderColor="@color/materialColorPrimary">
 
-    <include layout="@layout/task_thumbnail_deprecated"/>
+    <ViewStub
+        android:id="@+id/task_content_view"
+        android:inflatedId="@id/task_content_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
 
-    <include layout="@layout/task_thumbnail_deprecated"
-        android:id="@+id/bottomright_snapshot" />
+    <ViewStub
+        android:id="@+id/bottomright_task_content_view"
+        android:inflatedId="@id/bottomright_task_content_view"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
 
     <!-- Filtering affects only alpha instead of the visibility since visibility can be altered
          separately through RecentsView#resetFromSplitSelectionState() -->
diff --git a/quickstep/res/layout/task_header_view.xml b/quickstep/res/layout/task_header_view.xml
new file mode 100644
index 0000000..ea5c24e
--- /dev/null
+++ b/quickstep/res/layout/task_header_view.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+     Copyright (C) 2025 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<com.android.quickstep.views.TaskHeaderView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:id="@+id/task_header_view"
+    android:background="@drawable/task_thumbnail_header_bg">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/task_thumbnail_header_height"
+        android:layout_marginStart="@dimen/task_thumbnail_header_margin_edge"
+        android:layout_marginEnd="@dimen/task_thumbnail_header_margin_edge"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+        <ImageView
+            android:id="@+id/header_app_icon"
+            android:contentDescription="@string/header_app_icon_description"
+            android:layout_width="@dimen/task_thumbnail_header_icon_size"
+            android:layout_height="@dimen/task_thumbnail_header_icon_size"
+            android:layout_marginEnd="@dimen/task_thumbnail_header_margin_between_views"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/header_app_title"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent" />
+        <TextView
+            android:id="@+id/header_app_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="@dimen/task_thumbnail_header_margin_between_views"
+            android:maxLines="1"
+            android:text="@string/header_default_app_title"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toEndOf="@id/header_app_icon"
+            app:layout_constraintTop_toTopOf="parent" />
+        <ImageButton
+            android:id="@+id/header_close_button"
+            android:contentDescription="@string/header_close_icon_description"
+            android:layout_width="@dimen/task_thumbnail_header_icon_size"
+            android:layout_height="@dimen/task_thumbnail_header_icon_size"
+            android:layout_marginStart="@dimen/task_thumbnail_header_margin_between_views"
+            android:src="@drawable/task_header_close_button"
+            android:tint="@android:color/darker_gray"
+            android:background="@null"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toEndOf="@id/header_app_title"
+            app:layout_constraintHorizontal_bias="1" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.quickstep.views.TaskHeaderView>
diff --git a/quickstep/res/layout/task_menu.xml b/quickstep/res/layout/task_menu.xml
index b6d8786..abc728a 100644
--- a/quickstep/res/layout/task_menu.xml
+++ b/quickstep/res/layout/task_menu.xml
@@ -43,8 +43,7 @@
             android:id="@+id/menu_option_layout"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:orientation="horizontal"
-            android:showDividers="middle" />
+            android:orientation="horizontal" />
 
     </ScrollView>
 
diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml
index afbcdb5..8280e13 100644
--- a/quickstep/res/layout/task_thumbnail.xml
+++ b/quickstep/res/layout/task_thumbnail.xml
@@ -15,10 +15,10 @@
 -->
 <com.android.quickstep.task.thumbnail.TaskThumbnailView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/snapshot"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
+    android:layout_height="0dp"
+    android:layout_weight="1" >
 
     <com.android.quickstep.views.FixedSizeImageView
         android:id="@+id/task_thumbnail"
@@ -53,10 +53,7 @@
         android:id="@+id/splash_icon"
         android:layout_width="@dimen/task_thumbnail_splash_icon_size"
         android:layout_height="@dimen/task_thumbnail_splash_icon_size"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_gravity="center"
         android:scaleType="fitCenter"
         android:alpha="0"
         android:importantForAccessibility="no" />
diff --git a/quickstep/res/layout/taskbar_edu_features.xml b/quickstep/res/layout/taskbar_edu_features.xml
index a7bd184..aa1312e 100644
--- a/quickstep/res/layout/taskbar_edu_features.xml
+++ b/quickstep/res/layout/taskbar_edu_features.xml
@@ -20,7 +20,7 @@
     android:layout_height="wrap_content">
 
     <TextView
-        android:id="@+id/title"
+        android:id="@+id/taskbar_edu_title"
         style="@style/TextAppearance.TaskbarEduTooltip.Title"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
@@ -42,7 +42,7 @@
 
         app:layout_constraintEnd_toEndOf="@id/splitscreen_text"
         app:layout_constraintStart_toStartOf="@id/splitscreen_text"
-        app:layout_constraintTop_toBottomOf="@id/title" />
+        app:layout_constraintTop_toBottomOf="@id/taskbar_edu_title" />
 
     <TextView
         android:id="@+id/splitscreen_text"
@@ -72,7 +72,7 @@
 
         app:layout_constraintEnd_toEndOf="@id/pinning_text"
         app:layout_constraintStart_toStartOf="@id/pinning_text"
-        app:layout_constraintTop_toBottomOf="@id/title" />
+        app:layout_constraintTop_toBottomOf="@id/taskbar_edu_title" />
 
     <TextView
         android:id="@+id/pinning_text"
@@ -96,7 +96,7 @@
 
         app:layout_constraintEnd_toEndOf="@id/suggestions_text"
         app:layout_constraintStart_toStartOf="@id/suggestions_text"
-        app:layout_constraintTop_toBottomOf="@id/title" />
+        app:layout_constraintTop_toBottomOf="@id/taskbar_edu_title" />
 
     <TextView
         android:id="@+id/suggestions_text"
diff --git a/quickstep/res/layout/taskbar_edu_pinning.xml b/quickstep/res/layout/taskbar_edu_pinning.xml
index 27a7b23..5937d62 100644
--- a/quickstep/res/layout/taskbar_edu_pinning.xml
+++ b/quickstep/res/layout/taskbar_edu_pinning.xml
@@ -19,7 +19,7 @@
     android:layout_height="wrap_content">
 
     <TextView
-        android:id="@+id/title"
+        android:id="@+id/taskbar_edu_title"
         style="@style/TextAppearance.TaskbarEduTooltip.Title"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
@@ -37,7 +37,7 @@
         app:layout_constraintBottom_toTopOf="@id/pinning_text"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/title"
+        app:layout_constraintTop_toBottomOf="@id/taskbar_edu_title"
         app:lottie_rawRes="@raw/taskbar_edu_pinning"
         app:lottie_autoPlay="true"
         app:lottie_loop="true" />
diff --git a/quickstep/res/layout/taskbar_edu_search.xml b/quickstep/res/layout/taskbar_edu_search.xml
index ca84f35..ec4d4b4 100644
--- a/quickstep/res/layout/taskbar_edu_search.xml
+++ b/quickstep/res/layout/taskbar_edu_search.xml
@@ -19,7 +19,7 @@
     android:layout_height="wrap_content">
 
     <TextView
-        android:id="@+id/title"
+        android:id="@+id/taskbar_edu_title"
         style="@style/TextAppearance.TaskbarEduTooltip.Title"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
@@ -37,7 +37,7 @@
         app:layout_constraintBottom_toTopOf="@id/search_edu_text"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/title"
+        app:layout_constraintTop_toBottomOf="@id/taskbar_edu_title"
         app:lottie_rawRes="@raw/taskbar_edu_search"
         app:lottie_autoPlay="true"
         app:lottie_loop="true" />
diff --git a/quickstep/res/layout/taskbar_edu_swipe.xml b/quickstep/res/layout/taskbar_edu_swipe.xml
index 3f5e819..9b4809e 100644
--- a/quickstep/res/layout/taskbar_edu_swipe.xml
+++ b/quickstep/res/layout/taskbar_edu_swipe.xml
@@ -19,7 +19,7 @@
     android:layout_height="wrap_content">
 
     <TextView
-        android:id="@+id/title"
+        android:id="@+id/taskbar_edu_title"
         style="@style/TextAppearance.TaskbarEduTooltip.Title"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
@@ -36,7 +36,7 @@
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toBottomOf="@id/title"
+        app:layout_constraintTop_toBottomOf="@id/taskbar_edu_title"
         app:lottie_autoPlay="true"
         app:lottie_loop="true"
         app:lottie_rawRes="@raw/taskbar_edu_stashing" />
diff --git a/quickstep/res/values-af/strings.xml b/quickstep/res/values-af/strings.xml
index 365916e..7a6435b 100644
--- a/quickstep/res/values-af/strings.xml
+++ b/quickstep/res/values-af/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Vormvry"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Rekenaar"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Skuif na eksterne skerm"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Maak toe"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Werkskerm"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Geen onlangse items nie"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Programgebruikinstellings"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Vee alles uit"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Onlangse programme"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Taak is toegemaak"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minuut"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> oor vandag"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Programvoorstelle is geaktiveer"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Programvoorstelle is gedeaktiveer"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Voorspelde app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Gebaarnavigasietutoriaal"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Draai jou toestel"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Draai asseblief jou toestel om die tutoriaal oor gebaarnavigasie te voltooi"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Maak seker dat jy van die rand heel regs of heel links af swiep"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Gaan na Instellings om sensitiwiteit van teruggebaar te verander"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Swiep om terug te gaan"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Swiep van die linker- of regterrand na die middel van die skerm om na die vorige skerm terug te gaan."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Swiep met 2 vingers van die linker- of regterkant van die skerm af na die middel toe om terug te keer na die vorige skerm."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Gaan terug"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Swiep van die linker- of regterrand af na die middel van die skerm toe"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Maak seker dat jy van die onderrand van die skerm af opswiep"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Jy het die Gaan na Tuisskerm-gebaar voltooi"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Swiep om na tuisskerm toe te gaan"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Swiep op van die onderkant van jou skerm af. Hierdie gebaar neem jou altyd na die tuisskerm toe."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Swiep met 2 vingers op van die onderkant van die skerm af. Dié gebaar neem jou altyd na die tuisskerm toe."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Gaan na tuisskerm"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Swiep van die onderkant van jou skerm af op"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Knap gedaan!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Jy het die Wissel Apps-gebaar voltooi"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Swiep om tussen programme te wissel"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Swiep van die onderkant van jou skerm af op, hou en laat los dan om tussen apps te wissel."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Swiep met 2 vingers op van die onderkant van jou skerm af, hou en laat los dan om tussen apps te wissel."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Wissel apps"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Swiep van die onderkant van jou skerm af op, hou, en los dan"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Welgedaan!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taakbalk word gewys"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taakbalk en borrels wys links"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taakbalk en borrels wys regs"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taakbalk is versteek"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taakbalk en borrels is versteek"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigasiebalk"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Wys altyd Taakbalk"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Verander navigasiemodus"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taakbalkoorloop"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Skuif na links bo"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Skuif na regs onder"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{meer app}other{meer apps}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Werkskerm"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"vou <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> uit"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"vou <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> in"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Omkring en Soek"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Appikoon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Apptitel"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Maak Toe-knoppie"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-am/strings.xml b/quickstep/res/values-am/strings.xml
index b9ee381..7286f3f 100644
--- a/quickstep/res/values-am/strings.xml
+++ b/quickstep/res/values-am/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"ነፃ ቅጽ"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ዴስክቶፕ"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"ወደ ውጫዊ ማሳያ አንቀሳቅስ"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"ዝጋ"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ዴስክቶፕ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ምንም የቅርብ ጊዜ ንጥሎች የሉም"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"የመተግበሪያ አጠቃቀም ቅንብሮች"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ሁሉንም አጽዳ"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"የቅርብ ጊዜ መተግበሪያዎች"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"ተግባር ተዘግቷል"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>፣ <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 ደቂቃ"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"ዛሬ <xliff:g id="TIME">%1$s</xliff:g> ቀርቷል"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"የመተግበሪያ አስተያየት ጥቆማዎች ነቅቷል"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"የመተግበሪያ አስተያየቶች ቦዝነዋል"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"የተገመተው መተግበሪያ፦ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"የእጅ ምልክት ዳሰሳ አጋዥ ሥልጠና"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"መሣሪያዎን ያሽከርክሩ"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"የእጅ ምልክት ዳሰሳ አጋዥ ሥልጠናን ለማጠናቀቅ እባክዎ መሣሪያዎን ያሽከርክሩ"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ከቀኝ ጥግ ወይም ከግራ ጥግ ጠርዝ ጀምሮ ማንሸራተትዎን ያረጋግጡ"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"ከኋላ ስሜት ሰጭነት ደረጃ ለመለወጥ ወደ ቅንብሮች ይመለሱ"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"ወደኋላ ለመመለስ ያንሸራትቱ"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"ወደ መጨረሻው ማያ ገፅ ለመመለስ ከግራ ወይም ከቀኝ ጠርዝ ወደ ማያ ገጹ መሃል ያንሸራትቱ።"</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"ወደ መጨረሻው ማያ ገፅ ለመመለስ በ2 ጣቶች ከግራ ወይም ከቀኝ ጠርዝ ወደ ማያ ገጹ መሃል ያንሸራትቱ።"</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"ተመለስ"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"ከግራ ወይም ከቀኝ ጠርዝ ወደ ማያ ገጹ መካከል ያንሸራትቱ"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"ከማያ ገጹ የታችኛው ጠርዝ ወደ ላይ ማንሸራተትዎን ያረጋግጡ"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"ወደ መነሻ ሂድ ምልክትን አጠናቅቀዋል"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"ወደ መነሻ ለመሄድ ያንሸራትቱ"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"ከእርስዎ ማያ ገፅ ግርጌ ላይ ወደ ላይ በጣት ጠረግ ያድርጉ። ይህ የእጅ ውዝዋዜ ሁልጊዜ ወደ መነሻ ማያ ገፅ ይወስድዎታል።"</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"በ2 ጣቶች ከማያ ገጹ ግርጌ ወደ ላይ ያንሸራትቱ። ይህ የእጅ ምልክት ሁልጊዜ ወደ መነሻ ማያ ገፅ ይወስደዎታል።"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"ወደ መነሻ ይሂዱ"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"ከማያ ገጽዎ የታችኛው ክፍል ወደ ላይ ያንሸራትቱ"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"ጥሩ ሠርተዋል!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"መተግበሪያዎችን ቀይር ምልክትን አጠናቅቀዋል"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"መተግበሪያዎችን ለመቀየር ያንሸራትቱ"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"በመተግበሪያዎች መካከል ለመቀያየር ከማያ ገጽዎ ግርጌ ወደ ላይ ያንሸራትቱ፣ ይያዙ፣ ከዚያ ይለቀቁ።"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"በመተግበሪያዎች መካከል ለመቀያየር ከማያ ገጽዎ ግርጌ ላይ በ2 ጣቶች ያንሸራትቱ፣ ይያዙ፣ ከዚያ ይለቀቁ።"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"መተግበሪያዎችን ይቀያይሩ"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"ከማያ ገጽዎ የታችኛው ክፍል ወደ ላይ ያንሸራትቱ፣ ይያዙ፣ ከዚያ ይልቀቁ"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"ጥሩ ሰርተዋል!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"የተግባር አሞሌ ይታያል"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"የተግባር አሞሌ እና አረፋዎች በግራ በኩል ይታያሉ"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"የተግባር አሞሌ እና አረፋዎች በቀኝ በኩል ይታያሉ"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"የተግባር አሞሌ ተደብቋል"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"የተግባር አሞሌ እና አረፋዎች ተደብቀዋል"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"የአሰሳ አሞሌ"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"ሁልጊዜ የተግባር አሞሌ ያሳዩ"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"የአሰሳ ሁነታን ይለውጡ"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"የተግባር አሞሌ ትርፍ ፍሰት"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ወደ ላይ/ግራ ይውሰዱ"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ወደ ታች/ቀኝ ይውሰዱ"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ተጨማሪ መተግበሪያ}one{ተጨማሪ መተግበሪያ}other{ተጨማሪ መተግበሪያዎች}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"ዴስክቶፕ"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> እና <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ን ዘርጋ"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ን ሰብስብ"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"ለመፈለግ ክበብ"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"የመተግበሪያ አዶ"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"የመተግበሪያ ርዕስ"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"የዝጋ አዝራር"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ar/strings.xml b/quickstep/res/values-ar/strings.xml
index b699d93..e673ac6 100644
--- a/quickstep/res/values-ar/strings.xml
+++ b/quickstep/res/values-ar/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"شكل مجاني"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"الكمبيوتر المكتبي"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"نقل التطبيق إلى شاشة خارجية"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"إغلاق"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"كمبيوتر مكتبي"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ما مِن عناصر تم استخدامها مؤخرًا"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"إعدادات استخدام التطبيق"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"محو الكل"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"التطبيقات المستخدمة مؤخرًا"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"تم إغلاق المهمة."</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>، <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"أقل من دقيقة"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"يتبقى اليوم <xliff:g id="TIME">%1$s</xliff:g>."</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"تم تفعيل ميزة \"التطبيقات المقترحة\"."</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ميزة \"التطبيقات المقترحة\" غير مفعّلة."</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"التطبيق المتوقع: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"دليل توجيهي للتنقُّل بالإيماءات"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"يُرجى تدوير الجهاز"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"يُرجى تدوير جهازك لإكمال الدليل التوجيهي للتنقُّل بالإيماءات."</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"تأكَّد من التمرير سريعًا من أقصى الحافة اليسرى أو اليمنى."</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"لتغيير مستوى حساسية إيماءة الرجوع، انتقِل إلى \"الإعدادات\""</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"مرِّر سريعًا للرجوع."</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"للرجوع إلى الشاشة السابقة، مرِّر سريعًا من الحافة اليسرى أو الحافة اليمنى إلى وسط الشاشة."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"للرجوع إلى الشاشة السابقة، عليك التمرير سريعًا بإصبعين من الحافة اليسرى أو اليمنى نحو وسط الشاشة."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"الرجوع"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"مرِّر سريعًا من الحافة اليمنى أو اليسرى إلى منتصف الشاشة."</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"تأكَّد من التمرير سريعًا من الحافة السفلية للشاشة إلى أعلاها."</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"لقد أكملت التدريب على إيماءة الانتقال إلى الشاشة الرئيسية."</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"مرِّر سريعًا للانتقال إلى الشاشة الرئيسية"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"مرِّر سريعًا من أسفل الشاشة إلى أعلاها. تنقلك هذه الإيماءة دائمًا إلى الشاشة الرئيسية."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"مرِّر سريعًا بإصبعين من أسفل الشاشة إلى أعلاها. تنقلك هذه الإيماءة دائمًا إلى الشاشة الرئيسية."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"الانتقال إلى الشاشة الرئيسية"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"مرِّر سريعًا من أسفل الشاشة إلى أعلاها."</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"أحسنت صنعًا."</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"لقد أكملت التدريب على إيماءة التبديل بين التطبيقات."</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"التمرير سريعًا للتبديل بين التطبيقات"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"للتبديل بين التطبيقات، مرِّر سريعًا من أسفل الشاشة إلى الأعلى ثمّ ثبِّت إصبعك قليلاً قبل رفعه."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"للتبديل بين التطبيقات، مرِّر سريعًا من أسفل الشاشة إلى أعلاها بإصبعين، ثمّ ثبّتهما قليلاً قبل رفعهما."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"تبديل التطبيقات"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"مرِّر سريعًا من أسفل الشاشة إلى أعلاها، وأبقِ إصبعك على الشاشة قليلاً ثم ارفعه."</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"أحسنت."</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"تم إظهار شريط التطبيقات"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"يتم عرض \"شريط التطبيقات\" و\"فقاعات المحادثات\" يمينًا"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"يتم عرض \"شريط التطبيقات\" و\"فقاعات المحادثات\" يسارًا"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"تم إخفاء شريط التطبيقات"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"تم إخفاء \"شريط التطبيقات\" و\"فقاعات المحادثات\""</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"شريط التنقل"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"إظهار شريط التطبيقات دائمًا"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"تغيير وضع التنقل"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"القائمة الكاملة لشريط التطبيقات"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"الانتقال إلى يمين الشاشة أو أعلاها"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"الانتقال إلى يسار الشاشة أو أسفلها"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{تطبيق واحد آخر}zero{تطبيق آخر}two{تطبيقان آخران}few{تطبيقات أخرى}many{تطبيقًا آخر}other{تطبيق آخر}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"وضع الكمبيوتر المكتبي"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"\"<xliff:g id="APP_NAME_1">%1$s</xliff:g>\" و\"<xliff:g id="APP_NAME_2">%2$s</xliff:g>\""</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"توسيع <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"تصغير <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"دائرة البحث"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"رمز التطبيق"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"عنوان التطبيق"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"زر الإغلاق"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-as/strings.xml b/quickstep/res/values-as/strings.xml
index 7599530..4f3c22d 100644
--- a/quickstep/res/values-as/strings.xml
+++ b/quickstep/res/values-as/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ডেস্কটপ"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"বাহ্যিক ডিছপ্লে’লৈ নিয়ক"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"বন্ধ কৰক"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ডেস্কটপ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"কোনো শেহতীয়া বস্তু নাই"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"এপে ব্যৱহাৰ কৰা ডেটাৰ ছেটিং"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"আটাইবোৰ মচক"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"শেহতীয়া এপ্‌সমূহ"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"কাৰ্য বন্ধ কৰা হ’ল"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; ১ মিনিট"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"আজি <xliff:g id="TIME">%1$s</xliff:g> বাকী আছ"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"এপৰ পৰামৰ্শসমূহ সক্ষম কৰা আছে"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"এপৰ পৰামৰ্শসমূহ অক্ষম কৰা আছে"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"পূৰ্বানুমান কৰা এপ্: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"আঙুলিৰ স্পৰ্শৰ নিৰ্দেশেৰে কৰা নেভিগেশ্বন সম্পৰ্কীয় টিউট’ৰিয়েল"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"আপোনাৰ ডিভাইচটো ঘূৰাওক"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"আঙুলিৰ স্পৰ্শৰ নিৰ্দেশেৰে কৰা নেভিগেশ্বনৰ টিউট’ৰিয়েল শেষ কৰিবলৈ অনুগ্ৰহ কৰি আপোনাৰ ডিভাইচটো ঘূৰাওক"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"আপুনি সোঁ অথবা বাওঁ কাষৰ একেবাৰে সীমাৰ পৰা ছোৱাইপ কৰাটো নিশ্চিত কৰক"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"উভতি যোৱাৰ নির্দেশটোৰ সংবেদনশীলতা সলনি কৰিবলৈ ছেটিঙলৈ যাওক"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"উভতি যাবলৈ ছোৱাইপ কৰক"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"অন্তিম স্ক্ৰীনখনলৈ উভতি যাবলৈ বাওঁ অথবা সোঁ প্ৰান্তৰৰ পৰা মাজলৈ ছোৱাইপ কৰক।"</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"অন্তিম স্ক্ৰীনখনলৈ উভতি যাবলৈ ২ টা আঙুলিৰে স্ক্ৰীনখনৰ বাওঁ অথবা সোঁ প্ৰান্তৰৰ পৰা মাজলৈ ছোৱাইপ কৰক।"</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"উভতি যাওক"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"স্ক্ৰীনখনৰ বাওঁ অথবা সোঁ প্ৰান্তৰৰ পৰা মধ্যভাগলৈ ছোৱাইপ কৰক"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"আপুনি স্ক্ৰীনৰ তলৰ প্ৰান্তৰ পৰা ওপৰলৈ ছোৱাইপ কৰাটো নিশ্চিত কৰক"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"আপুনি গৃহ স্ক্ৰীনলৈ যোৱাৰ নিৰ্দেশটো সম্পূৰ্ণ কৰিলে"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"গৃহ স্ক্ৰীনলৈ যাবলৈ ছোৱাইপ কৰক"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"আপোনাৰ স্ক্ৰীনৰ তলৰ অংশৰ পৰা ওপৰলৈ ছোৱাইপ কৰক। এই নিৰ্দেশটোৱে আপোনাক সদায় গৃহ স্ক্ৰীনলৈ লৈ যায়।"</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"স্ক্ৰীনখনৰ একেবাৰে তলৰ পৰা ২ টা আঙুলিৰে ওপৰলৈ ছোৱাইপ কৰক। এই নিৰ্দেশটোৱে আপোনাক সদায় গৃহ স্ক্ৰীনলৈ লৈ যায়।"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"গৃহ পৃষ্ঠালৈ যাওক"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"আপোনাৰ স্ক্ৰীনৰ একেবাৰে তলৰ অংশৰ পৰা ওপৰলৈ ছোৱাইপ কৰক"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"বঢ়িয়া!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"আপুনি এপ্‌ সলনি কৰাৰ নিৰ্দেশটো সম্পূৰ্ণ কৰিলে"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"এপ্‌ সলনি কৰিবলৈ ছোৱাইপ কৰক"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"এপ্‌সমূহ সালসলনিকৈ ব্যৱহাৰ কৰিবলৈ আপোনাৰ স্ক্ৰীনৰ একেবাৰে তলৰ অংশৰ পৰা ওপৰলৈ ছোৱাইপ কৰি ধৰি ৰাখক আৰু তাৰ পাছত এৰি দিয়ক।"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"এপ্‌সমূহ সালসলনিকৈ ব্যৱহাৰ কৰিবলৈ ২ টা আঙুলিৰে আপোনাৰ স্ক্ৰীনৰ একেবাৰে তলৰ অংশৰ পৰা ওপৰলৈ ছোৱাইপ কৰি ধৰি ৰাখক আৰু তাৰ পাছত এৰি দিয়ক।"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"এপ্‌ সলনি কৰক"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"আপোনাৰ স্ক্ৰীনৰ তলৰ অংশৰ পৰা ওপৰলৈ ছোৱাইপ কৰক, ধৰি ৰাখক, তাৰ পাছত এৰি দিয়ক"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"বঢ়িয়া!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"টাস্কবাৰ দেখুওৱা হৈছে"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"টাস্কবাৰ আৰু বাবল বাওঁফালে দেখুওৱা হৈছে"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"টাস্কবাৰ আৰু বাবল সোঁফালে দেখুওৱা হৈছে"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"টাস্কবাৰ লুকুৱাই থোৱা হৈছে"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"টাস্কবাৰ আৰু বাবল লুকুওৱা হৈছে"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"নেভিগেশ্বনৰ দণ্ড"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"টাস্কবাৰ সদায় দেখুৱাওক"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"নেভিগেশ্বন ম’ড সলনি কৰক"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"টাস্কবাৰ অ’ভাৰফ্ল"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ওপৰৰ বাঁওফাললৈ নিয়ক"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"তলৰ সোঁফাললৈ নিয়ক"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{অধিক এপ্‌}one{অধিক এপ্‌}other{অধিক এপ্‌}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"ডেস্কটপ"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> আৰু <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> বিস্তাৰ কৰক"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> সংকোচন কৰক"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"সন্ধান কৰিবৰ বাবে বৃত্ত"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"এপৰ আইকন"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"এপৰ শিৰোনাম"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"বন্ধ কৰা বুটাম"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-az/strings.xml b/quickstep/res/values-az/strings.xml
index 6c5748d..f7f22e2 100644
--- a/quickstep/res/values-az/strings.xml
+++ b/quickstep/res/values-az/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Sərbəst rejim"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Masaüstü"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Xarici displeyə köçürün"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Bağlayın"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Masaüstü"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Son elementlər yoxdur"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Tətbiq istifadə ayarları"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Hamısını silin"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Son tətbiqlər"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Tapşırıq bağlanıb"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 dəq"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Bu gün <xliff:g id="TIME">%1$s</xliff:g> qaldı"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Tətbiq təklifləri aktivdir"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Tətbiq təklifləri deaktivdir"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Proqnozlaşdırılan tətbiq: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Jest naviqasiyası üçün öyrədici"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Cihazı fırladın"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Jest naviqasiyası təlimatını tamamlamaq üçün cihazı fırladın"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Ən sağ və ya sol kənardan sürüşdürün"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Geri qayıtma jestinin həssaslığını dəyişmək üçün Ayarlara keçin"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Geri qayıtmaq üçün sürüşdürün"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Sonuncu ekrana qayıtmaq üçün ekranın sol, yaxud sağ kənarından mərkəzinə doğru sürüşdürün."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Sonuncu ekrana qayıtmaq üçün 2 barmaqla ekranın sol, yaxud sağ kənarından mərkəzinə doğru sürüşdürün."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Geri qayıdın"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Sol və ya sağ kənardan ekranın ortasına sürüşdürün"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Ekranın aşağı kənarından yuxarı sürüşdürün"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Əsas ekrana keçid jestini tamamladınız"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Əsas səhifəyə keçmək üçün sürüşdürün"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Ekranın aşağısından yuxarısına sürüşdürün. Bu jest həmişə Əsas səhifəyə aparır."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"2 barmaqla ekranın aşağısından yuxarısına sürüşdürün. Bu jest həmişə Əsas səhifəyə aparır."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Əsas səhifəyə keçin"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Ekranın aşağısından yuxarı sürüşdürün"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Əla!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Tətbiqlər arasında keçid jestini tamamladınız"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Tətbiqi keçirmək üçün sürüşdürün"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Tətbiqlər arasında keçid üçün ekranın aşağısından yuxarı doğru sürüşdürüb saxlayın, sonra buraxın."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Tətbiqlər arasında keçid üçün 2 barmaqla ekranın aşağısından yuxarı doğru sürüşdürüb saxlayın, sonra buraxın."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Tətbiqləri dəyişin"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Ekranın aşağısından yuxarı sürüşdürüb saxlayın, sonra buraxın"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Afərin!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"İşləmə paneli göstərilir"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"İşləmə paneli, yumrucuqlar solda göstərilir"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"İşləmə paneli, yumrucuqlar sağda göstərilir"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"İşləmə paneli gizlədilib"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"İşləmə paneli, yumrucuqlar gizlədilib"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Naviqasiya paneli"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"İşləmə paneli həmişə görünsün"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Naviqasiya rejimini dəyişin"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Tapşırıqlar Paneli üzrə əlavə menyu"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Yuxarı/sola köçürün"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Aşağı/sağa köçürün"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{əlavə tətbiq}other{əlavə tətbiq}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Masaüstü"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"genişləndirin: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"yığcamlaşdırın: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Dairəyə alaraq axtarın"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Tətbiq ikonası"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Tətbiq başlığı"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Qapatma düyməsi"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-b+sr+Latn/strings.xml b/quickstep/res/values-b+sr+Latn/strings.xml
index cbcffdf..67ecbcd 100644
--- a/quickstep/res/values-b+sr+Latn/strings.xml
+++ b/quickstep/res/values-b+sr+Latn/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Slobodni oblik"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Računar"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Premestite na spoljni ekran"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Zatvori"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Računari"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Podešavanja korišćenja aplikacije"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Obriši sve"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nedavne aplikacije"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Zadatak je zatvoren"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Još <xliff:g id="TIME">%1$s</xliff:g> danas"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Predlozi aplikacija su omogućeni"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Predlozi aplikacija su onemogućeni"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predviđamo aplikaciju: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Vodič za navigaciju pomoću pokreta"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotirajte uređaj"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rotirajte uređaj da biste dovršili vodič za navigaciju pomoću pokreta"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Obavezno prevucite od same desne ili leve ivice"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Osetljivost pok. za nazad možete da promenite u Podešavanjima"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Prevucite da biste se vratili unazad"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Da biste se vratili na poslednji ekran, prevucite od leve ili desne ivice do sredine ekrana."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Da biste se vratili na poslednji ekran, prevucite pomoću dva prsta od leve ili desne ivice do sredine ekrana."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Nazad"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Prevucite od leve ili desne ivice do sredine ekrana"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Obavezno prevucite nagore od donje ivice ekrana"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Dovršili ste pokret za povratak na početnu stranicu."</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Prevucite da biste otišli na početnu stranicu"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Prevucite nagore od dna ekrana. Ovaj pokret vas uvek vodi na početni ekran."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Prevucite pomoću dva prsta nagore od dna ekrana. Ovim pokretom uvek otvarate početni ekran."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Idite na početni ekran"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Prevucite nagore sa donjeg dela ekrana"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Odlično!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Dovršili ste pokret za promenu aplikacija"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Prevucite da biste zamenili aplikacije"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Za prelazak sa jedne aplikacije na drugu prevucite nagore od dna ekrana, zadržite, pa pustite."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Za prelazak između aplikacija prevucite pomoću dva prsta nagore od dna ekrana, zadržite, pa pustite."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Pređite na drugu aplikaciju"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Prevucite nagore od dna ekrana, zadržite, pa pustite"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Odlično!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Traka zadataka je prikazana"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Prikaz zadataka/oblačića levo"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Prikaz zadataka/oblačića desno"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Traka zadataka je skrivena"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Skriveni zadaci/oblačići"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Traka za navigaciju"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Uvek prikazuj traku zadataka"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Promeni režim navigacije"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Preklopna traka zadataka"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Premesti gore levo"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premesti dole desno"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{dodatna aplikacija}one{dodatna aplikacija}few{dodatne aplikacije}other{dodatnih aplikacija}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"proširite oblačić <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"skupite oblačić <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Pretraga zaokruživanjem"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikona aplikacije"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Naziv aplikacije"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Dugme Zatvori"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-be/strings.xml b/quickstep/res/values-be/strings.xml
index 103e243..436ed3f 100644
--- a/quickstep/res/values-be/strings.xml
+++ b/quickstep/res/values-be/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Адвольная форма"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Працоўны стол"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Перамясціць на знешні дысплэй"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Закрыць"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Працоўны стол"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Няма новых элементаў"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Налады выкарыстання праграмы"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Ачысціць усё"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Нядаўнія праграмы"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Задача закрыта"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 хв"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Сёння засталося <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Прапановы праграм уключаны"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Прапановы праграм выключаны"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Праграма з падказкі: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Дапаможнік па навігацыі жэстамі"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Павярніце прыладу"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Каб пачаць азнаямленне з дапаможнікам па навігацыі жэстамі, павярніце прыладу"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Правядзіце пальцам справа налева ці злева направа ад самага краю экрана"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Каб змяніць адчувальнасць жэста вяртання, адкрыйце налады"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Правядзіце пальцам, каб вярнуцца"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Каб вярнуцца на папярэдні экран, правядзіце пальцам ад левага ці правага краю да цэнтра экрана."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Каб вярнуцца на папярэдні экран, правядзіце двума пальцамі ад левага ці правага краю ў цэнтр экрана."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Назад"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Правядзіце пальцам па экране злева направа ці справа налева ў цэнтр экрана"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Правядзіце пальцам знізу ўверх з ніжняга краю экрана"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Вы навучыліся рабіць жэст для пераходу на галоўны экран"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Правядзіце пальцам для пераходу на галоўны экран"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Правядзіце пальцам па экране знізу ўверх. Гэты жэст дазваляе вярнуцца на Галоўны экран."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Правядзіце двума пальцамі па экране знізу ўверх. Гэты жэст дазваляе вярнуцца на Галоўны экран."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Пераход на галоўны экран"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Правядзіце пальцам па экране знізу ўверх"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"У вас добра атрымліваецца!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Вы навучыліся рабіць жэст для пераключэння паміж праграмамі"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Правядзіце пальцам для пераключэння паміж праграмамі"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Каб пераключыцца на іншую праграму, правядзіце па экране знізу ўверх, патрымайце палец і адпусціце."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Каб пераключыцца на іншую праграму, правядзіце двума пальцамі знізу ўверх, патрымайце і адпусціце."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Пераключэнне праграм"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Правядзіце пальцам уверх ад ніжняга краю экрана, затрымайце палец, а потым адпусціце"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Выдатна!"</string>
@@ -92,8 +90,8 @@
     <string name="allset_title" msgid="5021126669778966707">"Гатова!"</string>
     <string name="allset_hint" msgid="459504134589971527">"Каб перайсці на галоўны экран, правядзіце пальцам уверх"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Каб перайсці на галоўны экран, націсніце кнопку галоўнага экрана"</string>
-    <string name="allset_description_generic" msgid="5385500062202019855">"Вы можаце пачаць карыстанне прыладай \"<xliff:g id="DEVICE">%1$s</xliff:g>\""</string>
-    <string name="default_device_name" msgid="6660656727127422487">"прылада"</string>
+    <string name="allset_description_generic" msgid="5385500062202019855">"Вы можаце пачаць карыстацца <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
+    <string name="default_device_name" msgid="6660656727127422487">"прыладай"</string>
     <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>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Панэль задач паказана"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Панэль задач і ўсплывальныя чаты паказваюцца злева"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Панэль задач і ўсплывальныя чаты паказваюцца справа"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Панэль задач схавана"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Панэль задач і ўсплывальныя чаты схаваны"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Панэль навігацыі"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Заўсёды паказваць панэль задач"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Змяніць рэжым навігацыі"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Меню з пашырэннем панэлі задач"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Перамясціць уверх/улева"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Перамясціць уніз/управа"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{даступная праграма}one{даступная праграма}few{даступныя праграмы}many{даступных праграм}other{даступнай праграмы}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Працоўны стол"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> і <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>: разгарнуць"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>: згарнуць"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Абвесці для пошуку"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Значок праграмы"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Назва праграмы"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Кнопка \"Закрыць\""</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-bg/strings.xml b/quickstep/res/values-bg/strings.xml
index d624914..922a473 100644
--- a/quickstep/res/values-bg/strings.xml
+++ b/quickstep/res/values-bg/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Свободна форма"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"За компютър"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Преместване към външния екран"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Затваряне"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Настолен компютър"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Няма скорошни елементи"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Настройки за използването на приложенията"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Изчистване на всички"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Скорошни приложения"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Задачата е затворена"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 мин"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Оставащо време днес: <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Предложенията за приложения са активирани"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Функцията „Предложения за приложения“ е деактивирана"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Предвидено приложение: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Урок за навигирането с жестове"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Завъртете устройството си"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Моля, завъртете устройството си, за да завършите урока за навигиране с жестове"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Трябва да плъзнете пръст от най-дясната или най-лявата част на екрана"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Променете чувств. на жеста за връщане назад от настройките"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Жест за връщане назад"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"За да се върнете на предишния екран, плъзнете пръст от левия или десния край на екрана до средата."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"За да се върнете към последния екран, прекарайте два пръста от левия или десния край на екрана до средата."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Връщане назад"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Плъзнете пръст от левия или десния край до средата на екрана"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Трябва да плъзнете пръст нагоре от долния край на екрана"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Изпълнихте жеста за преминаване към началния екран"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Жест за преминаване към началния екран"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Прекарайте пръст нагоре от долната част на екрана. Този жест винаги ще ви отвежда до началния екран."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Прекарайте два пръста нагоре от долната част на екрана. Този жест винаги води до началния екран."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Отваряне на началния екран"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Плъзнете пръст нагоре от долната част на екрана"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Отлично!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Изпълнихте жеста за превключване между приложения"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Жест за превключване между приложенията"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"За превключване плъзнете пръст нагоре от долната част на екрана, задръжте и освободете."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"За превключване на прил. плъзнете 2 пръста нагоре от долната част на екрана, задръжте и освободете."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Превключване на приложенията"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Плъзнете пръст нагоре от долната част на екрана, задръжте и след това го вдигнете"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Браво!"</string>
@@ -90,7 +88,7 @@
     <string name="gesture_tutorial_nice" msgid="2936275692616928280">"Чудесно!"</string>
     <string name="gesture_tutorial_step" msgid="1279786122817620968">"Урок <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
     <string name="allset_title" msgid="5021126669778966707">"Готово!"</string>
-    <string name="allset_hint" msgid="459504134589971527">"Плъзнете пръст нагоре, за да отворите началния екран"</string>
+    <string name="allset_hint" msgid="459504134589971527">"Плъзнете с пръст нагоре, за да отворите началния екран"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Докоснете бутона „Начало“, за да преминете към началния екран"</string>
     <string name="allset_description_generic" msgid="5385500062202019855">"Можете да започнете да използвате <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="default_device_name" msgid="6660656727127422487">"устройството"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Лентата на задачите се показва"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Лентата и балончетата са вляво"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Лентата и балончетата са вдясно"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Лентата на задачите е скрита"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Лентата и балончетата са скрити"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Лента за навигация"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Лентата на задачите винаги да се показва"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Промяна на режима на навигация"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Меню при препълване на лентата на задачите"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Преместване горе/вляво"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Преместване долу/вдясно"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{допълнително приложение}other{допълнителни приложения}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Режим за настолни компютри"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> и <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"разгъване на <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"свиване на <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Търсене с ограждане"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Икона на приложението"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Име на приложението"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Бутон за затваряне"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-bn/strings.xml b/quickstep/res/values-bn/strings.xml
index c7bc2cf..974994a 100644
--- a/quickstep/res/values-bn/strings.xml
+++ b/quickstep/res/values-bn/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"ফ্রি-ফর্ম"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ডেস্কটপ"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"এক্সটার্নাল ডিসপ্লেতে সরিয়ে নিয়ে যান"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"বন্ধ করুন"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ডেস্কটপ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"কোনও সাম্প্রতিক আইটেম নেই"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"অ্যাপ ব্যবহারের সেটিংস"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"সবকিছু খালি করুন"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"সম্প্রতি ব্যবহৃত অ্যাপ"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"টাস্ক বন্ধ করা হয়েছে"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; ১ মি."</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"আজকে <xliff:g id="TIME">%1$s</xliff:g> বাকি আছে"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"অ্যাপ সাজেশন চালু করা আছে"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"অ্যাপ সাজেশন বন্ধ করা আছে"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"আপনার প্রয়োজন হতে পারে এমন অ্যাপ: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"জেসচার নেভিগেশন সম্পর্কিত টিউটোরিয়াল"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"আপনার ডিভাইস ঘোরান"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"জেসচার নেভিগেশন টিউটোরিয়াল সম্পূর্ণ করতে আপনার ডিভাইসটি ঘোরান"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"স্ক্রিনের একেবারে ডান বা বাঁদিকের প্রান্ত থেকে সোয়াইপ করেছেন কিনা তা দেখে নিন"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"ফিরে যাওয়ার জেসচারের সেন্সিটিভিটি পরিবর্তন করতে, সেটিংসে যান"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"ফিরে যেতে সোয়াইপ করুন"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"শেষের স্ক্রিনে ফিরে যেতে, ডান বা বাঁ প্রান্ত থেকে স্ক্রিনের মাঝখান পর্যন্ত সোয়াইপ করুন।"</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"শেষ স্ক্রিনে ফিরতে, বাম বা ডান প্রান্ত থেকে স্ক্রিনের মাঝামাঝি পর্যন্ত ২টি আঙুল দিয়ে সোয়াইপ করুন।"</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"ফিরে যান"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"স্ক্রিনের বাঁদিক বা ডানদিক থেকে মাঝখানে সোয়াইপ করুন"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"স্ক্রিনের নিচের প্রান্ত থেকে আপনি সোয়াইপ করেছেন কিনা ভাল করে দেখে নিন"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"আপনি জেনেছেন হাতের জেসচার ব্যবহার করে হোম স্ক্রিনে কীভাবে যাওয়া যায়"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"হোম স্ক্রিনে যেতে সোয়াইপ করুন"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"স্ক্রিনের নিচের প্রান্ত থেকে উপরের দিকে সোয়াইপ করুন। এটি করলে, আপনি সবসময় হোম স্ক্রিনে যেতে পারবেন।"</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"স্ক্রিনের নিচ থেকে ২টি আঙুল দিয়ে উপরে সোয়াইপ করুন। এই জেসচার সবসময় আপনাকে হোম স্ক্রিনে নিয়ে যায়।"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"হোম স্ক্রিনে যান"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"আপনার স্ক্রিনের একেবারে নিচ থেকে সোয়াইপ করুন"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"অসাধারণ!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"আপনি জেনেছেন যে হাতের জেসচার ব্যবহার করে একটি অ্যাপ থেকে অন্য অ্যাপে কীভাবে যাওয়া যায়"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"একটি অ্যাপ থেকে অন্য অ্যাপে যেতে সোয়াইপ করুন"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"একটি অ্যাপ থেকে অন্যটিতে পাল্টাতে, স্ক্রিনের নিচ থেকে উপরে সোয়াইপ করে ধরে রাখুন, তারপরে ছেড়ে দিন।"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"একটি থেকে অন্য অ্যাপে পাল্টাতে, ২টি আঙুল দিয়ে স্ক্রিনের নিচ থেকে উপরে সোয়াইপ করে ধরে রেখে ছেড়ে দিন।"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"অ্যাপ পরিবর্তন করুন"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"স্ক্রিনের নিচের থেকে উপরের দিকে সোয়াইপ করে ধরে থাকুন, তারপরে ছেড়ে দিন"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"শাবাশ!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"\'টাস্কবার\' দেখানো হয়েছে"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"টাস্কবার ও বাবল বাঁদিকে দেখানো হয়েছে"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"টাস্কবার ও বাবল ডানদিকে দেখানো হয়েছে"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"\'টাস্কবার\' লুকানো রয়েছে"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"টাস্কবার ও বাবল লুকানো হয়েছে"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"নেভিগেশন বার"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"সবসময় টাস্কবার দেখুন"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"\'নেভিগেশন\' মোড পরিবর্তন করুন"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"টাস্কবার ওভারফ্লো"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"উপরে/বাঁদিকে সরান"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"নিচে/ডানদিকে সরান"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{আরও অ্যাপ}one{আরও অ্যাপ}other{আরও অ্যাপ}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"ডেস্কটপ"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ও <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> বড় করুন"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> আড়াল করুন"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"খোঁজার জন্য সার্কেল বানান"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"অ্যাপ আইকন"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"অ্যাপের শিরোনাম"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"বন্ধ করার বোতাম"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-bs/strings.xml b/quickstep/res/values-bs/strings.xml
index cea1921..c89f84a 100644
--- a/quickstep/res/values-bs/strings.xml
+++ b/quickstep/res/values-bs/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Slobodan oblik"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Radna površina"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Premještanje na vanjski ekran"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Zatvaranje"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Radna površina"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Postavke korištenja aplikacije"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Obriši sve"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nedavne aplikacije"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Zadatak je zatvoren"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Preostalo vrijeme: <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Prijedlozi aplikacija su omogućeni"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Prijedlozi aplikacija su onemogućeni"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predviđena aplikacija: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Vodič za navigaciju pokretima"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotirajte uređaj"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rotirajte uređaj da završite vodič za navigaciju pokretima"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Prevucite s krajnjeg desnog ili lijevog ruba"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Promijenite osjetljivost pokreta za povratak u Postavkama"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Prevucite da se vratite"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Da se vratite na prethodni ekran, prevucite s lijevog ili desnog ruba prema sredini ekrana."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Da se vratite na posljednji ekran, prevucite s 2 prsta od lijevog ili desnog ruba do sredine ekrana."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Vratite se nazad"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Prevucite s lijevog ili desnog ruba prema sredini ekrana"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Prevucite prema gore s donjeg ruba ekrana"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Savladali ste pokret za otvaranje početnog ekrana"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Prevucite da odete na početni ekran"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Prevucite s dna ekrana prema gore. Tim pokretom uvijek idete na početni ekran."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Prevucite s 2 prsta od dna ekrana. Tim pokretom uvijek idete na početni ekran"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Idite na početni ekran"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Prevucite s dna ekrana prema gore"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Sjajno!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Savladali ste pokret za prebacivanje između aplikacija"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Prevucite da prebacujete između aplikacija"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Da se prebacujete između aplikacija, prevucite s dna ekrana nagore, zadržite, a zatim pustite."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Da se prebacujete između aplikacija, prevucite s 2 prsta od dna ekrana, zadržite, a zatim pustite."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Prebacujte između aplikacija"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Prevucite s dna ekrana prema gore, zadržite, a zatim pustite"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Odlično!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Traka zadataka je prikazana"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Traka zadataka/oblačići prik. lijevo"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Traka zadataka/oblačići prik. desno"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Traka zadataka je sakrivena"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Traka zadataka/oblačići sakriveni"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigaciona traka"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Uvijek prikaži traku zadataka"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Promijeni način navigacije"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Preklopni meni trake zadataka"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Premjesti gore lijevo"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premjesti dolje desno"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{dodatna aplikacija}one{dodatna aplikacija}few{dodatne aplikacije}other{dodatnih aplikacija}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"proširivanje oblačića <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"sužavanje oblačića <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Pretraživanje zaokruživanjem"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikona aplikacije"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Naslov aplikacije"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Dugme za zatvaranje"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ca/strings.xml b/quickstep/res/values-ca/strings.xml
index e2352d7..87591da 100644
--- a/quickstep/res/values-ca/strings.xml
+++ b/quickstep/res/values-ca/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Format lliure"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Escriptori"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Mou a la pantalla externa"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Tanca"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Escriptori"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"No hi ha cap element recent"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuració d\'ús d\'aplicacions"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Esborra-ho tot"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplicacions recents"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Tasca tancada"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>; <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minut"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"temps restant avui: <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Els suggeriments d\'aplicacions estan activats"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Els suggeriments d\'aplicacions estan desactivats"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicció d\'aplicació: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial de navegació amb gestos"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Gira el dispositiu"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Gira el dispositiu per completar el tutorial de navegació amb gestos"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Assegura\'t de lliscar des de l\'extrem dret o esquerre de la pantalla."</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Per canviar la sensibilitat del gest, ves a Configuració"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Llisca per anar enrere"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Per tornar a la darrera pantalla, llisca des de la vora esquerra o dreta cap al centre de la pantalla."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Per tornar a la pantalla anterior, llisca amb dos dits des de l\'extrem esquerre o dret cap al centre de la pantalla."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Torna enrere"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Llisca des de la vora esquerra o dreta cap al centre de la pantalla."</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Assegura\'t de lliscar cap amunt des de la part inferior de la pantalla."</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Has completat el gest per anar a la pantalla d\'inici"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Llisca per anar a la pantalla d\'inici"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Llisca cap amunt des de la part inferior de la pantalla. Aquest gest et porta a la pantalla d\'inici."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Llisca amb dos dits cap amunt des de la part inferior. Aquest gest sempre porta a la pantalla d\'inici."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Ves a la pàgina d\'inici"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Llisca cap amunt des de la part inferior de la pantalla."</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Ben fet!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Has completat el gest per canviar d\'aplicació"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Llisca per canviar d\'aplicació"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Per canviar entre aplicacions, llisca cap amunt des de la part inferior, mantén premut i deixa anar."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Per canviar entre apps, llisca amb dos dits cap amunt des de la part inferior, mantén i deixa anar."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Canvia d\'aplicació"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Llisca cap amunt des de la part inferior de la pantalla, mantén premut i, a continuació, deixa anar."</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Ben fet!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Es mostra la Barra de tasques"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Barra i bombolles a l\'esquerra"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Barra i bombolles a la dreta"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"S\'ha amagat la Barra de tasques"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Barra i bombolles amagades"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barra de navegació"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Barra de tasques sempre visible"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Canvia el mode de navegació"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Menú addicional de la barra de tasques"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mou a la part superior o a l\'esquerra"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mou a la part inferior o a la dreta"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{aplicació més}other{aplicacions més}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Escriptori"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"desplega <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"replega <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Encercla per cercar"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Icona de l\'aplicació"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Títol de l\'aplicació"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Botó Tanca"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-cs/strings.xml b/quickstep/res/values-cs/strings.xml
index af9cf38..6d53ca8 100644
--- a/quickstep/res/values-cs/strings.xml
+++ b/quickstep/res/values-cs/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Neomezený režim"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Počítač"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Přesunout na externí displej"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Zavřít"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Počítač"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Žádné položky z nedávné doby"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Nastavení využití aplikací"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Vymazat vše"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Poslední aplikace"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Úkol byl zavřen"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minuta"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"dnes zbývá: <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Návrhy aplikací jsou povoleny"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Návrhy aplikací jsou zakázány"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Předpokládaná aplikace: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Výukový program navigace gesty"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Otočte zařízení"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Pokud chcete dokončit výukový program navigace gesty, otočte zařízení"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Přejeďte prstem z úplného pravého nebo levého okraje obrazovky"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Citlivost gesta pro přechod zpět můžete změnit v Nastavení"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Přejetím prstem se vrátíte zpět"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Na předchozí obrazovku se vrátíte tak, že přejedete prstem z levého nebo pravého okraje obrazovky doprostřed."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Pokud se chcete vrátit na poslední obrazovku, přejeďte dvěma prsty z levého nebo pravého okraje doprostřed obrazovky."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Návrat zpět"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Přejeďte prstem z levého nebo pravého okraje do středu obrazovky"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Přejeďte prstem nahoru z dolního okraje obrazovky"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Provedli jste gesto pro přechod na plochu"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Přechod na plochu přejetím prstem"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Přejeďte prstem ze spodní části obrazovky nahoru. Tímto gestem se vždy dostanete na plochu."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Přejeďte dvěma prsty z dolního okraje obrazovky nahoru. Tímto gestem se vždy dostanete na plochu."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Přechod na plochu"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Přejeďte prstem ze spodní části obrazovky nahoru"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Výborně!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Provedli 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>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Přepínání mezi aplikacemi: Přejeďte dvěma prsty nahoru z dolního okraje obrazovky, podržte obrazovku a uvolněte."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Přepínání aplikací"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Přejeďte prstem nahoru z dolního okraje obrazovky, podržte obrazovku a potom prst uvolněte"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Výborně!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Panel aplikací je zobrazen"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Panel a bubliny vlevo zobr."</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Panel a bubliny vpravo zobr."</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Panel aplikací je skrytý"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Panel a bubliny jsou skryty"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigační panel"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Vždy zobrazovat panel aplikací"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Změnit navigační režim"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Přetečení panelu aplikací"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Přesunout doleva nahoru"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Přesunout doprava dolů"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{další aplikace}few{další aplikace}many{další aplikace}other{dalších aplikací}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"rozbalit <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"sbalit <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Zakroužkuj a hledej"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikona aplikace"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Název aplikace"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Tlačítko Zavřít"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-da/strings.xml b/quickstep/res/values-da/strings.xml
index ea9c317..debea45 100644
--- a/quickstep/res/values-da/strings.xml
+++ b/quickstep/res/values-da/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Frit format"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Computertilstand"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Flyt til ekstern skærm"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Luk"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Computertilstand"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Ingen nye elementer"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Indstillinger for appforbrug"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Ryd alt"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Seneste apps"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Opgaven er lukket"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> tilbage i dag"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Appforslag er aktiveret"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Appforslag er deaktiveret"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"App, du forventes at skulle bruge: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Vejledning i navigation med bevægelser"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotér din enhed"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rotér enheden for at gennemgå vejledningen i navigation med bevægelser"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Stryg fra kanten yderst til højre eller venstre"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Juster følsomheden for bevægelsen Gå tilbage i Indstillinger"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Stryg for at gå tilbage"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Stryg mod midten af skærmen fra venstre eller højre kant for at gå tilbage til den seneste skærm."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Du kan gå tilbage til forrige skærm ved at stryge fra venstre eller højre kant mod midten af skærmen med 2 fingre."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Gå tilbage"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Stryg fra venstre eller højre kant til midten af skærmen"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Stryg opad fra bunden af skærmen"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Du har udført bevægelsen for Gå til startskærmen"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Stryg for at gå til startskærmen"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Stryg opad fra bunden af skærmen. Denne bevægelse åbner altid startskærmen."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Stryg opad med 2 fingre fra bunden af skærmen. Denne bevægelse åbner altid startskærmen."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Gå til startskærmen"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Stryg opad fra bunden af skærmen"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Flot!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Du har udført bevægelsen for at skifte mellem apps"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Stryg for at skifte app"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Skift mellem apps ved at stryge opad fra bunden af skærmen, holde fingeren stille og løfte den."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Skift mellem apps ved at stryge opad fra bunden af skærmen med 2 fingre, holde dem nede og slippe."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Skift mellem apps"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Stryg opad fra bunden af skærmen, hold fingeren nede, og slip"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Flot!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Proceslinjen vises"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Proceslinje og bobler til venstre"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Proceslinje og bobler til højre"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Proceslinjen er skjult"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Proceslinje og bobler skjult"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigationslinje"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Vis altid proceslinjen"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Skift navigationstilstand"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Prikmenu på proceslinjen"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Flyt til toppen eller venstre side"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Flyt til bunden eller højre side"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{yderligere app}one{yderligere app}other{yderligere apps}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Computertilstand"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> og <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"udvid <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"skjul <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Appikon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Apptitel"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Knappen Luk"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-de/strings.xml b/quickstep/res/values-de/strings.xml
index f70e408..b4d5ac1 100644
--- a/quickstep/res/values-de/strings.xml
+++ b/quickstep/res/values-de/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform-Modus"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktopmodus"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Auf externes Display verschieben"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Schließen"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktopmodus"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Keine kürzlich verwendeten Elemente"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Einstellungen zur App-Nutzung"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Alle Apps schließen"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Kürzlich geöffnete Apps"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Aufgabe geschlossen"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 Min."</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Heute noch <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Funktion „App-Vorschläge“ aktiviert"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Funktion \"App-Vorschläge\" deaktiviert"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Vorgeschlagene App: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Anleitung zur Bedienung über Gesten"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Gerät drehen"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Bitte drehe dein Gerät, um die Anleitung für die Bedienung über Gesten abzuschließen"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Wische vom äußersten rechten oder linken Displayrand"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Du kannst die Empfindlichkeit von „Zurück“ in den Einstellungen ändern"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Zum Zurückgehen wischen"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Wenn du zum letzten Bildschirm zurückgehen möchtest, wische vom linken oder rechten Rand zur Mitte."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Wenn du zum letzten Bildschirm zurückgehen möchtest, wische mit zwei Fingern vom linken oder rechten Displayrand zur Mitte."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Zurück"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Wische vom linken oder rechten Displayrand bis zur Mitte des Displays"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Wische vom unteren Displayrand nach oben"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Du hast den Schritt für die „Startbildschirm“-Touch-Geste abgeschlossen"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Den Startbildschirm aufrufen"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Wenn du zum Startbildschirm gehen möchtest, wische einfach vom unteren Displayrand nach oben."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Wische mit zwei Fingern vom unteren Displayrand nach oben. So gelangst du immer zum Startbildschirm."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Zum Start­bildschirm"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Wische vom unteren Displayrand nach oben"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Gut gemacht!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Du hast den Schritt für die „Apps wechseln“-Touch-Geste abgeschlossen"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Zwischen Apps wechseln"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Wische auf dem Display von unten nach oben, halte den Finger gedrückt und lass dann los, um zwischen Apps zu wechseln."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Wische zum App-Wechseln mit zwei Fingern vom unteren Displayrand nach oben, halte und lass dann los."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Apps wechseln"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Wische auf dem Display von unten nach oben, halte es gedrückt und lass es dann los"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Perfekt!"</string>
@@ -94,7 +92,7 @@
     <string name="allset_button_hint" msgid="2395219947744706291">"Startbildschirmtaste drücken, um zum Startbildschirm zu gehen"</string>
     <string name="allset_description_generic" msgid="5385500062202019855">"Du kannst dein <xliff:g id="DEVICE">%1$s</xliff:g> jetzt verwenden"</string>
     <string name="default_device_name" msgid="6660656727127422487">"Gerät"</string>
-    <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Einstellungen der Systemsteuerung"</annotation></string>
+    <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Einstellungen für die Systemnavigation"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"Teilen"</string>
     <string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
     <string name="action_split" msgid="2098009717623550676">"Splitscreen"</string>
@@ -109,7 +107,7 @@
     <string name="skip_tutorial_dialog_title" msgid="2725643161260038458">"Tutorial zur Bedienung überspringen?"</string>
     <string name="skip_tutorial_dialog_subtitle" msgid="544063326241955662">"Du findest es später auch in der <xliff:g id="NAME">%1$s</xliff:g> App"</string>
     <string name="gesture_tutorial_action_button_label_cancel" msgid="3809842569351264108">"Abbrechen"</string>
-    <string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Überspringen"</string>
+    <string name="gesture_tutorial_action_button_label_skip" msgid="394452764989751960">"Über­springen"</string>
     <string name="accessibility_rotate_button" msgid="4771825231336502943">"Bildschirm drehen"</string>
     <string name="taskbar_edu_a11y_title" msgid="5417986057866415355">"Informationen zur Taskleiste"</string>
     <string name="taskbar_edu_splitscreen" msgid="5605512479258053350">"App zur Seite ziehen, um zwei Apps gleichzeitig zu nutzen"</string>
@@ -120,7 +118,7 @@
     <string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Taskleiste immer anzeigen"</string>
     <string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Damit die Taskleiste immer unten angezeigt wird, halte den Teiler gedrückt"</string>
     <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Aktionstaste gedrückt halten, um auf dem Bildschirm zu suchen"</string>
-    <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Dieses Produkt verwendet den ausgewählten Teil deines Bildschirms für die Suche. Es gelten die <xliff:g id="BEGIN_PRIVACY_LINK">&lt;a href="%1$s"&gt;</xliff:g>Datenschutzerklärung<xliff:g id="END_PRIVACY_LINK">&lt;/a&gt;</xliff:g> und die <xliff:g id="BEGIN_TOS_LINK">&lt;a href="%2$s"&gt;</xliff:g>Nutzungsbedingungen<xliff:g id="END_TOS_LINK">&lt;/a&gt;</xliff:g> von Google."</string>
+    <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Dieses Produkt sucht dann anhand des ausgewählten Teils deines Displays nach weiteren Informationen. Es gelten die <xliff:g id="BEGIN_PRIVACY_LINK">&lt;a href="%1$s"&gt;</xliff:g>Datenschutzerklärung<xliff:g id="END_PRIVACY_LINK">&lt;/a&gt;</xliff:g> und die <xliff:g id="BEGIN_TOS_LINK">&lt;a href="%2$s"&gt;</xliff:g>Nutzungsbedingungen<xliff:g id="END_TOS_LINK">&lt;/a&gt;</xliff:g> von Google."</string>
     <string name="taskbar_edu_close" msgid="887022990168191073">"Schließen"</string>
     <string name="taskbar_edu_done" msgid="6880178093977704569">"Fertig"</string>
     <string name="taskbar_button_home" msgid="2151398979630664652">"Startbildschirm"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taskleiste eingeblendet"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taskleiste &amp; Bubbles links eingeblendet"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taskleiste &amp; Bubbles rechts eingeblendet"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taskleiste ausgeblendet"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taskleiste &amp; Bubbles ausgeblendet"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigationsleiste"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Taskleiste immer anzeigen"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Navigationsmodus ändern"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Dreipunkt-Menü der Taskleiste"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Nach oben / Nach links verschieben"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Nach unten / Nach rechts verschieben"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{weitere App}other{weitere Apps}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Desktopmodus"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"„<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>“ maximieren"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"„<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>“ minimieren"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"App-Symbol"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Titel der App"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Schaltfläche „Schließen“"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-el/strings.xml b/quickstep/res/values-el/strings.xml
index d7ff2ad..5dde857 100644
--- a/quickstep/res/values-el/strings.xml
+++ b/quickstep/res/values-el/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Ελεύθερη μορφή"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Υπολογιστής"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Μετακίνηση σε εξωτερική οθόνη"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Κλείσιμο"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Υπολογιστής"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Δεν υπάρχουν πρόσφατα στοιχεία"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ρυθμίσεις χρήσης εφαρμογής"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Διαγραφή όλων"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Πρόσφατες εφαρμογές"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Η εργασία έκλεισε"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 λ."</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Απομένουν <xliff:g id="TIME">%1$s</xliff:g> σήμερα"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Οι προτεινόμενες εφαρμογές ενεργοποιήθηκαν"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Οι προτεινόμενες εφαρμογές είναι απενεργοποιημένες"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Εφαρμογή από πρόβλεψη: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Οδηγός για την πλοήγηση με κινήσεις"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Περιστρέψτε τη συσκευή σας"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Περιστρέψτε τη συσκευή σας για να ολοκληρώσετε τον οδηγό πλοήγησης με κινήσεις"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Φροντίστε να σύρετε από το άκρο της δεξιάς ή της αριστερής πλευράς."</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Μεταβείτε στις Ρυθμίσεις για αλλαγή ευαισθ. κίνησης επιστρ."</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Σύρετε για επιστροφή"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Για να επιστρέψετε στην τελευταία οθόνη, σύρετε από το αριστερό ή το δεξί άκρο προς το μέσο της οθόνης."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Για να επιστρέψετε στην προηγούμενη οθόνη, σύρετε με 2 δάχτυλα από την αριστερή ή δεξιά άκρη προς τη μέση της οθόνης."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Επιστροφή"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Σύρετε από το αριστερό ή το δεξί άκρο προς το κέντρο της οθόνης"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Φροντίστε να σύρετε προς τα επάνω από το κάτω άκρο της οθόνης"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Ολοκληρώσατε την κίνηση μετάβασης στην αρχική οθόνη"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Σύρετε για μετάβαση στην αρχική οθόνη"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Σύρετε προς τα πάνω από το κάτω μέρος της οθόνης. Αυτή η κίνηση σάς μεταφέρει πάντα στην αρχ. οθόνη."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Σύρετε από το κάτω άκρο προς τα πάνω με 2 δάχτ. Αυτή η κίνηση σάς μεταφέρει πάντα στην αρχική οθόνη."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Μετάβαση στην αρχική οθόνη"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Σύρετε προς τα επάνω από το κάτω μέρος της οθόνης"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Μπράβο!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Ολοκληρώσατε την κίνηση εναλλαγής εφαρμογών"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Σύρετε για εναλλαγή εφαρμογών"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Για εναλλαγή εφαρμογών, σύρετε προς τα πάνω από το κάτω μέρος της οθόνης, πατήστε παρατεταμένα και μετά αφήστε."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Για εναλλαγή μεταξύ εφαρμογών, σύρετε από κάτω προς τα πάνω με 2 δάχτ., κρατήστε και έπειτα αφήστε."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Εναλλαγή μεταξύ εφαρμογών"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Σύρετε προς τα πάνω από το κάτω μέρος της οθόνης, κρατήστε τα δάχτυλα επάνω στην οθόνη και μετά απομακρύνετέ τα"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Μπράβο!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Η γραμμή εργαλείων εμφανίζεται"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Εμφάν. αριστ. γρ. εργ. και συν."</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Εμφάν. δεξιάς γρ. εργ. και συν."</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Η γραμμή εργαλείων είναι κρυφή"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Απόκρυψη εργαλείων και συννεφ."</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Γραμμή πλοήγησης"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Εμφάνιση Γραμμής εργαλείων"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Αλλαγή τρόπου πλοήγησης"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Υπερχείλιση γραμμής εργαλείων"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Μετακίνηση επάνω/αριστερά"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Μετακίνηση κάτω/δεξιά"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ακόμη εφαρμογή}other{ακόμη εφαρμογές}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Υπολογιστής"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> και <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"ανάπτυξη <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"σύμπτυξη <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Κυκλώστε για αναζήτηση"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Εικονίδιο εφαρμογής"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Τίτλος εφαρμογής"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Κουμπί κλεισίματος"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-en-rAU/strings.xml b/quickstep/res/values-en-rAU/strings.xml
index 6b81b05..eef812c 100644
--- a/quickstep/res/values-en-rAU/strings.xml
+++ b/quickstep/res/values-en-rAU/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Move to external display"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Close"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recent apps"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Task closed"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minute"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> left today"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"App suggestions enabled"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"App suggestions are disabled"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicted app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Gesture navigation tutorial"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotate your device"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Please rotate your device to complete the gesture navigation tutorial"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Make sure you swipe from the far-right or far-left edge"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"To change sensitivity of the back gesture, go to Settings"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Swipe to go back"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"To go back to the last screen, swipe from the left or right edge to the middle of the screen."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"To go back to the last screen, swipe with two fingers from the left or right edge to the middle of the screen."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Go back"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Swipe from the left or right edge to the middle of the screen"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Make sure you swipe up from the bottom edge of the screen"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"You completed the go home gesture"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Swipe to go home"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Swipe up from the bottom of your screen. This gesture always takes you to the home screen."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Swipe up with two fingers from the bottom of the screen. This gesture always takes you to the home screen."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Go home"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Swipe up from the bottom of your screen"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Great work!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"You completed the switch apps gesture"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Swipe to switch apps"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"To switch between apps, swipe up from the bottom of your screen, hold, then release."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"To switch between apps, swipe up with two fingers from the bottom of your screen, hold, then release."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Switch apps"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Swipe up from the bottom of your screen, hold, then release"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Well done!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taskbar shown"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taskbar and bubbles left shown"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taskbar and bubbles right shown"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taskbar hidden"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taskbar and bubbles hidden"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigation bar"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Always show Taskbar"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Change navigation mode"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taskbar overflow"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Move to top/left"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{more app}other{more apps}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Desktop"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expand <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"collapse <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"App icon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"App title"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Close button"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-en-rCA/strings.xml b/quickstep/res/values-en-rCA/strings.xml
index da4effb..d1319ce 100644
--- a/quickstep/res/values-en-rCA/strings.xml
+++ b/quickstep/res/values-en-rCA/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Move to external display"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Close"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recent apps"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Task Closed"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minute"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> left today"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"App suggestions enabled"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"App suggestions are disabled"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicted app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Gesture Navigation Tutorial"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotate your device"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Please rotate your device to complete the gesture navigation tutorial"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Make sure you swipe from the far-right or far-left edge"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"To change the sensitivity of the back gesture, go to Settings"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Swipe to go back"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"To go back to the last screen, swipe from the left or right edge to the middle of the screen."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"To go back to the last screen, swipe with 2 fingers from the left or right edge to the middle of the screen."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Go back"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Swipe from the left or right edge to the middle of the screen"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Make sure you swipe up from the bottom edge of the screen"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"You completed the go home gesture"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Swipe to go home"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Swipe up from the bottom of your screen. This gesture always takes you to the Home screen."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Swipe up with 2 fingers from the bottom of the screen. This gesture always takes you to the Home screen."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Go home"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Swipe up from the bottom of your screen"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Great job!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"You completed the switch apps gesture"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Swipe to switch apps"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"To switch between apps, swipe up from the bottom of your screen, hold, then release."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"To switch between apps, swipe up with 2 fingers from the bottom of your screen, hold, then release."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Switch apps"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Swipe up from the bottom of your screen, hold, then release"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Well done!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taskbar shown"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taskbar &amp; bubbles left shown"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taskbar &amp; bubbles right shown"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taskbar hidden"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taskbar &amp; bubbles hidden"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigation bar"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Always show Taskbar"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Change navigation mode"</string>
@@ -143,6 +139,7 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taskbar Overflow"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Move to top/left"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
+    <string name="open_app_as_a_bubble" msgid="6642626287247807473">"Open app as a bubble"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{more app}other{more apps}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Desktop"</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>
@@ -156,4 +153,9 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expand <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"collapse <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"App icon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"App title"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Close button"</string>
+    <string name="pin_to_taskbar" msgid="6607778046321626950">"Pin to taskbar"</string>
+    <string name="unpin_from_taskbar" msgid="2178811773165572676">"Unpin from taskbar"</string>
 </resources>
diff --git a/quickstep/res/values-en-rGB/strings.xml b/quickstep/res/values-en-rGB/strings.xml
index 6b81b05..eef812c 100644
--- a/quickstep/res/values-en-rGB/strings.xml
+++ b/quickstep/res/values-en-rGB/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Move to external display"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Close"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recent apps"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Task closed"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minute"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> left today"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"App suggestions enabled"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"App suggestions are disabled"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicted app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Gesture navigation tutorial"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotate your device"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Please rotate your device to complete the gesture navigation tutorial"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Make sure you swipe from the far-right or far-left edge"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"To change sensitivity of the back gesture, go to Settings"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Swipe to go back"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"To go back to the last screen, swipe from the left or right edge to the middle of the screen."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"To go back to the last screen, swipe with two fingers from the left or right edge to the middle of the screen."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Go back"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Swipe from the left or right edge to the middle of the screen"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Make sure you swipe up from the bottom edge of the screen"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"You completed the go home gesture"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Swipe to go home"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Swipe up from the bottom of your screen. This gesture always takes you to the home screen."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Swipe up with two fingers from the bottom of the screen. This gesture always takes you to the home screen."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Go home"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Swipe up from the bottom of your screen"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Great work!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"You completed the switch apps gesture"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Swipe to switch apps"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"To switch between apps, swipe up from the bottom of your screen, hold, then release."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"To switch between apps, swipe up with two fingers from the bottom of your screen, hold, then release."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Switch apps"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Swipe up from the bottom of your screen, hold, then release"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Well done!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taskbar shown"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taskbar and bubbles left shown"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taskbar and bubbles right shown"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taskbar hidden"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taskbar and bubbles hidden"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigation bar"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Always show Taskbar"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Change navigation mode"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taskbar overflow"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Move to top/left"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{more app}other{more apps}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Desktop"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expand <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"collapse <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"App icon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"App title"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Close button"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-en-rIN/strings.xml b/quickstep/res/values-en-rIN/strings.xml
index 6b81b05..eef812c 100644
--- a/quickstep/res/values-en-rIN/strings.xml
+++ b/quickstep/res/values-en-rIN/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Move to external display"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Close"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"No recent items"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"App usage settings"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Clear all"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recent apps"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Task closed"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minute"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> left today"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"App suggestions enabled"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"App suggestions are disabled"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicted app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Gesture navigation tutorial"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotate your device"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Please rotate your device to complete the gesture navigation tutorial"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Make sure you swipe from the far-right or far-left edge"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"To change sensitivity of the back gesture, go to Settings"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Swipe to go back"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"To go back to the last screen, swipe from the left or right edge to the middle of the screen."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"To go back to the last screen, swipe with two fingers from the left or right edge to the middle of the screen."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Go back"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Swipe from the left or right edge to the middle of the screen"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Make sure you swipe up from the bottom edge of the screen"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"You completed the go home gesture"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Swipe to go home"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Swipe up from the bottom of your screen. This gesture always takes you to the home screen."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Swipe up with two fingers from the bottom of the screen. This gesture always takes you to the home screen."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Go home"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Swipe up from the bottom of your screen"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Great work!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"You completed the switch apps gesture"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Swipe to switch apps"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"To switch between apps, swipe up from the bottom of your screen, hold, then release."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"To switch between apps, swipe up with two fingers from the bottom of your screen, hold, then release."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Switch apps"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Swipe up from the bottom of your screen, hold, then release"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Well done!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taskbar shown"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taskbar and bubbles left shown"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taskbar and bubbles right shown"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taskbar hidden"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taskbar and bubbles hidden"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigation bar"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Always show Taskbar"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Change navigation mode"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taskbar overflow"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Move to top/left"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Move to bottom/right"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{more app}other{more apps}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Desktop"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expand <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"collapse <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"App icon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"App title"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Close button"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-es-rUS/strings.xml b/quickstep/res/values-es-rUS/strings.xml
index 57333f4..17e3a7d 100644
--- a/quickstep/res/values-es-rUS/strings.xml
+++ b/quickstep/res/values-es-rUS/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Formato libre"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover a pantalla externa"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Cerrar"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Computadoras"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"No hay elementos recientes"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuración de uso de la app"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Cerrar todo"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Apps recientes"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Se cerró la tarea"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minuto"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Tiempo restante: <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Sugerencias de apps habilitadas"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Las sugerencias de aplicaciones están inhabilitadas"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predicción de app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Instructivo de navegación por gestos"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rota el dispositivo"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rota el dispositivo para completar el instructivo de la navegación por gestos"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Asegúrate de deslizar desde el extremo derecho o izquierdo"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Cambia sensibilidad de gesto \"Atrás\" en Configuración"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Desliza para ir atrás"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Desliza el dedo desde el borde derecho o izquierdo hasta el centro para volver a la última pantalla."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Para volver la pantalla anterior, desliza 2 dedos desde el borde izquierdo o derecho hacia el centro de la pantalla."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Volver atrás"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Desliza desde el borde izquierdo o derecho hacia el centro de la pantalla"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Asegúrate de deslizar hacia arriba desde el borde inferior de la pantalla"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Completaste el gesto para ir a la página principal"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Desliza para ir a la página principal"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Desliza hacia arriba desde la parte inferior de la pantalla. Este gesto te llevará a la pantalla principal."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Desliza hacia arriba desde la parte inferior. Este gesto te llevará siempre a la pantalla principal."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Ve a la pantalla principal"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Desliza hacia arriba desde la parte inferior de la pantalla"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"¡Bien hecho!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Completaste el gesto para cambiar de app"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Desliza para cambiar de app"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Desliza el dedo hacia arriba desde la parte inferior, mantenlo presionado y suéltalo."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Para alternar entre apps, desliza el dedo hacia arriba, mantén presionado y, luego, suelta."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Cambia de app"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Desliza hacia arriba desde la parte inferior de la pantalla, mantenla presionada y, luego, suelta"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"¡Bien hecho!"</string>
@@ -92,7 +90,7 @@
     <string name="allset_title" msgid="5021126669778966707">"Todo listo"</string>
     <string name="allset_hint" msgid="459504134589971527">"Desliza el dedo hacia arriba para ir a la página principal"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Presiona el botón de inicio para ir a la pantalla principal"</string>
-    <string name="allset_description_generic" msgid="5385500062202019855">"Ya puedes comenzar a usar tu <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
+    <string name="allset_description_generic" msgid="5385500062202019855">"Ya puedes comenzar a usar tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="default_device_name" msgid="6660656727127422487">"dispositivo"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Configuración de navegación del sistema"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"Compartir"</string>
@@ -120,7 +118,7 @@
     <string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Mostrar siempre la Barra de tareas"</string>
     <string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Mantén presionado el divisor para mostrar siempre la Barra de tareas en la parte inferior de la pantalla"</string>
     <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Mantén presionada la tecla de acción para buscar qué hay en la pantalla"</string>
-    <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Este producto usa la parte seleccionada de la pantalla para buscar. Se aplican la <xliff:g id="BEGIN_PRIVACY_LINK">&lt;a href="%1$s"&gt;</xliff:g>Política de Privacidad<xliff:g id="END_PRIVACY_LINK">&lt;/a&gt;</xliff:g> y las <xliff:g id="BEGIN_TOS_LINK">&lt;a href="%2$s"&gt;</xliff:g>Condiciones del Servicio<xliff:g id="END_TOS_LINK">&lt;/a&gt;</xliff:g> de Google."</string>
+    <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Este producto usa la parte seleccionada de la pantalla para hacer búsquedas. Se aplican la <xliff:g id="BEGIN_PRIVACY_LINK">&lt;a href="%1$s"&gt;</xliff:g>Política de Privacidad<xliff:g id="END_PRIVACY_LINK">&lt;/a&gt;</xliff:g> y las <xliff:g id="BEGIN_TOS_LINK">&lt;a href="%2$s"&gt;</xliff:g>Condiciones del Servicio<xliff:g id="END_TOS_LINK">&lt;/a&gt;</xliff:g> de Google."</string>
     <string name="taskbar_edu_close" msgid="887022990168191073">"Cerrar"</string>
     <string name="taskbar_edu_done" msgid="6880178093977704569">"Listo"</string>
     <string name="taskbar_button_home" msgid="2151398979630664652">"Botón de inicio"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Barra de tareas visible"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Barra tareas y burb. a la izq."</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Barra tareas y burb. a la der."</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Barra de tareas oculta"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Barra tareas y burb. ocultas"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barra de navegación"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Barra de tareas visible"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Cambiar el modo de navegación"</string>
@@ -143,6 +139,7 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Barra de tareas ampliada"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover a la parte superior o izquierda"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover a la parte inferior o derecha"</string>
+    <string name="open_app_as_a_bubble" msgid="6642626287247807473">"Abrir app como burbuja"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{app más}other{apps más}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Escritorio"</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>
@@ -156,4 +153,9 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expandir <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"contraer <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Busca con un círculo"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ícono de la app"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Título de la app"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Botón de cerrar"</string>
+    <string name="pin_to_taskbar" msgid="6607778046321626950">"Fijar a la barra"</string>
+    <string name="unpin_from_taskbar" msgid="2178811773165572676">"No fijar a la barra"</string>
 </resources>
diff --git a/quickstep/res/values-es/strings.xml b/quickstep/res/values-es/strings.xml
index bde0c19..69628cf 100644
--- a/quickstep/res/values-es/strings.xml
+++ b/quickstep/res/values-es/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Formato libre"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover a pantalla externa"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Cerrar"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Ordenador"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"No hay nada reciente"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ajustes de uso de la aplicación"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Borrar todo"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplicaciones recientes"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Tarea cerrada"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt;1 minuto"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"tiempo restante: <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Sugerencias de aplicaciones habilitadas"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Las sugerencias de aplicaciones están inhabilitadas"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Aplicación sugerida: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial de navegación por gestos"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Gira el dispositivo"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Gira el dispositivo para completar el tutorial de navegación por gestos"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Asegúrate de deslizar desde el borde derecho o izquierdo de la pantalla"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Para cambiar la sensibilidad del gesto, ve a Ajustes"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Desliza para ir atrás"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Para volver a la última pantalla, desliza el dedo desde un lateral de la pantalla hasta el centro."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Para volver a la pantalla anterior, desliza dos dedos desde el borde izquierdo o derecho hacia el centro de la pantalla."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Volver"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Desliza desde el borde izquierdo o derecho de la pantalla hasta el centro"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Asegúrate de deslizar hacia arriba desde el borde inferior de la pantalla"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Has completado el gesto para ir a la pantalla de inicio"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Desliza para ir a la pantalla de inicio"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Desliza hacia arriba desde la parte inferior de la pantalla. Este gesto siempre te lleva a la pantalla de inicio."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Desliza dos dedos hacia arriba desde la parte inferior de la pantalla. Si haces este gesto, siempre irás a la pantalla de inicio."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Ir a inicio"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Desliza hacia arriba desde la parte inferior de la pantalla"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"¡Bien hecho!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Has completado el gesto para cambiar de aplicación"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Desliza el dedo para cambiar de aplicación"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Para cambiar de aplicación, desliza el dedo hacia arriba desde el borde inferior, mantenlo pulsado y suéltalo."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Para cambiar de app, desliza dos dedos hacia arriba desde el borde inferior, mantén pulsada la pantalla y, luego, suelta."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Cambiar de aplicación"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Desliza hacia arriba desde la parte inferior de la pantalla, mantenla pulsada y suelta el dedo."</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"¡Muy bien!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Barra de tareas visible"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Tareas y burbujas a izquierda"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Tareas y burbujas a derecha"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Barra de tareas oculta"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Tareas y burbujas ocultas"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barra de navegación"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Barra de tareas visible"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Cambiar el modo de navegación"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Barra de tareas ampliada"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover arriba/a la izquierda"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover abajo/a la derecha"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{app más}other{apps más}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Ordenador"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> y <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"desplegar <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"contraer <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Rodea para buscar"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Icono de la aplicación"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Título de la aplicación"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Botón de cerrar"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-et/strings.xml b/quickstep/res/values-et/strings.xml
index 6192e81..41cb357 100644
--- a/quickstep/res/values-et/strings.xml
+++ b/quickstep/res/values-et/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Vabavorm"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Lauaarvuti režiim"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Liikuge välisele ekraanile"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Sule"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Töölaud"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Hiljutisi üksusi pole"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Rakenduse kasutuse seaded"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Sule kõik"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Hiljutised rakendused"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Ülesanne suleti"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minut"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Tääna jäänud <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Rakenduste soovitused on lubatud"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Rakenduste soovitused on keelatud"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Ennustatud rakendus: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Liigutustega navigeerimise juhend"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Pöörake seadet"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Pöörake seadet, et liigutustega navigeerimise õpetused lõpetada"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pühkige kindlasti parem- või vasakpoolsest servast"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Tagasiliigutuse tundlikkuse muutmiseks avage menüü Seaded"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Tagasiliikumiseks pühkige"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Eelmisele ekraanikuvale naasmiseks pühkige vasakust või paremast servast ekraanikuva keskele."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Eelmisele ekraanikuvale naasmiseks pühkige vasakust või paremast servast kahe sõrmega ekraanikuva keskele."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Tagasiliikumine"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Pühkige ekraani paremast või vasakust servast keskele."</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Pühkige kindlasti ekraani alumisest servast üles."</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Tegite avakuvale minemise liigutuse"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Pühkige avakuvale minemiseks"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Pühkige ekraani alaosast üles. See liigutus viib teid alati tagasi avakuvale."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Pühkige ekraanikuva alumisest servast 2 sõrmega üles. See liigutus viib teid alati tagasi avakuvale."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Avakuvale"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Pühkige ekraani allosast üles."</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Väga hea!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Tegite rakenduste vahel vahetamise liigutuse"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Pühkige rakenduste vahetamiseks"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Rakenduste vahel vahetamiseks pühkige ekraanikuva alaosast üles, hoidke ja seejärel vabastage."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Rakenduste vahel vahetamiseks pühkige kuva alaosast kahe sõrmega üles, hoidke ja seejärel vabastage."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Rakenduste vahetamine"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Pühkige ekraani allosast üles, hoidke ja seejärel vabastage."</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Hästi tehtud!"</string>
@@ -92,7 +90,7 @@
     <string name="allset_title" msgid="5021126669778966707">"Valmis!"</string>
     <string name="allset_hint" msgid="459504134589971527">"Avalehele liikumiseks pühkige üles"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Avakuvale liikumiseks puudutage avakuva nuppu"</string>
-    <string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> on nüüd kasutamiseks valmis"</string>
+    <string name="allset_description_generic" msgid="5385500062202019855">"Teie <xliff:g id="DEVICE">%1$s</xliff:g> on nüüd kasutamiseks valmis."</string>
     <string name="default_device_name" msgid="6660656727127422487">"seade"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Süsteemi navigeerimisseaded"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"Jaga"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Tegumiriba on kuvatud"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Tegumiriba ja mullid kuvatakse"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Tegumiriba ja mullid kuvatakse paremal"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Tegumiriba on peidetud"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Tegumiriba ja mullid on peidetud"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigeerimisriba"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Kuva tegumiriba alati"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Navigeerimisrežiimi muutmine"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Tegumiriba ületäide"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Teisalda üles/vasakule"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Teisalda alla/paremale"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{rakendus veel}other{rakendust veel}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Lauaarvuti"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"Toiminguriba <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> laiendamine"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"Toiminguriba <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ahendamine"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Ring otsimiseks"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Rakenduse ikoon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Rakenduse pealkiri"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Sulgemisnupp"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-eu/strings.xml b/quickstep/res/values-eu/strings.xml
index ac28a56..15c4742 100644
--- a/quickstep/res/values-eu/strings.xml
+++ b/quickstep/res/values-eu/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Modu librea"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Ordenagailua"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Eraman kanpoko pantailara"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Itxi"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Mahaigaina"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Ez dago azkenaldi honetako ezer"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Aplikazioen erabileraren ezarpenak"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Garbitu guztiak"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Azkenaldiko aplikazioak"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Itxi da zeregina"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> gelditzen dira gaur"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Gaituta daude aplikazioen iradokizunak"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Desgaituta daude aplikazioen iradokizunak"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Iragarritako aplikazioa: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Keinu bidezko nabigazioaren tutoriala"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Biratu gailua"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Keinu bidezko nabigazioaren tutoriala osatzeko, biratu gailua"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Ziurtatu hatza pantailaren eskuineko edo ezkerreko ertzetik hasten zarela pasatzen"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Keinuaren sentikortasuna aldatzeko, joan Ezarpenak atalera"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Pasatu hatza atzera egiteko"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Aurreko pantailara itzultzeko, pasatu hatza pantailaren ezkerreko edo eskuineko ertzetik erdialdera."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Aurreko pantailara itzultzeko, pasatu bi hatz pantailaren ezkerreko edo eskuineko ertzetik erdialdera."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Egin atzera"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Pasatu hatza pantailaren eskuineko edo ezkerreko ertzetik erdialdera"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Ziurtatu hatza pantailaren beheko ertzetik gora pasatzen duzula"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Ikasi duzu orri nagusira joateko keinua"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Pasatu hatza orri nagusira joateko"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Pasatu hatza pantailaren behealdetik gora. Keinu horrek orri nagusira eramango zaitu beti."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Pasatu bi hatz pantailaren behealdetik gora. Orri nagusira eramango zaitu beti keinu horrek."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Joan orri nagusira"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Pasatu hatza pantailaren behealdetik gora"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Bikain!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Ikasi duzu aplikazioz aldatzeko keinua"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Pasatu hatza aplikazioa aldatzeko"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Aplikazio batetik bestera joateko, pasatu hatza pantailaren behealdetik gora, eduki pantaila sakatuta eta altxatu hatza."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Aplikazio batetik bestera joateko, pasatu bi hatz pantailaren behealdetik gora, eduki pantaila sakatuta eta altxatu hatza."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Aldatu aplikazioa"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Pasatu hatza pantailaren behealdetik gora, eduki sakatuta une batez, eta jaso hatza"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Oso ongi!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Zereginen barra ikusgai dago"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Zereginen barra eta burbuilak ezkerrean ikusgai"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Zereginen barra eta burbuilak eskuinean ikusgai"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Zereginen barra itxita dago"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Zereginen barra eta burbuilak ezkutatuta"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Nabigazio-barra"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Erakutsi beti zereginen barra"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Aldatu nabigazio modua"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Zereginen barraren luzapena"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Eraman gora, ezkerretara"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Eraman behera, eskuinetara"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{aplikazio gehiago}other{aplikazio gehiago}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Ordenagailua"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"zabaldu <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"tolestu <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Inguratu bilatzeko"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Aplikazioaren ikonoa"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Aplikazioaren izena"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Ixteko botoia"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-fa/strings.xml b/quickstep/res/values-fa/strings.xml
index bc14f0b..810632b 100644
--- a/quickstep/res/values-fa/strings.xml
+++ b/quickstep/res/values-fa/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"حالت رایانه"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"انتقال به نمایشگر خارجی"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"بستن"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"رایانه"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"چیز جدیدی اینجا نیست"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"تنظیمات استفاده از برنامه"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"پاک کردن همه"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"برنامه‌های اخیر"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"تکلیف بسته شد"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>، <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; ۱ دقیقه"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> باقی‌مانده برای امروز"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"«پیشنهاد برنامه» فعال است"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"«پیشنهاد برنامه» غیرفعال است"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"برنامه پیش‌بینی‌شده: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"آموزش گام‌به‌گام پیمایش اشاره‌ای"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"دستگاهتان را بچرخانید"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"لطفاً برای تکمیل آموزش گام‌به‌گام پیمایش اشاره‌ای، دستگاهتان را بچرخانید"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"دقت کنید که از انتهای لبه سمت راست یا سمت چپ تند بکشید"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"برای تغییر حساسیت اشاره برگشت، به «تنظیمات» بروید"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"تند بکشید تا به‌عقب برگردید"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"برای برگشتن به صفحه آخر، از لبه سمت چپ یا راست تند به وسط صفحه بکشید."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"برای برگشتن به صفحه قبلی، با ۲ انگشت از لبه سمت چپ یا راست تند به‌وسط صفحه بکشید."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"برگشتن"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"از لبه سمت راست یا سمت چپ تند به وسط صفحه بکشید"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"دقت کنید که از لبه پایینی صفحه تند به بالا بکشید"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"اشاره رفتن به صفحه اصلی را تکمیل کردید"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"تند کشیدن برای رفتن به صفحه اصلی"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"از پایین صفحه، تند به‌سمت بالا بکشید. این اشاره همیشه شما را به صفحه اصلی می‌برد."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"با ۲ انگشت از پایین صفحه تند به‌بالا بکشید. این اشاره همیشه شما را به صفحه اصلی می‌برد."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"رفتن به صفحه اصلی"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"از پایین صفحه تند به بالا بکشید"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"عالی است!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"اشاره جابه‌جایی بین برنامه‌ها را تکمیل کردید"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"برای جابه‌جا شدن بین برنامه‌ها، تند به‌بالا بکشید"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"برای جابه‌جا شدن بین برنامه‌ها، از پایین صفحه تند به‌بالا بکشید، نگه دارید، و سپس رها کنید."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"برای جابه‌جایی بین برنامه‌ها، با ۲ انگشت از پایین صفحه تند به‌بالا بکشید، نگه دارید، و سپس رها کنید."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"جابه‌جایی بین برنامه‌ها"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"از پایین صفحه تند به‌بالا بکشید، نگه دارید، سپس رها کنید"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"آفرین!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"نوار وظیفه نمایان است"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"نمایش نوار وظیفه و حبابک‌ها در چپ"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"نمایش نوار وظیفه و حبابک‌ها در راست"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"نوار وظیفه پنهان است"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"نوار وظیفه و حبابک‌ها پنهان هستند"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"نوار پیمایش"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"نوار وظیفه همیشه نشان داده شود"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"تغییر حالت پیمایش"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"سرریز نوار وظیفه"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"انتقال به بالا/ چپ"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"انتقال به پایین/ راست"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{برنامه دیگر}one{برنامه دیگر}other{برنامه دیگر}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"رایانه"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> و <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"ازهم باز کردن <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"جمع کردن <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"حلقه جستجو"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"نماد برنامه"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"عنوان برنامه"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"دکمه بستن"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-fi/strings.xml b/quickstep/res/values-fi/strings.xml
index 10e4699..e23ae30 100644
--- a/quickstep/res/values-fi/strings.xml
+++ b/quickstep/res/values-fi/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Vapaamuotoinen"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Tietokone"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Siirrä ulkoiselle näytölle"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Sulje"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Tietokone"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Ei viimeaikaisia kohteita"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Sovelluksen käyttöasetukset"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Poista kaikki"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Viimeisimmät sovellukset"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Tehtävä suljettu"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> jäljellä tänään"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Sovellusehdotukset käytössä"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Sovellusehdotukset on poistettu käytöstä"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Ennakoitu sovellus: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Eleillä navigoinnin ohje"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Käännä laite"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Käännä laite, niin voit katsoa esittelyn eleillä navigoinnista"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pyyhkäise aivan oikeasta tai vasemmasta reunasta"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Voit muuttaa Takaisin-eleen herkkyyttä asetuksista"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Siirry takaisin pyyhkäisemällä"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Voit palata edelliseen näkymään pyyhkäisemällä näytön vasemmasta tai oikeasta reunasta keskelle."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Palaa takaisin edelliselle näytölle pyyhkäisemällä kahdella sormella vasemmasta tai oikeasta reunasta näytön keskikohtaan."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Edelliseen siirtyminen"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Pyyhkäise vasemmasta tai oikeasta reunasta näytön keskelle"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Pyyhkäise ylös näytön alareunasta"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Olet oppinut aloitusnäytölle palaamiseleen"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Siirry aloitusnäytölle pyyhkäisemällä"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Pyyhkäise ylös näytön alareunasta. Tämä ele vie sinut aina aloitusnäytölle."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Pyyhkäise näytön alareunasta ylöspäin kahdella sormella. Tämä ele vie sinut aina aloitusnäytölle."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Aloitusnäytölle siirtyminen"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Pyyhkäise ylös näytön alareunasta"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Hienoa!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Olet oppinut sovellusten vaihtamiseleen"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Vaihda sovellusta pyyhkäisemällä"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Voit vaihtaa sovelluksesta toiseen pyyhkäisemällä ylöspäin näytön alareunasta ja päästämällä sitten irti."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Vaihda sovelluksia pyyhkäisemällä ylös näytön alareunasta kahdella sormella ja päästä sitten irti."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Sovelluksen vaihtaminen"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Pyyhkäise ylöspäin näytön alareunasta ja päästä irti"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Hienoa!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Tehtäväpalkki näkyvissä"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Vas. palkki ja kuplat näkyvissä"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Oik. palkki ja kuplat näkyvissä"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Tehtäväpalkki piilotettu"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Palkki ja kuplat piilotettu"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigointipalkki"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Näytä tehtäväpalkki aina"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Vaihda navigointitilaa"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Tehtäväpalkin ylivuotu"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Siirrä ylös tai vasemmalle"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Siirrä alas tai oikealle"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{muu sovellus}other{muuta sovellusta}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Tietokone"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"laajenna <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"tiivistä <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Sovelluskuvake"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Sovelluksen nimi"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Sulje-painike"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-fr-rCA/strings.xml b/quickstep/res/values-fr-rCA/strings.xml
index 2c296ca9..448abf5 100644
--- a/quickstep/res/values-fr-rCA/strings.xml
+++ b/quickstep/res/values-fr-rCA/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Forme libre"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Bureau"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Passer à un écran externe"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Fermer"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Bureau"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Aucun élément récent"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres d\'utilisation de l\'appli"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Tout effacer"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Applis récentes"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Tâche fermée"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> : <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Il reste <xliff:g id="TIME">%1$s</xliff:g> aujourd\'hui"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Les suggestions d\'applis sont activées"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Les suggestions d\'applis sont désactivées"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Appli prédite : <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutoriel de navigation par gestes"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Faites pivoter votre appareil"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Veuillez faire pivoter votre appareil pour terminer le tutoriel de navigation par gestes"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Assurez-vous de balayer l\'écran à partir de l\'extrémité droite ou gauche"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Modifiez la sensibilité du geste de retour dans Paramètres"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Balayez l\'écran pour revenir en arrière"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Pour revenir à l\'écran précédent, balayez l\'écran de l\'extrémité gauche ou droite vers le centre."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Pour revenir à l\'écran précédent, balayez l\'écran avec deux doigts du bord gauche ou droit jusqu\'au centre."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Retour"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Balayez l\'écran à partir du bord gauche ou droit de l\'écran vers le centre"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Assurez-vous de balayer l\'écran à partir de l\'extrémité inférieure vers le haut"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Vous avez appris le geste de retour à l\'écran d\'accueil"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Balayez pour revenir à l\'écran d\'accueil"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Balayez l\'écran du bas vers le haut. Ce geste vous ramène toujours à l\'écran d\'accueil."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Balayez l\'écran de bas en haut avec deux doigts. Ce geste vous ramène toujours à l\'écran d\'accueil."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Retour à la page d\'accueil"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Balayez votre écran du bas vers le haut"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Super!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Vous avez appris le geste de changement d\'appli"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Balayez pour basculer entre les applis"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Pour changer d\'appli, balayez l\'écran de bas en haut, maintenez le doigt dessus, puis relâchez-le."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Pour changer d\'appli, balayez l\'écran de bas en haut avec deux doigts, maintenez-les et relâchez-les."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Changer d\'appli"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Balayez l\'écran de bas en haut, maintenez le doigt en place, puis relâchez-le"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Bien joué!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Barre des tâches affichée"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Barre des tâches/bulles à gauche"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Barre des tâches/bulles à droite"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Barre des tâches masquée"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Barre des tâches/bulles masquées"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barre de navigation"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Tjrs afficher barre des tâches"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Changer de mode de navigation"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Barre des tâches à développer"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Déplacer vers le coin supérieur gauche de l\'écran"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Déplacer vers le coin inférieur droit de l\'écran"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{autre appli}one{autre appli}other{autres applis}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"Développer <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"Réduire <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Encercler et rechercher"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Icône de l\'appli"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Nom de l\'appli"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Bouton Fermer"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-fr/strings.xml b/quickstep/res/values-fr/strings.xml
index ebea8d3..59310e2 100644
--- a/quickstep/res/values-fr/strings.xml
+++ b/quickstep/res/values-fr/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Format libre"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Mode ordinateur"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Déplacer vers l\'écran externe"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Fermer"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Mode ordinateur"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Aucun élément récent"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Paramètres de consommation de l\'application"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Tout effacer"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Applications récentes"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Tâche fermée"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Encore <xliff:g id="TIME">%1$s</xliff:g> aujourd\'hui"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Suggestions d\'applications activées"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Les suggestions d\'applications sont désactivées"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Application prédite : <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutoriel sur la navigation par gestes"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Faire pivoter l\'appareil"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Veuillez faire pivoter votre appareil pour effectuer le tutoriel de navigation par gestes"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Veillez à bien balayer l\'écran depuis le bord gauche ou droit"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Modifiez la sensibilité du geste retour dans les paramètres"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Balayez l\'écran pour revenir en arrière"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Pour revenir à l\'écran précédent, balayez l\'écran depuis le bord droit ou gauche jusqu\'au centre."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Pour revenir au dernier écran, balayez l\'écran avec deux doigts en partant du bord gauche ou droit vers le milieu."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Retour"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Balayez l\'écran à partir du bord gauche ou droit jusqu\'au centre"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Veillez à balayer vers le haut depuis le bord inférieur de l\'écran"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Vous avez appris le geste pour revenir à l\'écran d\'accueil"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Balayez pour revenir à l\'écran d\'accueil"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Balayez l\'écran de bas en haut. Ce geste vous ramènera toujours à l\'écran d\'accueil."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Balayez l\'écran de bas en haut avec 2 doigts. Ce geste vous ramènera toujours à l\'écran d\'accueil."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Retour à l\'accueil"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Balayez l\'écran de bas en haut"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Bravo !"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Vous avez appris le geste pour passer d\'une appli à l\'autre"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Balayez pour passer d\'une appli à l\'autre"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Pour changer d\'appli, balayez l\'écran de bas en haut, appuyez de manière prolongée et relâchez."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Pour changer d\'appli, balayez l\'écran de bas en haut avec deux doigts, maintenez appuyé et relâchez."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Changer d\'appli"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Balayez l\'écran de bas en haut, appuyez de manière prolongée, puis relâchez"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Bravo !"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Barre des tâches affichée"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Barre des tâches et bulles affichées à gauche"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Barre des tâches et bulles affichées à droite"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Barre des tâches masquée"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Barre des tâches et bulles masquées"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barre de navigation"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Barre des tâches tjs visible"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Modifier le mode de navigation"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Développement de la barre des tâches"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Déplacer en haut ou à gauche"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Déplacer en bas ou à droite"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{autre application}one{autre application}other{autres applications}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Mode ordinateur"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> et <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"Développer <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"Réduire <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Entourer pour chercher"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Icône de l\'application"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Titre de l\'application"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Bouton \"Fermer\""</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-gl/strings.xml b/quickstep/res/values-gl/strings.xml
index d9a78ee..994b5fd 100644
--- a/quickstep/res/values-gl/strings.xml
+++ b/quickstep/res/values-gl/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Forma libre"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Escritorio"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover á pantalla externa"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Pechar"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Ordenador"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Non hai elementos recentes"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configuración do uso de aplicacións"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Borrar todo"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Apps recentes"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Tarefa pechada"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt;1 min"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Tempo restante hoxe <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"As suxestións de aplicacións están activadas"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"As suxestións de aplicacións están desactivadas"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Aplicación predita: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Titorial de navegación con xestos"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Xira o dispositivo"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Xira o dispositivo para completar o titorial de navegación con xestos"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Asegúrate de pasar o dedo desde o bordo dereito ou esquerdo"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Podes cambiar a sensibilidade do xesto en Configuración"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Pasa o dedo para volver"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Para volver á última pantalla, pasa o dedo cara ao medio desde o bordo dereito ou esquerdo."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Para volver á última pantalla, pasa 2 dedos desde o bordo esquerdo ou o dereito ata a metade da pantalla."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Volver atrás"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Pasa o dedo desde o bordo esquerdo ou dereito ata o medio da pantalla"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Asegúrate de pasar o dedo cara arriba desde o bordo inferior da pantalla"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Completaches o xesto de ir ao inicio"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Pasa o dedo para ir ao inicio"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Pasa o dedo cara arriba desde a parte inferior da pantalla. Ao facelo, irás á pantalla de inicio."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Pasa 2 dedos desde a parte inferior da pantalla. Ao facelo, sempre irás á pantalla de inicio."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Ir á pantalla de inicio"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Pasa o dedo cara arriba desde a parte inferior da pantalla"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Moi ben!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Completaches o xesto para cambiar de aplicación"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Pasa o dedo para cambiar de aplicación"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Para cambiar de aplicación, pasa o dedo cara arriba desde a parte inferior da pantalla, mantén premido e levanta o dedo."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Para cambiar de app, pasa 2 dedos cara arriba desde abaixo, mantén premida a pantalla e levántaos."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Pasar dunha aplicación a outra"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Pasa o dedo cara arriba desde a parte inferior da pantalla, mantena premida e sepárao"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Ben feito!"</string>
@@ -98,7 +96,7 @@
     <string name="action_share" msgid="2648470652637092375">"Compartir"</string>
     <string name="action_screenshot" msgid="8171125848358142917">"Facer captura"</string>
     <string name="action_split" msgid="2098009717623550676">"Dividir"</string>
-    <string name="action_save_app_pair" msgid="5974823919237645229">"Gardar parella apps"</string>
+    <string name="action_save_app_pair" msgid="5974823919237645229">"Gardar parella de apps"</string>
     <string name="toast_split_select_app" msgid="8464310533320556058">"Para usar a pantalla dividida, toca outra app"</string>
     <string name="toast_contextual_split_select_app" msgid="433510957123687090">"Escolle outra aplicación para usar a pantalla dividida."</string>
     <string name="toast_split_select_app_cancel" msgid="1939025102486630426">"Cancelar"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Estase mostrando a barra de tarefas"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Barra e burbullas á esquerda"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Barra e burbullas á dereita"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Non se está mostrando a barra de tarefas"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Barra e burbullas ocultas"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barra de navegación"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Ver sempre a barra de tarefas"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Cambiar modo de navegación"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Menú adicional da barra de tarefas"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover á parte superior ou á esquerda"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover á parte inferior ou á dereita"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{aplicación máis}other{aplicacións máis}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Escritorio"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"despregar <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"contraer <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Rodear para buscar"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Icona da aplicación"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Título da aplicación"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Botón Pechar"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-gu/strings.xml b/quickstep/res/values-gu/strings.xml
index 1bdcaa1..03fbd63 100644
--- a/quickstep/res/values-gu/strings.xml
+++ b/quickstep/res/values-gu/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"ફ્રિફોર્મ"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ડેસ્કટૉપ"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"બાહ્ય ડિસ્પ્લે પર ખસેડો"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"બંધ કરો"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ડેસ્કટૉપ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"તાજેતરની કોઈ આઇટમ નથી"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ઍપ વપરાશનું સેટિંગ"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"બધું સાફ કરો"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"તાજેતરની ઍપ"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"કાર્ય બંધ કર્યું"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 મિનિટ"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> આજે બાકી"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ઍપના સુઝાવો ચાલુ છે"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ઍપના સુઝાવો બંધ છે"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"પૂર્વાનુમાનિત ઍપ: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"સંકેતથી નૅવિગેશનનું ટ્યૂટૉરિઅલ"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"તમારા ડિવાઇસને ફેરવો"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"સંકેતથી નૅવિગેશન ટ્યૂટૉરિઅલ પૂર્ણ કરવા માટે કૃપા કરીને તમારા ડિવાઇસને ફેરવો"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ખાતરી કરો કે તમે એકદમ દૂરની જમણી કે ડાબી કિનારીએથી સ્વાઇપ કરો છો"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"પાછા જવાના સંકેતની સંવેદિતા બદલવા માટે, સેટિંગમાં જાઓ"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"પાછળ જવા માટે સ્વાઇપ કરો"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"છેલ્લી સ્ક્રીન પર પાછા જવા, ડાબી કે જમણી કિનારીએથી સ્ક્રીનના મધ્ય ભાગ સુધી સ્વાઇપ કરો."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"છેલ્લી સ્ક્રીન પર પાછા જવા માટે, 2 આંગળી વડે ડાબી કે જમણી કિનારીએથી સ્ક્રીનના મધ્ય ભાગ સુધી સ્વાઇપ કરો."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"પાછા જાઓ"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"જમણી કે ડાબી કિનારીએથી સ્ક્રીનના મધ્ય ભાગ સુધી સ્વાઇપ કરો"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"ખાતરી કરો કે તમે સ્ક્રીનની નીચેની કિનારીએથી ઉપરની તરફ સ્વાઇપ કરો છો"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"તમે હોમ સ્ક્રીન પર જવાનો સંકેત પૂર્ણ કર્યો"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"હોમ સ્ક્રીન પર જવા માટે સ્વાઇપ કરો"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"તમારી સ્ક્રીનના નીચેના ભાગથી ઉપરની તરફ સ્વાઇપ કરો. આ સંકેત તમને હંમેશાં હોમ સ્ક્રીન પર લઈ જાય છે."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"2 આંગળી વડે સ્ક્રીનના સૌથી નીચેના ભાગથી ઉપરની તરફ સ્વાઇપ કરો. આ સંકેત તમને હંમેશાં હોમ સ્ક્રીન પર લઈ જાય છે."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"હોમ પર જાઓ"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"તમારી સ્ક્રીનની સૌથી નીચેથી ઉપરની તરફ સ્વાઇપ કરો"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"ખૂબ સરસ કામ!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"તમે ઍપ સ્વિચ કરવાનો સંકેત પૂર્ણ કર્યો."</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"ઍપ સ્વિચ કરવા સ્વાઇપ કરો"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"એક ઍપ પરથી બીજી ઍપ પર સ્વિચ કરવા માટે, તમારી સ્ક્રીનના નીચેના ભાગથી ઉપરની તરફ સ્વાઇપ કરીને, થોડીવાર દબાવી રાખો, પછી છોડી દો."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"એક ઍપ પરથી બીજી ઍપ પર સ્વિચ કરવા માટે, 2 આંગળી વડે તમારી સ્ક્રીનના સૌથી નીચેના ભાગથી ઉપરની તરફ સ્વાઇપ કરીને, થોડીવાર દબાવી રાખો, પછી છોડી દો."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"ઍપ સ્વિચ કરો"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"તમારી સ્ક્રીનના નીચેના ભાગથી ઉપરની તરફ સ્વાઇપ કરીને થોડીવાર દબાવી રાખો પછી છોડી દો"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"વાહ!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ટાસ્કબાર બતાવવામાં આવ્યો"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ટાસ્કબાર અને બબલ ડાબે બતાવ્યા"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ટાસ્કબાર અને બબલ જમણે બતાવ્યા"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ટાસ્કબાર છુપાવવામાં આવ્યો"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ટાસ્કબાર અને બબલ છુપાવેલા છે"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"નૅવિગેશન બાર"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"હંમેશાં ટાસ્કબાર બતાવો"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"નૅવિગેશન મોડ બદલો"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ટાસ્કબાર ઓવરફ્લો"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"સૌથી ઉપર ડાબી બાજુએ ખસેડો"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"સૌથી નીચે જમણી બાજુએ ખસેડો"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{વધુ ઍપ}one{વધુ ઍપ}other{વધુ ઍપ}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"ડેસ્કટૉપ"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> અને <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> મોટો કરો"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> નાનો કરો"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"શોધવા માટે વર્તુળ દોરો"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ઍપનું આઇકન"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ઍપનું શીર્ષક"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"\'બંધ કરો\' બટન"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-hi/strings.xml b/quickstep/res/values-hi/strings.xml
index e97aa78..89648d4 100644
--- a/quickstep/res/values-hi/strings.xml
+++ b/quickstep/res/values-hi/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"फ़्रीफ़ॉर्म"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटॉप"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"बाहरी डिसप्ले पर जाएं"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"बंद करें"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"डेस्कटॉप"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"हाल ही का कोई आइटम नहीं है"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ऐप्लिकेशन इस्तेमाल की सेटिंग"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"सभी हटाएं"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"हाल ही में इस्तेमाल किए गए ऐप्लिकेशन"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"टास्क बंद किया गया"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt;1 मिनट"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"आज <xliff:g id="TIME">%1$s</xliff:g> और चलेगा"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"सुझाए गए ऐप्लिकेशन की सुविधा चालू है"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"सुझाए गए ऐप्लिकेशन की सुविधा बंद है"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"सुझाया गया ऐप्लिकेशन: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"जेस्चर वाले नेविगेशन के लिए ट्यूटोरियल"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"अपना डिवाइस घुमाएं"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"जेस्चर वाले नेविगेशन से जुड़े ट्यूटोरियल को पूरा करने के लिए अपने डिवाइस को घुमाएं"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"स्क्रीन पर बिलकुल दाएं या बाएं किनारे से स्वाइप करें"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"\'सेटिंग\' में जाकर, पीछे जाने के लिए इस्तेमाल होने वाले हाथ के जेस्चर (हाव-भाव) की संवेदनशीलता बदलें"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"पिछली स्क्रीन पर वापस जाने के लिए स्वाइप करें"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"पिछली स्क्रीन पर वापस जाने के लिए, स्क्रीन के बाएं या दाएं किनारे से बीचों-बीच तक स्वाइप करें."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"पिछली स्क्रीन पर वापस जाने के लिए, स्क्रीन के बाएं या दाएं किनारे से स्क्रीन के बीच तक दो उंगलियों से स्वाइप करें."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"पिछली स्क्रीन पर वापस जाना"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"स्क्रीन पर बाएं या दाएं किनारे से बीच तक स्वाइप करें"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"स्क्रीन पर निचले किनारे से ऊपर की ओर स्वाइप करें"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"आपने जान लिया है कि हाथ का जेस्चर इस्तेमाल करके होम स्क्रीन पर कैसे जाएं"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"होम स्क्रीन पर जाने के लिए स्वाइप करें"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"स्क्रीन पर नीचे से ऊपर की ओर स्वाइप करें. हाथ का यह जेस्चर आपको हमेशा होम स्क्रीन पर ले जाता है."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"स्क्रीन के सबसे नीचे से ऊपर की ओर 2 उंगलियों से स्वाइप करें. जेस्चर हमेशा होम स्क्रीन पर ले जाता है."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"होम स्क्रीन पर जाना"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"स्क्रीन पर सबसे नीचे से ऊपर की ओर स्वाइप करें"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"बहुत बढ़िया!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"आपने जान लिया है कि हाथ का जेस्चर इस्तेमाल करके ऐप्लिकेशन के बीच स्विच कैसे करें"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"एक ऐप्लिकेशन से दूसरे पर जाने के लिए स्वाइप करें"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"एक ऐप से दूसरे पर जाने के लिए स्क्रीन पर नीचे से ऊपर की ओर स्वाइप करें, दबाकर रखें, और फिर छोड़ दें."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"इन ऐप के बीच स्विच करने के लिए, दो उंगलियों से नीचे से ऊपर स्वाइप करें, होल्ड करें, और फिर छोड़ें."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"ऐप्लिकेशन के बीच स्विच करना"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"अपनी स्क्रीन पर सबसे नीचे से ऊपर की ओर स्वाइप करें, स्क्रीन को दबाकर रखें, और फिर छोड़ दें"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"बहुत खूब!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"टास्कबार दिखाया गया"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"टास्कबार और बबल बाईं ओर हैं"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"टास्कबार और बबल दाईं ओर हैं"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"टास्कबार छिपाया गया"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"टास्कबार और बबल छिपाए गए"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"नेविगेशन बार"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"टास्कबार हमेशा दिखाएं"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"नेविगेशन का मोड बदलें"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"टास्कबार ओवरफ़्लो आइकॉन"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ऊपर/बाईं तरफ़ ले जाएं"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"नीचे/दाईं तरफ़ ले जाएं"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ज़्यादा ऐप्लिकेशन}one{ज़्यादा ऐप्लिकेशन}other{ज़्यादा ऐप्लिकेशन}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"डेस्कटॉप"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> और <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> को बड़ा करें"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> को छोटा करें"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"सर्कल बनाकर ढूंढें"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ऐप्लिकेशन आइकॉन"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ऐप्लिकेशन का नाम"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"\'बंद करें\' बटन"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-hr/strings.xml b/quickstep/res/values-hr/strings.xml
index 441a80c..e4a0f7c 100644
--- a/quickstep/res/values-hr/strings.xml
+++ b/quickstep/res/values-hr/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Slobodni oblik"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Računalo"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Premještanje na vanjski zaslon"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Zatvori"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Radna površina"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nema nedavnih stavki"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Postavke upotrebe aplikacija"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Izbriši sve"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nedavne aplikacije"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Zadatak je zatvoren"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Još <xliff:g id="TIME">%1$s</xliff:g> danas"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Predlaganje apl. omogućeno"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Predlaganje apl. onemogućeno"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predviđena aplikacija: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Vodič za navigaciju pokretima"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Zakrenite uređaj"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Zakrenite uređaj da biste dovršili vodič o navigaciji pokretima"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Prijeđite prstom od krajnjeg desnog ili krajnjeg lijevog ruba"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Osjetljivost pokreta povratka promijenite u postavkama"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Prijeđite prstom da biste se vratili"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Da biste se vratili na prethodni zaslon, prijeđite prstom od lijevog ili desnog ruba do sredine zaslona."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Da biste se vratili na posljednji zaslon, prijeđite s dva prsta od lijevog ili desnog ruba do sredine zaslona."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Povratak"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Prijeđite prstom od lijevog ili desnog ruba do sredine zaslona"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Pazite da prijeđete prstom prema gore od donjeg ruba zaslona"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Napravili ste pokret za otvaranje početnog zaslona"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Prijeđite prstom da biste otvorili početni zaslon"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Prijeđite prstom od dna zaslona prema gore. Tim pokretom uvijek će se otvoriti početni zaslon."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Prijeđite s dva prsta od dna zaslona prema gore. Tim pokretom uvijek će se otvoriti početni zaslon."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Otvaranje početnog zaslona"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Prijeđite prstom od dna zaslona prema gore"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Sjajno!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Napravili ste pokret za promjenu aplikacije"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Povlačenje prstom za promjenu aplikacije"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Da biste promijenili aplikaciju, prijeđite prstom od dna zaslona prema gore, zadržite pritisak pa pustite."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Za promjenu aplikacije prijeđite s dva prsta od dna zaslona prema gore, zadržite pritisak i pustite."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Promjena aplikacije"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Prijeđite prstom od dna zaslona prema gore, zadržite pritisak pa pustite"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Odlično!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Traka sa zadacima prikazana"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Traka sa zadacima/oblačići prikazani lijevo"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Traka sa zadacima/oblačići prikazani desno"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Traka sa zadacima skrivena"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Traka sa zadacima/oblačići skriveni"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigacijska traka"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Uvijek prikaži traku zadataka"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Promijeni način navigacije"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Dodatni izbornik trake sa zadacima"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Premjesti gore/lijevo"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premjesti dolje/desno"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{dodatna aplikacija}one{dodatna aplikacija}few{dodatne aplikacije}other{dodatnih aplikacija}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Računalo"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"proširite oblačić <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"sažmite oblačić <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Zaokružite i potražite"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikona aplikacije"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Naziv aplikacije"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Gumb Zatvori"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-hu/strings.xml b/quickstep/res/values-hu/strings.xml
index ea29620..ac97372 100644
--- a/quickstep/res/values-hu/strings.xml
+++ b/quickstep/res/values-hu/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Szabad forma"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Asztali"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Áthelyezés külső kijelzőre"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Bezárás"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Asztali"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nincsenek mostanában használt elemek"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Alkalmazáshasználati beállítások"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Összes törlése"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Legutóbbi alkalmazások"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"A feladat bezárult"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 perc"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Ma még <xliff:g id="TIME">%1$s</xliff:g> van hátra"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Alkalmazásjavaslatok engedélyezve"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Alkalmazásjavaslatok letiltva"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Várható alkalmazás: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Kézmozdulatokkal való navigáció útmutatója"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Forgassa el eszközét"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Forgassa el eszközét a kézmozdulatokkal való navigáció útmutatójának befejezéséhez"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Csúsztasson a képernyő jobb vagy bal széléről."</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"A vissza mozdulat érzékenysége a Beállításokban módosítható"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Csúsztasson a visszalépéshez"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Ha visszatérne a legutóbbi képernyőre, csúsztasson a képernyő közepére a bal vagy a jobb széléről."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Ha vissza szeretne térni a legutóbbi képernyőre, csúsztasson gyorsan két ujjal a képernyő bal vagy jobb széléről a közepe felé."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Vissza"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Csúsztasson bal vagy jobb szélről a képernyő közepe felé."</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Csúsztasson felfelé a képernyő alsó szélétől."</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Teljesítette a kezdőképernyőre lépés kézmozdulatát."</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Csúsztatás a kezdőképernyőre lépéshez"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Csúsztassa ujját felfelé a képernyő aljától. Ez a mozdulat mindig a kezdőképernyőre visz."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Csúsztasson felfelé két ujjal a képernyő aljáról. Ez a kézmozdulat mindig a kezdőképernyőre viszi."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Ugrás a kezdőképernyőre"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Csúsztasson felfelé a képernyő aljától."</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Kiváló!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Teljesítette az alkalmazásváltás kézmozdulatát."</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Alkalmazásváltás csúsztatással"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Appok közti váltáshoz csúsztasson felfelé a kép aljáról, tartsa lenyomva az ujját, majd emelje fel."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Appváltáshoz csúsztasson fel két ujjal a kép aljáról, tartsa lenyomva ujjait, majd emelje fel őket."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Váltás az alkalmazások között"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Csúsztasson felfelé a képernyő aljáról, tartsa lenyomva ujját, majd emelje fel"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Szép munka!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Feladatsáv megjelenítve"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Feladatsáv és buborék balra"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Feladatsáv és buborék jobbra"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Feladatsáv elrejtve"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Rejtett feladatsáv és buborék"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigációs sáv"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Mindig megjelenő Feladatsáv"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Navigációs mód módosítása"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Feladatsáv túlcsordulása"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mozgatás felülre vagy a bal oldalra"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mozgatás alulra vagy a jobb oldalra"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{további alkalmazás}other{további alkalmazás}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Asztali"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> kibontása"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> összecsukása"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Bekarikázással keresés"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Alkalmazásikon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Alkalmazás neve"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Bezárás gomb"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-hy/strings.xml b/quickstep/res/values-hy/strings.xml
index 14d715d..27ac837 100644
--- a/quickstep/res/values-hy/strings.xml
+++ b/quickstep/res/values-hy/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Կամայական ձև"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Համակարգիչ"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Տեղափոխել արտաքին էկրան"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Փակել"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Համակարգիչ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Այստեղ դեռ ոչինչ չկա"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Հավելվածի օգտագործման կարգավորումներ"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Փակել բոլորը"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Վերջին հավելվածներ"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Առաջադրանքը փակված է"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 ր"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Այսօր մնացել է՝ <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"«Առաջարկվող հավելվածներ» գործառույթը միացված է"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"«Առաջարկվող հավելվածներ» գործառույթն անջատված է"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Առաջարկվող հավելված՝ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Ժեստերով նավիգացիայի ուղեցույց"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Պտտեք սարքը"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Պտտեք սարքը՝ ժեստերով նավիգացիայի ուղեցույցն ավարտելու համար"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Համոզվեք, որ մատը սահեցնում եք էկրանի աջ կամ ձախ եզրից"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Հետ գնալու ժեստի զգայունությունը փոփոխեք կարգավորումներում"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Սահեցրեք մատը՝ հետ գնալու համար"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Վերջին էկրանին վերադառնալու համար էկրանի աջ կամ ձախ եզրից մատը սահեցրեք դեպի կենտրոն։"</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Վերջին էկրանին վերադառնալու համար 2 մատը սահեցրեք ձախ կամ աջ եզրից դեպի կենտրոն։"</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Հետ գնալ"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Էկրանի աջ կամ ձախ եզրից մատը սահեցրեք դեպի կենտրոն"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Համոզվեք, որ մատն էկրանի ներքևի եզրից վերև եք սահեցնում"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Դուք սովորեցիք հիմնական էկրան անցնելու ժեստը"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Սահեցրեք մատը՝ հիմնական էկրան անցնելու համար"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Մատը սահեցրեք էկրանի ներքևից վերև։ Այս ժեստը բացում է հիմնական էկրանը։"</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Երկու մատը էկրանի ներքևից սահեցրեք վերև։ Այս ժեստը բացում է հիմնական էկրանը։"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Անցնել հիմնական էկրան"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Մատը սահեցրեք էկրանի ներքևից վերև"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Կեցցե՛ք"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Դուք սովորեցիք մի հավելվածից մյուսն անցնելու ժեստը"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Մատը սահեցրեք՝ մյուս հավելվածին անցնելու համար"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Մեկ հավելվածից մյուսն անցնելու համար մատը էկրանի ներքևից սահեցրեք վերև, պահեք, ապա բաց թողեք։"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Մեկ հավելվածից մյուսն անցնելու համար 2 մատը էկրանի ներքևից սահեցրեք վերև, պահեք, ապա բաց թողեք։"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Անցում մեկ հավելվածից մյուսին"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Մատը սահեցրեք էկրանի ներքևից վերև, պահեք և բաց թողեք"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Հիանալի՛ է"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Խնդրագոտին ցուցադրվում է"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Հավելվածների վահանակն ու ամպիկները տեսանելի են ձախ կողմում"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Հավելվածների վահանակն ու ամպիկները տեսանելի են աջ կողմում"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Խնդրագոտին թաքցված է"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Հավելվածների վահանակն ու ամպիկները թաքցված են"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Նավիգացիայի գոտի"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Միշտ ցույց տալ վահանակը"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Փոխել նավիգացիայի ռեժիմը"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Հավելվածների վահանակի լրացուցիչ ընտրացանկ"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Տեղափոխել վերևի ձախ անկյուն"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Տեղափոխել ներքևի աջ անկյուն"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{լրացուցիչ հավելված}one{լրացուցիչ հավելված}other{լրացուցիչ հավելված}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Համակարգիչ"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> և <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>. ծավալել"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>. ծալել"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Շրջագծել որոնելու համար"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Հավելվածի պատկերակ"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Հավելվածի անվանում"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"«Փակել» կոճակ"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-in/strings.xml b/quickstep/res/values-in/strings.xml
index 4039f36..69a2a82 100644
--- a/quickstep/res/values-in/strings.xml
+++ b/quickstep/res/values-in/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Format bebas"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Pindahkan ke layar eksternal"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Tutup"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Tidak ada item yang baru dibuka"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Setelan penggunaan aplikasi"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Hapus semua"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplikasi terbaru"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Tugas Ditutup"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 menit"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> tersisa hari ini"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Saran aplikasi diaktifkan"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Saran aplikasi dinonaktifkan"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Aplikasi yang diprediksi: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial Navigasi Gestur"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Putar perangkat Anda"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Putar perangkat Anda untuk menyelesaikan tutorial navigasi gestur"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pastikan Anda menggeser dari tepi ujung kanan atau ujung kiri"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Untuk mengubah sensitivitas gestur kembali, buka Setelan"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Geser untuk kembali"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Untuk kembali ke layar yang sebelumnya dibuka, geser dari tepi kiri atau kanan ke tengah layar."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Untuk kembali ke layar terakhir, geser dengan 2 jari dari tepi kiri atau kanan ke tengah layar."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Kembali"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Geser dari tepi kiri atau kanan ke tengah layar"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Pastikan Anda menggeser ke atas dari tepi bawah layar"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Anda telah menyelesaikan gestur buka layar utama"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Geser untuk beralih ke layar utama"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Geser ke atas dari bagian bawah layar. Gestur ini akan selalu membawa Anda ke Layar utama."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Geser ke atas dengan 2 jari dari bawah layar. Gestur ini akan selalu membawa Anda ke Layar utama."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Buka layar utama"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Geser ke atas dari bagian bawah layar"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Bagus!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Anda telah menyelesaikan gestur beralih aplikasi"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Geser untuk beralih aplikasi"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Untuk beralih dari satu aplikasi ke aplikasi lain, geser ke atas dari bagian bawah layar, tahan, lalu lepaskan."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Untuk beralih antar-aplikasi, geser ke atas dengan 2 jari dari bawah layar, tahan, lalu lepaskan."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Beralih aplikasi"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Geser ke atas dari bagian bawah layar, tahan, kemudian lepas"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Oke!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taskbar ditampilkan"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taskbar &amp; balon kiri ditampilkan"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taskbar &amp; bubbles kanan ditampilkan"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taskbar disembunyikan"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taskbar &amp; balon disembunyikan"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Menu navigasi"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Selalu tampilkan Taskbar"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Ubah mode navigasi"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Tambahan Taskbar"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Pindahkan ke atas/kiri"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Pindahkan ke bawah/kanan"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{aplikasi lainnya}other{aplikasi lainnya}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"luaskan <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"ciutkan <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Lingkari untuk Menelusuri"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikon aplikasi"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Judul aplikasi"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Tombol tutup"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-is/strings.xml b/quickstep/res/values-is/strings.xml
index bddca4d..a8c2770 100644
--- a/quickstep/res/values-is/strings.xml
+++ b/quickstep/res/values-is/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Frjálst snið"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Tölva"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Færa í annað tæki"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Loka"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Tölva"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Engin nýleg atriði"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Notkunarstillingar forrits"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Hreinsa allt"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nýleg forrit"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Verkefni lokað"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 mín."</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> eftir í dag"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Kveikt á tillögum að forritum"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Slökkt er á tillögðum forritum"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Tillaga að forriti: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Leiðsögn fyrir bendingastjórnun"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Snúðu tækinu"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Snúðu tækinu til að ljúka leiðsögn um bendingastjórnun"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Passaðu að strjúka frá jaðri hægri eða vinstri brúnar"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Til að breyta næmi til baka-bendingar ferðu í stillingar"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Strjúktu til að fara til baka"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Til að fara til baka á síðasta skjá skaltu strjúka frá vinstri eða hægri brún að miðju skjásins."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Strjúktu frá vinstri eða hægri brún að miðju skjásins með 2 fingrum til að fara aftur á síðasta skjá."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Til baka"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Strjúktu frá vinstri eða hægri brún að miðju skjásins"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Passaðu að strjúka upp frá neðri brún skjásins"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Þú laukst við að kynna þér bendinguna „heim“"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Strjúktu til að fara heim"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Strjúktu upp frá neðri hluta skjásins. Þetta flytur þig alltaf á heimaskjáinn."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Strjúktu frá neðri brún skjásins með 2 fingrum. Þessi bending opnar ávallt heimaskjáinn."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Fara á heimaskjá"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Strjúktu upp frá neðri hluta skjásins"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Vel gert!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Þú laukst við að kynna þér bendinguna „skipta um forrit“"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Strjúktu til að skipta á milli forrita"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Strjúktu upp frá neðri hluta skjásins, haltu og slepptu svo til að skipta á milli forrita."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Strjúktu upp frá neðri brún skjásins með 2 fingrum, haltu og slepptu til að skipta á milli forrita."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Skipta um forrit"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Strjúktu upp frá neðri hluta skjásins, haltu inni og slepptu svo"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Vel gert!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Forritastika sýnd"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Verkstika og blöðrur sýndar til vinstri"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Verkstika og blöðrur sýndar til hægri"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Forritastika falin"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Verkstika og blöðrur eru faldar"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Yfirlitsstika"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Alltaf sýna forritastiku"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Breyta leiðsagnarstillingu"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Yfirflæði á forritastiku"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Færa efst/til vinstri"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Færa neðst/til hægri"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{forrit til viðbótar}one{forrit til viðbótar}other{forrit til viðbótar}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Skjáborð"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"stækka <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"minnka <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Forritstákn"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Titil forrits"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Hnappur til að loka"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-it/strings.xml b/quickstep/res/values-it/strings.xml
index af77be4..7d0bcd8 100644
--- a/quickstep/res/values-it/strings.xml
+++ b/quickstep/res/values-it/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Forma libera"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Sposta sul display esterno"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Chiudi"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nessun elemento recente"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Impostazioni di utilizzo delle app"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Cancella tutto"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"App recenti"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Attività chiusa"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Rimanente oggi: <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"La funzionalità app suggerite è attiva"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"La funzionalità app suggerite è disattivata"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"App prevista: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial sulla navigazione tramite gesti"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Ruota il dispositivo"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Ruota il dispositivo per completare il tutorial relativo alla navigazione tramite gesti"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Assicurati di scorrere dal bordo all\'estrema destra o all\'estrema sinistra"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Usa Impostazioni per cambiare sensibilità del gesto Indietro"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Scorri per tornare indietro"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Per tornare all\'ultima schermata, scorri dal bordo sinistro o destro verso il centro dello schermo."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Per tornare all\'ultima schermata, scorri con 2 dita dal bordo sinistro o destro verso il centro dello schermo."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Vai indietro"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Scorri dal bordo sinistro o destro verso il centro dello schermo"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Assicurati di scorrere verso l\'alto dal bordo inferiore dello schermo"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Hai completato il gesto Vai alla schermata Home"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Scorri per andare alla schermata Home"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Scorri verso l\'alto dalla parte inferiore dello schermo. Questo gesto ti porta sempre alla schermata Home."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Scorri verso l\'alto con 2 dita dal basso. Questo gesto ti porta sempre alla schermata Home."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Vai alla schermata Home"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Scorri verso l\'alto dalla parte inferiore dello schermo"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Ottimo lavoro!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Hai completato il gesto Cambia app"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Scorri per passare da un\'app all\'altra"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Per spostarti tra le app, scorri dal basso verso l\'alto sullo schermo, tieni premuto e rilascia."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Per spostarti tra le app, scorri verso l\'alto con 2 dita, tieni premuto e rilascia."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Cambia app"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Scorri verso l\'alto dalla parte inferiore dello schermo, tieni premuto e poi rilascia"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Ben fatto!"</string>
@@ -94,7 +92,7 @@
     <string name="allset_button_hint" msgid="2395219947744706291">"Tocca il pulsante Home per andare alla schermata Home"</string>
     <string name="allset_description_generic" msgid="5385500062202019855">"Puoi iniziare a usare il tuo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="default_device_name" msgid="6660656727127422487">"dispositivo"</string>
-    <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Impostazioni Navigazione del sistema"</annotation></string>
+    <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Impostazioni di navigazione del sistema"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"Condividi"</string>
     <string name="action_screenshot" msgid="8171125848358142917">"Screenshot"</string>
     <string name="action_split" msgid="2098009717623550676">"Dividi"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Barra delle app visualizzata"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Barra app e bolle most. sinis."</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Barra app e bolle most. destr."</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Barra delle app nascosta"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Barra app e bolle nascoste"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barra di navigazione"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Mostra sempre barra app"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Cambia modalità di navigazione"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Overflow barra delle app"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Sposta in alto/a sinistra"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Sposta in basso/a destra"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{altra app}other{altre app}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"espandi <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"comprimi <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Cerchia e Cerca"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Icona dell\'app"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Titolo dell\'app"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Pulsante Chiudi"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-iw/strings.xml b/quickstep/res/values-iw/strings.xml
index acd1878..55295cf 100644
--- a/quickstep/res/values-iw/strings.xml
+++ b/quickstep/res/values-iw/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"מצב חופשי"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"במחשב"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"העברה למסך חיצוני"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"סגירה"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"מחשב"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"אין פריטים אחרונים"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"הגדרות שימוש באפליקציה"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ניקוי הכול"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"אפליקציות אחרונות"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"המשימה סגורה"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"‏&lt; דקה"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"הזמן שנותר להיום: <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"התכונה \'הצעות לאפליקציות\' מופעלת"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ההצעות לאפליקציות מושבתות"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"האפליקציות החזויות: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"מדריך לניווט באמצעות תנועות"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"צריך לסובב את המכשיר"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"צריך לסובב את המכשיר כדי להשלים את המדריך לניווט באמצעות תנועות"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"חשוב להחליק מהקצה השמאלי או הימני"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"כדי לשנות את מידת הרגישות של תנועת החזרה, יש לעבור להגדרות"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"יש להחליק כדי לחזור"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"כדי לחזור למסך הקודם, יש להחליק מהקצה השמאלי או הימני למרכז המסך."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"כדי לחזור למסך הקודם, יש להחליק עם שתי אצבעות מהקצה השמאלי או הימני למרכז המסך."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"הקודם"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"מחליקים מהקצה השמאלי או הימני למרכז המסך"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"חשוב להחליק למעלה מהקצה התחתון של המסך"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"השלמת את תנועת המעבר למסך הבית"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"יש להחליק כדי לעבור למסך הבית"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"החלקה למעלה מתחתית המסך תמיד תעביר אותך למסך הבית."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"יש להחליק למעלה עם שתי אצבעות מתחתית המסך. התנועה הזו תמיד מעבירה אותך למסך הבית."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"מעבר למסך הבית"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"מחליקים כלפי מעלה מהחלק התחתון של המסך"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"מעולה!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"השלמת את תנועת המעבר בין האפליקציות"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"יש להחליק כדי לעבור בין אפליקציות"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"כדי לעבור בין אפלקציות, יש להחליק למעלה מתחתית המסך, להחזיק ולאחר מכן לשחרר."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"כדי לעבור בין אפלקציות, יש להחליק למעלה עם שתי אצבעות מתחתית המסך, להחזיק ולאחר מכן לשחרר."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"מעבר בין אפליקציות"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"מחליקים כלפי מעלה מתחתית המסך, מחזיקים ואז משחררים"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"כל הכבוד!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"סרגל האפליקציות מוצג"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"סרגל האפליקציות והבועות מוצגים משמאל"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"סרגל האפליקציות והבועות מוצגים מימין"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"סרגל האפליקציות מוסתר"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"סרגל האפליקציות והבועות הוסתרו"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"סרגל הניווט"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"סרגל האפליקציות מוצג תמיד"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"שינוי מצב הניווט"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"אפשרויות נוספות בסרגל האפליקציות"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"העברה לפינה השמאלית/העליונה"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"העברה לפינה הימנית/התחתונה"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{אפליקציה נוספת}one{אפליקציות נוספות}two{אפליקציות נוספות}other{אפליקציות נוספות}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"מחשב"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ו-<xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"הרחבה של <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"כיווץ של <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"מקיפים ומחפשים"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"סמל האפליקציה"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"שם האפליקציה"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"כפתור הסגירה"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ja/strings.xml b/quickstep/res/values-ja/strings.xml
index 8ce977d..b66126f 100644
--- a/quickstep/res/values-ja/strings.xml
+++ b/quickstep/res/values-ja/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"フリーフォーム"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"デスクトップ"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"外部ディスプレイに移動する"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"閉じる"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"デスクトップ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"最近のアイテムはありません"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"アプリの使用状況の設定"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"すべてクリア"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"最近使ったアプリ"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"タスクを閉じました"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>、<xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"1 分未満"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"今日はあと <xliff:g id="TIME">%1$s</xliff:g>です"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"アプリの候補表示が有効です"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"アプリの候補は無効です"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"予測されたアプリ: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"ジェスチャー ナビゲーションのチュートリアル"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"デバイスを回転してください"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ジェスチャー ナビゲーションのチュートリアルを終了するには、デバイスを回転してください"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"右端または左端からスワイプしてください"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"「戻る」操作の感度を変更するには [設定] に移動します"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"スワイプで戻る"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"直前の画面に戻るには、画面の左端または右端から中央に向かってスワイプします。"</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"直前の画面に戻るには、2 本の指で画面の左端または右端から中央に向かってスワイプします。"</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"戻る"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"画面の左端または右端から中央に向かってスワイプします"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"画面の下端から上にスワイプしてください"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"「ホームに移動」ジェスチャーを学習しました"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"スワイプでホームに戻る"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"画面を下から上にスワイプします。この操作でいつでもホーム画面に戻れます。"</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"2 本の指で画面下部から上にスワイプします。この操作で常にホーム画面に戻ります。"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"ホームに移動"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"画面を下から上にスワイプします"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"よくできました!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"「アプリの切り替え」操作を学習しました"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"スワイプでアプリを切り替える"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"アプリを切り替えるには、画面を下から上にスワイプして長押しし、指を離します。"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"アプリを切り替えるには、2 本の指で画面下部から上にスワイプしたまま長押しし、指を離します。"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"アプリの切り替え"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"画面を下から上にスワイプして長押しし、指を離します"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"完了です!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"タスクバー表示"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"タスクバーとバブルを表示"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"タスクバーとバブルを右側に表示"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"タスクバー非表示"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"タスクバーとバブルを非表示"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"ナビゲーション バー"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"常にタスクバーを表示する"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"ナビゲーション モードを変更"</string>
@@ -143,6 +139,7 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"タスクバーのオーバフロー"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"上 / 左に移動"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"下 / 右に移動"</string>
+    <string name="open_app_as_a_bubble" msgid="6642626287247807473">"アプリをバブルとして開く"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{個のその他のアプリ}other{個のその他のアプリ}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"デスクトップ"</string>
     <string name="quick_switch_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>
@@ -156,4 +153,9 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>を開きます"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>を閉じます"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"かこって検索"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"アプリのアイコン"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"アプリのタイトル"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"閉じるボタン"</string>
+    <string name="pin_to_taskbar" msgid="6607778046321626950">"タスクバーに固定"</string>
+    <string name="unpin_from_taskbar" msgid="2178811773165572676">"タスクバーの固定解除"</string>
 </resources>
diff --git a/quickstep/res/values-ka/strings.xml b/quickstep/res/values-ka/strings.xml
index 1f877e9..0608631 100644
--- a/quickstep/res/values-ka/strings.xml
+++ b/quickstep/res/values-ka/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"თავისუფალი ფორმა"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"დესკტოპი"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"გარე ეკრანზე გადასვლა"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"დახურვა"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"დესკტოპი"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ბოლოს გამოყენებული ერთეულები არ არის"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"აპების გამოყენების პარამეტრები"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ყველას გასუფთავება"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ბოლოდროინდელი აპები"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"ამოცანა დაიხურა"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 წუთი"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"დღეს დარჩენილია <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"აპის შეთავაზებები ჩართულია"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"აპის შეთავაზებები გათიშულია"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ნაწინასწარმეტყველები აპი: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"ჟესტებით ნავიგაციის სახელმძღვანელო"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"შეატრიალეთ მოწყობილობა"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ჟესტებით ნავიგაციის სახელმძღვანელოს დასასრულებლად შეატრიალეთ თქვენი მოწყობილობა"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"გადაფურცლეთ უკიდურესი მარჯვენა ან მარცხენა ბოლოდან"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"დაბრუნების ჟესტის მგრძნობელობის შესაცვლელად გადადით პარამეტრებზე"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"უკან დასაბრუნებლად გადაფურცლეთ"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"ბოლო ეკრანზე დასაბრუნებლად გადაფურცლეთ მარცხენა ან მარჯვენა კიდიდან ეკრანის ცენტრისკენ."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"ბოლო ეკრანზე დასაბრუნებლად ორი თითით გადაფურცლეთ მარცხენა ან მარჯვენა კიდიდან ეკრანის ცენტრისკენ."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"უკან დაბრუნება"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"გადაფურცლეთ მარცხენა ან მარჯვენა ბოლოდან ეკრანის შუისკენ"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"თქვენ შეასრულეთ მთავარ ეკრანზე დაბრუნების ჟესტი"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"მთავარი გვერდის სანახავად გადაფურცლეთ"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ. ამ ჟესტს ყოველთვის მთავარი გვერდის ეკრანზე გადაყავხართ."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"ორი თითით გადაფურცლეთ ეკრანის ქვედა ნაწილიდან. ეს ჟესტი ყოველთვის მთავარ ეკრანზე გადაგიყვანთ."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"მთავარზე გადასვლა"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"გადაფურცლეთ ზემოთ თქვენი ეკრანის ბოლოდან"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"შესანიშნავია!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"თქვენ შეასრულეთ აპების გადართვის ჟესტი"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"აპების გადასართავად გადაფურცლეთ"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"აპების გადასართავად, გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ, დააყოვნეთ, შემდეგ თითი აუშვით."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"აპებს შორის გადასართავად ეკრანის ქვედა კიდიდან ორი თითით გადაფურცლეთ, დააყოვნეთ და აუშვით."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"აპების გადართვა"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"გადაფურცლეთ ეკრანის ქვედა კიდიდან ზემოთ, დააყოვნეთ, შემდეგ აუშვით"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"ყოჩაღ!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ამოცანათა ზოლი ნაჩვენებია"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ამოცანათა ზოლი და ბუშტები ჩანს მარცხნივ"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ამოცანათა ზოლი და ბუშტები ჩანს მარჯვნივ"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ამოცანათა ზოლი დამალულია"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ამოცანათა ზოლი და ბუშტები დამალულია"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"ნავიგაციის ზოლი"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"ამოცანათა ზოლის მუდამ ჩვენება"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"შეცვალეთ ნავიგაციის რეჟიმი"</string>
@@ -143,6 +139,7 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ამოცანათა ზოლის გადავსება"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ზემოთ/მარცხნივ გადატანა"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ქვემოთ/მარჯვნივ გადატანა"</string>
+    <string name="open_app_as_a_bubble" msgid="6642626287247807473">"აპის გახსნა ბუშტის სახით"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{სხვა აპი}other{სხვა აპი}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"დესკტოპი"</string>
     <string name="quick_switch_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>
@@ -156,4 +153,9 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>-ის გაფართოება"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>-ის ჩაკეცვა"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"ძიება წრის მოხაზვით"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"აპის ხატულა"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"აპის სათაური"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"დახურვის ღილაკი"</string>
+    <string name="pin_to_taskbar" msgid="6607778046321626950">"ამოც. ზოლში ჩანიშვნა"</string>
+    <string name="unpin_from_taskbar" msgid="2178811773165572676">"ამოც. ზოლიდან მოხსნა"</string>
 </resources>
diff --git a/quickstep/res/values-kk/strings.xml b/quickstep/res/values-kk/strings.xml
index 5fd172e..8e18fab 100644
--- a/quickstep/res/values-kk/strings.xml
+++ b/quickstep/res/values-kk/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Еркін форма"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Сыртқы дисплейге ауыстыру"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Жабу"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Жұмыс үстелі"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Соңғы элементтер жоқ"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Қолданбаны пайдалану параметрлері"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Барлығын өшіру"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Соңғы қолданбалар"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Тапсырма жабылды."</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 мин"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Бүгін <xliff:g id="TIME">%1$s</xliff:g> қалды"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"\"Қолданба ұсыныстары\" функциясы қосылды."</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"\"Қолданба ұсыныстары\" функциясы өшірулі."</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Болжалды қолданба: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Қимылмен басқару оқулығы"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Құрылғыны бұрыңыз"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Қимылмен басқару нұсқаулығын аяқтау үшін құрылғыны бұрыңыз."</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Экранның оң немесе сол жиегінен сырғытыңыз."</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Артқа қайту қимылы сезгіштігін параметрлерден өзгертіңіз."</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Артқа қайту үшін сырғытыңыз"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Соңғы ашылған экранға оралу үшін экранның сол немесе оң жақ шетінен ортасына қарай сырғытыңыз."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Соңғы ашылған экранға оралу үшін екі саусақпен экранның сол не оң жағынан ортасына сырғытыңыз."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Артқа"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Экранның сол немесе оң жақ шетінен ортасына қарай сырғытыңыз."</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Экранның төменгі шетінен жоғары қарай сырғытыңыз."</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Негізгі экранға қайту қимылын аяқтадыңыз."</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Негізгі экранға өту үшін сырғытыңыз"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Экранның төменгі жағынан жоғары қарай сырғытыңыз. Сонда негізгі экран ашылады."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Екі саусақпен экранның төменгі жағынан жоғары сырғытыңыз. Бұл қимыл үнемі негізгі экранды ашады."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Негізгі экранға өту"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Экранның төменгі жағынан жоғары қарай сырғытыңыз."</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Жарайсыз!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Қолданбаларды ауыстыру қимылын аяқтадыңыз."</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Қолданбаларды ауыстыру үшін сырғытыңыз"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Бір қолданбадан екіншісіне ауысу үшін экранның төменгі жағынан жоғары қарай сырғытып, ұстап тұрыңыз, кейін жіберіңіз."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Бір қолданбадан екіншісіне ауысу үшін екі саусақпен экранның төменгі жағынан жоғары қарай сырғытып, ұстап тұрып жіберіңіз."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Қолданбалар арасында ауысу"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Экранның төменгі жағынан жоғары қарай сырғытып, ұстап тұрыңыз да, жіберіңіз."</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Жарайсыз!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Тапсырмалар жолағы көрсетілді"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Тапсырмалар жолағы мен қалқыма терезелер сол жақта көрсетілген"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Тапсырмалар жолағы мен қалқыма терезелер оң жақта көрсетілген"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Тапсырмалар жолағы жасырылды"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Тапсырмалар жолағы мен қалқыма терезелер жасырылған"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Навигация жолағы"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Тапсырма жолағын үнемі көрсету"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Навигация режимін өзгерту"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"\"Тапсырмалар жолағы\" қосымша мәзірі"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Жоғары/солға жылжыту"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Төмен/оңға жылжыту"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{қосымша қолданба}other{қосымша қолданба}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Компьютер"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> және <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>: жаю"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>: жию"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Қоршау арқылы іздеу"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Қолданба белгішесі"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Қолданба атауы"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"\"Жабу\" түймесі"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-km/strings.xml b/quickstep/res/values-km/strings.xml
index 4c8227e..44f7fe4 100644
--- a/quickstep/res/values-km/strings.xml
+++ b/quickstep/res/values-km/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"មុខងារទម្រង់សេរី"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ដែសថប"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"ផ្លាស់ទីទៅផ្ទាំងអេក្រង់ខាងក្រៅ"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"បិទ"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"អេក្រង់ដើម"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"មិនមានធាតុថ្មីៗទេ"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ការកំណត់​ការប្រើប្រាស់​កម្មវិធី"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"សម្អាត​ទាំងអស់"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"កម្មវិធី​ថ្មីៗ"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"បានបិទ​កិច្ចការ"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 នាទី"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"នៅសល់ <xliff:g id="TIME">%1$s</xliff:g> ទៀត​នៅថ្ងៃនេះ"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"បានបើក​ការណែនាំ​កម្មវិធី"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"បានបិទ​ការណែនាំ​កម្មវិធី"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"កម្មវិធី​ដែលបាន​ព្យាករ៖ <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"មេរៀនអំពីការរុករក​ដោយប្រើចលនា"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"បង្វិលឧបករណ៍របស់អ្នក"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"សូមបង្វិលឧបករណ៍របស់អ្នក ដើម្បីបញ្ចប់មេរៀនអំពីការរុករកដោយប្រើចលនា"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ត្រូវប្រាកដថា​អ្នកអូសពី​គែមខាងស្ដាំ ឬ​ខាងឆ្វេង"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"ដើម្បីប្ដូរកម្រិត​រំញោចនឹង​ចលនាថយក្រោយ សូមចូលទៅកាន់​ការកំណត់"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"អូស​ដើម្បី​ថយក្រោយ"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"ដើម្បី​ត្រឡប់ទៅ​អេក្រង់​មុនវិញ សូមអូស​ពីគែម​ខាងឆ្វេង ឬខាងស្ដាំ​ទៅផ្នែកកណ្ដាល​នៃអេក្រង់​។"</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"ដើម្បី​ត្រឡប់​ទៅ​អេក្រង់​ចុងក្រោយ​វិញ អូស​ដោយ​ប្រើ​ម្រាមដៃពីរ​ពី​គែម​ខាង​ឆ្វេង ឬ​ខាង​ស្ដាំ​ទៅផ្នែក​​កណ្ដាលនៃ​អេក្រង់។"</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"ថយ​ក្រោយ"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"អូសពីគែមខាងឆ្វេង ឬខាងស្ដាំទៅផ្នែកកណ្ដាលនៃអេក្រង់"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"ត្រូវប្រាកដថា​អ្នកអូសឡើងលើ​ពីគែមខាងក្រោម​នៃអេក្រង់"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"អ្នក​បានបញ្ចប់​ចលនា​ចូលទៅកាន់​ទំព័រដើម​ហើយ"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"អូស​ដើម្បីចូល​ទៅកាន់​អេក្រង់ដើម"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"អូស​ឡើងលើ​ពីផ្នែកខាងក្រោម​នៃ​អេក្រង់របស់អ្នក។ ចលនា​នេះ​នាំ​អ្នក​ទៅ​អេក្រង់ដើម​ជានិច្ច។"</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"អូសឡើងលើដោយប្រើម្រាមដៃពីរពីផ្នែកខាងក្រោមនៃ​អេក្រង់។ ចលនានេះ​តែងតែ​នាំអ្នក​ទៅ​អេក្រង់​ដើម​ជា​និច្ច។"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"ទៅអេក្រង់ដើម"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"អូសឡើងលើពី​ផ្នែកខាងក្រោមនៃ​អេក្រង់របស់អ្នក"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"ធ្វើបានល្អ!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"អ្នក​បានបញ្ចប់​ចលនា​ប្ដូរកម្មវិធី​ហើយ"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"អូស​ដើម្បីប្ដូរ​កម្មវិធី"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"ដើម្បីប្ដូររវាងកម្មវិធី សូមអូសឡើងលើពីផ្នែកខាងក្រោមនៃអេក្រង់របស់អ្នក រួចចុចឱ្យជាប់ បន្ទាប់មកដកដៃចេញ។"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"ដើម្បីប្ដូរកម្មវិធី អូសឡើងលើ​ដោយប្រើម្រាមដៃពីរពី​ផ្នែក​ខាងក្រោមនៃ​អេក្រង់របស់អ្នក សង្កត់ ហើយ​លែងវិញ។"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"ប្ដូរ​កម្មវិធី"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"អូស​ឡើងលើ​ពី​ផ្នែកខាងក្រោម​នៃ​អេក្រង់​របស់អ្នក រួចចុចឱ្យជាប់ បន្ទាប់មកដកដៃចេញ"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"ល្អណាស់!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"បានបង្ហាញរបារកិច្ចការ"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"បានបង្ហាញរបារកិច្ចការ និងផ្ទាំងអណ្ដែតនៅខាងឆ្វេង"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"បានបង្ហាញរបារកិច្ចការ និងផ្ទាំងអណ្ដែតនៅខាងស្ដាំ"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"បានលាក់របារកិច្ចការ"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"បានលាក់របារកិច្ចការ និងផ្ទាំងអណ្ដែត"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"របាររុករក"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"បង្ហាញរបារកិច្ចការជានិច្ច"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"ប្ដូរ​មុខងាររុករក"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ម៉ឺនុយបន្ថែមរបារកិច្ចការ"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ផ្លាស់ទីទៅខាងលើ/ឆ្វេង"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ផ្លាស់ទីទៅខាងក្រោម/ស្ដាំ"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{កម្មវិធីច្រើនទៀត}other{កម្មវិធីច្រើនទៀត}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"អេក្រង់ដើម"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> និង <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"ពង្រីក <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"បង្រួម <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"គូររង្វង់ដើម្បីស្វែងរក"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"រូបកម្មវិធី"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ចំណងជើងកម្មវិធី"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"ប៊ូតុងបិទ"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-kn/strings.xml b/quickstep/res/values-kn/strings.xml
index 5fc27d2..2187634 100644
--- a/quickstep/res/values-kn/strings.xml
+++ b/quickstep/res/values-kn/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"ಮುಕ್ತಸ್ವರೂಪ"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ಡೆಸ್ಕ್‌ಟಾಪ್"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"ಬಾಹ್ಯ ಡಿಸ್‌ಪ್ಲೇಗೆ ಸರಿಸಿ"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"ಮುಚ್ಚಿರಿ"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ಡೆಸ್ಕ್‌ಟಾಪ್"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ಯಾವುದೇ ಇತ್ತೀಚಿನ ಐಟಂಗಳಿಲ್ಲ"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ಆ್ಯಪ್‌ ಬಳಕೆಯ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ಎಲ್ಲವನ್ನೂ ತೆರವುಗೊಳಿಸಿ"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ಇತ್ತೀಚಿನ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"ಕಾರ್ಯವನ್ನು ಮುಚ್ಚಲಾಗಿದೆ"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 ನಿ"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"ಇಂದು <xliff:g id="TIME">%1$s</xliff:g> ಸಮಯ ಉಳಿದಿದೆ"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ಆ್ಯಪ್ ಸಲಹೆಗಳನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ಆ್ಯಪ್ ಸಲಹೆಗಳನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ಶಿಫಾರಸು ಮಾಡಿದ ಆ್ಯಪ್: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"ಗೆಸ್ಚರ್ ನ್ಯಾವಿಗೇಶನ್ ಟುಟೋರಿಯಲ್"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"ನಿಮ್ಮ ಸಾಧನವನ್ನು ತಿರುಗಿಸಿ"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ಗೆಸ್ಚರ್ ನ್ಯಾವಿಗೇಶನ್ ಟುಟೋರಿಯಲ್ ಅನ್ನು ಪೂರ್ಣಗೊಳಿಸಲು, ನಿಮ್ಮ ಸಾಧನವನ್ನು ತಿರುಗಿಸಿ"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ನೀವು ಬಲಕೊನೆಯ ಅಂಚಿನಿಂದ ಅಥವಾ ಎಡಕೊನೆಯ ಅಂಚಿನಿಂದ ಸ್ವೈಪ್ ಮಾಡುತ್ತಿದ್ದೀರಿ ಎಂದು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"ಬ್ಯಾಕ್ ಗೆಸ್ಚರ್‌ನ ಸೂಕ್ಷ್ಮತೆ ಬದಲಾಯಿಸಲು, ಸೆಟ್ಟಿಂಗ್‌ಗಳಿಗೆ ಹೋಗಿ"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"ಹಿಂದಕ್ಕೆ ಹೋಗಲು ಸ್ವೈಪ್ ಮಾಡಿ"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"ಹಿಂದಿನ ಸ್ಕ್ರೀನ್‌ಗೆ ಮರಳಲು, ಎಡ ಅಥವಾ ಬಲ ಅಂಚಿನಿಂದ ಸ್ಕ್ರೀನ್ ಮಧ್ಯದವರೆಗೆ ಸ್ವೈಪ್ ಮಾಡಿ."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"ಹಿಂದಿನ ಸ್ಕ್ರೀನ್‌ಗೆ ಹೋಗಲು, 2 ಬೆರಳುಗಳಿಂದ ಎಡ ಅಥವಾ ಬಲ ಅಂಚಿನಿಂದ ಸ್ಕ್ರೀನ್ ಮಧ್ಯಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"ಹಿಂದಿರುಗಿ"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"ಎಡ ಅಥವಾ ಬಲ ಅಂಚಿನಿಂದ ಸ್ಕ್ರೀನ್‌ನ ಮಧ್ಯಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"ಸ್ಕ್ರೀನ್‌ನ ಕೆಳಗಿನ ಅಂಚಿನಿಂದ ನೀವು ಸ್ವೈಪ್ ಮಾಡುತ್ತಿದ್ದೀರಿ ಎಂದು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಿ"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"ನೀವು ಹೋಮ್‌ಗೆ ಹೋಗಿ ಗೆಸ್ಚರ್ ಅನ್ನು ಪೂರ್ಣಗೊಳಿಸಿದ್ದೀರಿ"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಹಿಂತಿರುಗಲು ಸ್ವೈಪ್ ಮಾಡಿ"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"ಸ್ಕ್ರೀನ್‌ನ ಕೆಳಗಿನಿಂದ ಮೇಲೆ ಸ್ವೈಪ್ ಮಾಡಿ. ಈ ಗೆಸ್ಚರ್ ಯಾವಾಗಲೂ ನಿಮ್ಮನ್ನು ಹೋಮ್‌ ಸ್ಕ್ರೀನ್‌ಗೆ ಕರೆದೊಯ್ಯುತ್ತದೆ."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"2 ಬೆರಳುಗಳಿಂದ ಸ್ಕ್ರೀನ್‌ನ ಕೆಳಗಿನಿಂದ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ. ಈ ಗೆಸ್ಚರ್ ಯಾವಾಗಲೂ ನಿಮ್ಮನ್ನು ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಕರೆದೊಯ್ಯುತ್ತದೆ."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"ಹೋಮ್‌ಗೆ ಹೋಗಿ"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"ನಿಮ್ಮ ಸ್ಕ್ರೀನ್‌ನ ಕೆಳಗಿನಿಂದ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"ಭೇಷ್!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"ನೀವು ಆ್ಯಪ್‌ಗಳನ್ನು ಬದಲಾಯಿಸುವ ಗೆಸ್ಚರ್ ಅನ್ನು ಪೂರ್ಣಗೊಳಿಸಿದ್ದೀರಿ"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"ಆ್ಯಪ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಸ್ವೈಪ್ ಮಾಡಿ"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"ಆ್ಯಪ್‌ಗಳ ನಡುವೆ ಬದಲಿಸಲು, ನಿಮ್ಮ ಸ್ಕ್ರೀನ್‌ನ ಕೆಳಭಾಗದಿಂದ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ, ಹಿಡಿದಿಟ್ಟುಕೊಳ್ಳಿ, ನಂತರ ಬಿಟ್ಟುಬಿಡಿ."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"ಆ್ಯಪ್‌ಗಳ ನಡುವೆ ಬದಲಿಸಲು, 2 ಬೆರಳುಗಳಿಂದ ನಿಮ್ಮ ಸ್ಕ್ರೀನ್‌ನ ಕೆಳಭಾಗದಿಂದ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ, ಹಿಡಿದಿಟ್ಟುಕೊಳ್ಳಿ, ನಂತರ ಬಿಟ್ಟುಬಿಡಿ."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"ಆ್ಯಪ್‌ಗಳನ್ನು ಬದಲಿಸಿ"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"ನಿಮ್ಮ ಸ್ಕ್ರೀನ್‌ನ ಕೆಳಭಾಗದಿಂದ ಮೇಲಕ್ಕೆ ಸ್ವೈಪ್ ಮಾಡಿ, ಹೋಲ್ಡ್ ಮಾಡಿ, ನಂತರ ಬಿಟ್ಟುಬಿಡಿ"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"ಭೇಷ್!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ಟಾಸ್ಕ್‌ಬಾರ್ ತೋರಿಸಲಾಗಿದೆ"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ಟಾಸ್ಕ್‌ಬಾರ್ &amp; ಬಬಲ್ಸ್ ತೋರಿಸಲಾಗಿದೆ"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ಟಾಸ್ಕ್‌ಬಾರ್ &amp; ಬಬಲ್ಸ್ ಬಲಭಾಗದಲ್ಲಿ ತೋರಿಸಲಾಗಿದೆ"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ಟಾಸ್ಕ್‌ಬಾರ್ ಮರೆಮಾಡಲಾಗಿದೆ"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ಟಾಸ್ಕ್‌ಬಾರ್ &amp; ಬಬಲ್ಸ್ ಮರೆಮಾಡಲಾಗಿದೆ"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"ನ್ಯಾವಿಗೇಷನ್ ಬಾರ್"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"ಯಾವಾಗಲೂ ಟಾಸ್ಕ್‌ಬಾರ್ ತೋರಿಸಿ"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"ನ್ಯಾವಿಗೇಶನ್ ಮೋಡ್ ಬದಲಾಯಿಸಿ"</string>
@@ -143,6 +139,7 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ಟಾಸ್ಕ್ ಬಾರ್ ಓವರ್‌ಫ್ಲೋ"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ಮೇಲಿನ/ಎಡಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ಕೆಳಗಿನ/ಬಲಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
+    <string name="open_app_as_a_bubble" msgid="6642626287247807473">"ಆ್ಯಪ್ ಅನ್ನು ಬಬಲ್ ಆಗಿ ತೆರೆಯಿರಿ"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ಹೆಚ್ಚಿನ ಆ್ಯಪ್‌}one{ಹೆಚ್ಚಿನ ಆ್ಯಪ್‌ಗಳು}other{ಹೆಚ್ಚಿನ ಆ್ಯಪ್‌ಗಳು}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"ಡೆಸ್ಕ್‌ಟಾಪ್"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ಮತ್ತು <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +153,9 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ಅನ್ನು ವಿಸ್ತೃತಗೊಳಿಸಿ"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ಅನ್ನು ಕುಗ್ಗಿಸಿ"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"ಹುಡುಕಲು ಒಂದು ಸರ್ಕಲ್ ರಚಿಸಿ"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ಆ್ಯಪ್ ಐಕಾನ್"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ಆ್ಯಪ್ ಶೀರ್ಷಿಕೆ"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"ಮುಚ್ಚುವ ಬಟನ್"</string>
+    <string name="pin_to_taskbar" msgid="6607778046321626950">"ಟಾಸ್ಕ್‌ಬಾರ್‌ಗೆ ಪಿನ್ ಮಾಡಿ"</string>
+    <string name="unpin_from_taskbar" msgid="2178811773165572676">"ಟಾಸ್ಕ್‌ಬಾರ್‌ನಿಂದ ಅನ್‌ಪಿನ್"</string>
 </resources>
diff --git a/quickstep/res/values-ko/strings.xml b/quickstep/res/values-ko/strings.xml
index d602482..e5e5359 100644
--- a/quickstep/res/values-ko/strings.xml
+++ b/quickstep/res/values-ko/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"자유 형식"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"데스크톱"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"외부 디스플레이로 이동"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"닫기"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"데스크톱"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"최근 항목이 없습니다."</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"앱 사용 설정"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"모두 삭제"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"최근 앱"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"작업 종료됨"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1분"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"오늘 <xliff:g id="TIME">%1$s</xliff:g> 남음"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"앱 제안이 사용 설정됨"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"앱 제안이 사용 중지됨"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"예상 앱: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"동작 탐색 튜토리얼"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"기기를 회전시키세요"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"동작 탐색 튜토리얼을 완료하려면 기기를 회전시키세요."</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"오른쪽 또는 왼쪽 가장자리 끝에서 스와이프하세요."</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"돌아가기 동작의 민감도를 변경하려면 설정으로 이동하세요"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"스와이프하여 돌아가기"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"이전 화면으로 돌아가려면 왼쪽 또는 오른쪽 가장자리에서 화면 중앙으로 스와이프하세요."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"마지막 화면으로 돌아가려면 두 손가락을 사용해 왼쪽 또는 오른쪽 가장자리에서 화면 중앙으로 스와이프하세요"</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"뒤로"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"왼쪽 또는 오른쪽 가장자리에서 화면 중앙으로 스와이프하세요."</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"화면 하단 가장자리에서 위로 스와이프하세요."</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"홈으로 이동 동작을 완료했습니다."</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"스와이프하여 홈으로 이동"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"화면 하단에서 위로 스와이프합니다. 이 동작을 사용하면 언제든지 홈 화면으로 이동할 수 있습니다."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"두 손가락을 사용해 화면 하단에서 위로 스와이프하세요. 이 동작을 사용하면 언제든지 홈 화면으로 이동할 수 있습니다"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"홈으로 이동"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"화면 하단에서 위로 스와이프하세요."</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"아주 좋습니다"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"앱 전환 동작을 완료했습니다."</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"스와이프하여 앱 전환"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"앱 간에 전환하려면 화면 하단에서 위로 스와이프하고 잠시 멈춘 다음 손가락을 떼세요."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"앱 간에 전환하려면 두 손가락을 사용해 화면 하단에서 위로 스와이프하고 잠시 멈춘 다음 손가락을 떼세요"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"앱 전환"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"화면 하단에서 위로 스와이프하고 잠시 멈춘 다음 손가락을 떼세요."</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"잘하셨습니다"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"태스크 바 표시"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"태스크 바와 대화창을 왼쪽에 표시"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"태스크 바와 대화창을 오른쪽에 표시"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"태스크 바 숨김"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"태스크 바 및 대화창 숨김"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"탐색 메뉴"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"태스크 바 항상 표시"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"탐색 모드 변경"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"태스크 바 오버플로"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"상단/왼쪽으로 이동"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"하단/오른쪽으로 이동"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{추가 앱}other{추가 앱}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"데스크톱"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> 및 <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> 펼치기"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> 접기"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"서클 투 서치"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"앱 아이콘"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"앱 제목"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"닫기 버튼"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index e5fed79..d23ee7e 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Эркин форма режими"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Тышкы экранга жылдыруу"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Жабуу"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Компьютер"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Акыркы колдонмолор жок"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Колдонмону пайдалануу параметрлери"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Баарын тазалоо"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Акыркы колдонмолор"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Тапшырма жабылды"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 мүнөт"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Бүгүн <xliff:g id="TIME">%1$s</xliff:g> мүнөт калды"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Сунушталган колдонмолор функциясы иштетилди"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Сунушталган колдонмолор функциясы өчүрүлгөн"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Божомолдонгон колдонмо: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Жаңсап чабыттоо боюнча үйрөткүч"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Түзмөгүңүздү буруңуз"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Жаңсап чабыттоо үйрөткүчүнүн аягына чыгуу үчүн түзмөктү буруңуз"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Экранды эң четинен оңдон солго же солдон оңго карай сүрүңүз"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"\"Артка\" жаң-нун сезгичтигин өзгөртүү үчүн параметрлерге өтүңүз"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Артка кайтуу үчүн сүрүңүз"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Акыркы экранга кайтуу үчүн экранды сол же оң жагынан ортосуна карай сүрүңүз."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Акыркы экранга кайтуу үчүн экранды сол же оң жагынан ортосуна карай 2 манжаңыз менен сүрүңүз."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Артка кайтуу"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Экранды сол же оң жагынан ортосуна карай сүрүңүз"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Экранды ылдыйдан өйдө сүрүңүз"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"\"Башкы бетке өтүү\" жаңсоосун үйрөндүңүз"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Башкы бетке өтүү үчүн сүрүп коюңуз"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Экранды ылдый жагынан өйдө сүрүңүз. Бул жаңсоо сизди ар дайым Башкы экранга алып барат."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Экранды ылдый жагынан өйдө 2 манжаңыз менен сүрүңүз. Бул жаңсоо ар дайым Башкы экранга алып барат."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Башкы бетке өтүү"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Экранды төмөндөн жогору карай сүрүңүз"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Азаматсыз!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"\"Колдонмолорду которуштуруу\" жаңсоосун үйрөндүңүз"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Башка колдонмого которулуу үчүн сүрүңүз"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Бир колдонмодон экинчисине өтүү үчүн экранды ылдыйдан өйдө карай сүрүп, бир аз коё бербей туруңуз."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Бир колдонмодон экинчисине өтүү үчүн экранды 2 манжа менен ылдыйдан өйдө сүрүп, коё бербей туруңуз."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Башка колдонмого которулуу"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Экранды төмөндөн жогору карай сүрүңүз да, бир аз коё бербей кармап туруңуз"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Эң жакшы!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Тапшырмалар панели көрсөтүлдү"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Тапшырмалар панели, калкыма билдирмелер сол жакта көрсөтүлдү"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Тапшырмалар панели, калкыма билдирмелер оң жакта көрсөтүлгөн"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Тапшырмалар панели жашырылды"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Тапшырмалар панели жана калкып чыкма билдирмелер жашырылган"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Чабыттоо тилкеси"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Такта ар дайым көрүнсүн"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Өтүү режимин өзгөртүү"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"\"Тапшырмалар панели\" кошумча менюсу"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Жогорку/сол бурчка жылдыруу"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Төмөнкү/оң бурчка жылдыруу"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{колдонмо бар}other{колдонмо бар}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Компьютер"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> жана <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> жайып көрсөтүү"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> жыйыштыруу"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Тегеректеп издөө"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Колдонмонун сүрөтчөсү"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Колдонмонун аталышы"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Жабуу баскычы"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-lo/strings.xml b/quickstep/res/values-lo/strings.xml
index 2df1a49..8407311 100644
--- a/quickstep/res/values-lo/strings.xml
+++ b/quickstep/res/values-lo/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"ຮູບແບບອິດສະຫລະ"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ເດັສທັອບ"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"ຍ້າຍໄປຫາຈໍສະແດງຜົນພາຍນອກ"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"ປິດ"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ເດັສທັອບ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ບໍ່ມີລາຍການຫຼ້າສຸດ"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ການຕັ້ງຄ່າການນຳໃຊ້ແອັບ"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ລຶບລ້າງທັງໝົດ"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ແອັບຫຼ້າສຸດ"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"ປິດໜ້າວຽກແລ້ວ"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 ນາທີ"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"ເຫຼືອ <xliff:g id="TIME">%1$s</xliff:g> ມື້ນີ້"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ເປີດການນຳໃຊ້ການແນະນຳແອັບແລ້ວ"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ປິດການນຳໃຊ້ການແນະນຳແອັບແລ້ວ"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ແອັບທີ່ຄາດເດົາໄວ້: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"ສອນການນຳໃຊ້ການນຳທາງແບບທ່າທາງ"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"ໝຸນອຸປະກອນຂອງທ່ານ"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ກະລຸນາໝຸນອຸປະກອນຂອງທ່ານເພື່ອເຮັດຕາມການສອນການນຳໃຊ້ກ່ຽວກັບການນຳທາງແບບທ່າທາງໃຫ້ສຳເລັດ"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ກະລຸນາກວດສອບວ່າທ່ານປັດຈາກຂອບຂວາສຸດ ຫຼື ຊ້າຍສຸດ"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"ເພື່ອປ່ຽນຄວາມລະອຽດອ່ອນຂອງທ່າທາງກັບຄືນ, ໃຫ້ໄປຫາການຕັ້ງຄ່າ"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"ປັດເພື່ອກັບຄືນ"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"ເພື່ອກັບໄປໜ້າຈໍຫຼ້າສຸດ, ໃຫ້ປັດຈາກຂອບຊ້າຍ ຫຼື ຂວາໄປຫາທາງກາງຂອງໜ້າຈໍ."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"ເພື່ອກັບໄປໜ້າຈໍຫຼ້າສຸດ, ໃຫ້ປັດດ້ວຍ 2 ນິ້ວຈາກຂອບຊ້າຍ ຫຼື ຂວາໄປຫາທາງກາງຂອງໜ້າຈໍ."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"ກັບຄືນ"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"ປັດຈາກຂອບຊ້າຍ ຫຼື ຂວາໄປຫາກາງໜ້າຈໍ"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"ກະລຸນາກວດສອບວ່າທ່ານປັດຂຶ້ນຈາກຂອບລຸ່ມສຸດຂອງໜ້າຈໍ"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"ທ່ານໃຊ້ທ່າທາງໄປໜ້າຫຼັກສຳເລັດແລ້ວ"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"ປັດເພື່ອໄປໜ້າຫຼັກ"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"ປັດຂຶ້ນມາຈາກລຸ່ມສຸດຂອງໜ້າຈໍທ່ານ. ທ່າທາງນີ້ຈະພາທ່ານໄປໂຮມສະກຣີນສະເໝີ."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"ປັດຂຶ້ນດ້ວຍ 2 ນິ້ວຈາກລຸ່ມສຸດຂອງໜ້າຈໍ. ທ່າທາງນີ້ຈະພາທ່ານໄປໂຮມສະກຣີນສະເໝີ."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"ໄປໜ້າຫຼັກ"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"ປັດຂຶ້ນຈາກລຸ່ມສຸດຂອງໜ້າຈໍທ່ານ"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"ດີຫຼາຍ!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"ທ່ານໃຊ້ທ່າທາງສະຫຼັບແອັບສຳເລັດແລ້ວ"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"ປັດເພື່ອສະຫຼັບແອັບ"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"ເພື່ອສະຫຼັບລະຫວ່າງແອັບ, ໃຫ້ປັດຂຶ້ນຈາກລຸ່ມສຸດຂອງໜ້າຈໍທ່ານ, ກົດຄ້າງໄວ້, ຈາກນັ້ນປ່ອຍ."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"ເພື່ອສະຫຼັບລະຫວ່າງແອັບ, ໃຫ້ປັດຂຶ້ນດ້ວຍ 2 ນິ້ວຈາກລຸ່ມສຸດຂອງໜ້າຈໍທ່ານ, ກົດຄ້າງໄວ້ແລ້ວປ່ອຍ."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"ສະຫຼັບແອັບ"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"ປັດຂຶ້ນຈາກລຸ່ມສຸດຂອງໜ້າຈໍຂອງທ່ານຄ້າງໄວ້ແລ້ວປ່ອຍ"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"ດີຫຼາຍ!"</string>
@@ -92,7 +90,7 @@
     <string name="allset_title" msgid="5021126669778966707">"ຮຽບຮ້ອຍໝົດແລ້ວ!"</string>
     <string name="allset_hint" msgid="459504134589971527">"ປັດຂຶ້ນເພື່ອໄປຫາໜ້າຫຼັກ"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"ແຕະປຸ່ມໜ້າທຳອິດເພື່ອໄປຫາໂຮມສະກຣີນຂອງທ່ານ"</string>
-    <string name="allset_description_generic" msgid="5385500062202019855">"ທ່ານເລີ່ມໃຊ້ແທັບເລັດ <xliff:g id="DEVICE">%1$s</xliff:g> ຂອງທ່ານໄດ້ແລ້ວ"</string>
+    <string name="allset_description_generic" msgid="5385500062202019855">"ທ່ານເລີ່ມໃຊ້<xliff:g id="DEVICE">%1$s</xliff:g>ຂອງທ່ານໄດ້ແລ້ວ"</string>
     <string name="default_device_name" msgid="6660656727127422487">"ອຸປະກອນ"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"ການຕັ້ງຄ່າການນຳທາງລະບົບ"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"ແບ່ງປັນ"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ແຖບໜ້າວຽກທີ່ສະແດງຢູ່"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ແຖບໜ້າວຽກ ແລະ ຟອງສະແດງຢູ່ເບື້ອງຊ້າຍ"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ແຖບໜ້າວຽກ ແລະ ຟອງສະແດງຢູ່ເບື້ອງຂວາ"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ແຖບໜ້າວຽກທີ່ເຊື່ອງໄວ້ຢູ່"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ແຖບໜ້າວຽກ ແລະ ຟອງຖືກເຊື່ອງໄວ້"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"ແຖບການນຳທາງ"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"ສະແດງແຖບໜ້າວຽກສະເໝີ"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"ປ່ຽນໂໝດການນຳທາງ"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ສ່ວນເພີ່ມເຕີມຂອງແຖບໜ້າວຽກ"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ຍ້າຍໄປຊ້າຍ/ເທິງ"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ຍ້າຍໄປຂວາ/ລຸ່ມ"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ແອັບເພີ່ມເຕີມ}other{ແອັບເພີ່ມເຕີມ}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"ເດັສທັອບ"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ແລະ <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"ຂະຫຍາຍ <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"ຫຍໍ້ <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ລົງ"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"ແຕ້ມວົງມົນເພື່ອຊອກຫາ"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ໄອຄອນແອັບ"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ຊື່ແອັບ"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"ປຸ່ມປິດ"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-lt/strings.xml b/quickstep/res/values-lt/strings.xml
index 1d29f57..268eeb7 100644
--- a/quickstep/res/values-lt/strings.xml
+++ b/quickstep/res/values-lt/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Laisva forma"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Stalinis kompiuteris"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Perkelkite į išorinį ekraną"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Uždaryti"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Stalinis kompiuteris"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nėra jokių naujausių elementų"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Programos naudojimo nustatymai"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Išvalyti viską"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Naujausios programos"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Užduotis uždaryta"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min."</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Šiandien liko: <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Siūlomų programų funkcija įgalinta"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Siūlomų programų funkcija išjungta"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Numatoma programa: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Naršymo gestais mokomoji medžiaga"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Pasukite įrenginį"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Pasukite įrenginį, kad pereitumėte į naršymo gestais mokomąją medžiagą"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Turite perbraukti nuo dešiniojo ar kairiojo krašto"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Norėd. pak. grįžimo gesto jautr., eikite į sk. „Nustatymai“"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Norėdami grįžti, perbraukite"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Norėdami grįžti į ankstesnį ekraną, perbr. nuo kairiojo arba dešinio krašto link ekrano vidurio."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Jei norite grįžti į ankstesnį ekraną, perbraukite dviem pirštais nuo kairiojo arba dešiniojo krašto link ekrano vidurio."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Grįžti"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Perbraukite nuo kairiojo arba dešiniojo krašto link ekrano vidurio"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Turite perbraukti aukštyn nuo apatinio ekrano krašto"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Atlikote perėjimo į pagrindinį ekraną gestą"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Perbraukite, kad pereitumėte į pagrindinį ekraną"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Perbraukite aukštyn nuo ekrano apačios. Atlikus šį gestą, visada nukreipiama į pagrindinį ekraną."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Perbraukite dviem pirštais nuo ekrano apačios. Šis gestas visada nukreipia į pagrindinį ekraną."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Eiti į pagrindinį ekraną"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Perbraukite aukštyn nuo ekrano apačios"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Puiku!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Atlikote programų perjungimo gestą"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Perbraukite, kad perjungtumėte programas"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Norėdami perjungti programas, perbraukite aukštyn nuo ekrano apačios, palaikykite ir paleiskite."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Perjunkite programas perbraukę dviem pirštais aukštyn nuo ekrano apačios, palaikę ir paleidę."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Perjungti programas"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Perbraukite aukštyn nuo ekrano apačios, palaikykite ir paleiskite"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Puiku!"</string>
@@ -92,7 +90,7 @@
     <string name="allset_title" msgid="5021126669778966707">"Paruošta!"</string>
     <string name="allset_hint" msgid="459504134589971527">"Perbraukite aukštyn, kad grįžtumėte į pagrindinį ekraną"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Norėdami eiti į pagrindinį ekraną, palieskite pagrindinio ekrano mygtuką"</string>
-    <string name="allset_description_generic" msgid="5385500062202019855">"Esate pasirengę pradėti naudoti <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
+    <string name="allset_description_generic" msgid="5385500062202019855">"Jau parengta naudoti: <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="default_device_name" msgid="6660656727127422487">"įrenginys"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Sistemos naršymo nustatymai"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"Bendrinti"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Užduočių juosta rodoma"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Užduočių juosta ir burbulai rodomi kairėje"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Užduočių juosta ir burbulai rodomi dešinėje"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Užduočių juosta paslėpta"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Užduočių juosta ir burbulai paslėpti"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Naršymo juosta"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Visada rodyti užduočių juostą"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Keisti naršymo režimą"</string>
@@ -143,6 +139,7 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Užduočių juostos perpildymas"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Perkelti aukštyn, kairėn"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Perkelti žemyn, dešinėn"</string>
+    <string name="open_app_as_a_bubble" msgid="6642626287247807473">"Atidaryti programą kaip burbulą"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{papildoma programa}one{papildoma programa}few{papildomos programos}many{papildomos programos}other{papildomų programų}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Stalinis kompiuteris"</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>
@@ -156,4 +153,9 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"išskleisti „<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>“"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"sutraukti „<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>“"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Paieška apibrėžiant"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Programos piktograma"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Programos pavadinimas"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Mygtukas „Uždaryti“"</string>
+    <string name="pin_to_taskbar" msgid="6607778046321626950">"Priseg. prie užd. j."</string>
+    <string name="unpin_from_taskbar" msgid="2178811773165572676">"Atsegti nuo užd. j."</string>
 </resources>
diff --git a/quickstep/res/values-lv/strings.xml b/quickstep/res/values-lv/strings.xml
index 805a598..ad83c24 100644
--- a/quickstep/res/values-lv/strings.xml
+++ b/quickstep/res/values-lv/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Brīva forma"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Dators"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Pārvietošana uz ārējo displeju"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Aizvērt"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Darbvirsma"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nav nesenu vienumu."</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Lietotņu izmantošanas iestatījumi"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Notīrīt visu"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Pēdējās izmantotās lietotnes"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Uzdevums ir aizvērts"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt;1 minūte"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Šodien atlicis: <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Ieteicamās lietotnes ir iespējotas"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Ieteicamās lietotnes ir atspējotas"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Prognozētā lietotne: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Žestu navigācijas pamācība"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Pagrieziet ierīci"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Lūdzu, pagrieziet savu ierīci, lai pabeigtu žestu navigācijas apmācību."</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Jāvelk no pašas labās vai kreisās malas."</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Atgriešanās žesta jutīguma līmeni varat mainīt iestatījumos."</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Vilkšana, lai atgrieztos"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Lai atgrieztos iepriekšējā ekrānā, velciet no kreisās vai labās malas uz ekrāna vidu."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Lai pārietu uz iepriekšējo ekrānu, ar diviem pirkstiem velciet no kreisās vai labās malas uz ekrāna vidu."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Atpakaļ"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Velciet no ekrāna kreisās vai labās malas uz vidu."</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Jāvelk augšup no ekrāna apakšmalas."</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Jūs sekmīgi veicāt sākuma ekrāna atvēršanas žestu."</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Vilkšana, lai pārietu uz sākumu"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Velciet augšup no ekrāna apakšdaļas. Ar šo žestu vienmēr varat atvērt sākuma ekrānu."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Ar 2 pirkstiem velciet augšup no ekrāna apakšdaļas. Ar šo žestu vienmēr varat atvērt sākuma ekrānu."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Doties uz sākuma ekrānu"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Velciet augšup no ekrāna apakšdaļas."</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Lieliski!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Jūs sekmīgi veicāt lietotņu pārslēgšanas žestu."</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Vilkšana, lai pārslēgtu lietotnes"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Lai pārslēgtu lietotnes, velciet augšup no ekrāna apakšdaļas, turiet un pēc tam atlaidiet."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Lai pārslēgtu lietotnes, ar 2 pirkstiem velciet no ekrāna apakšdaļas, turiet un pēc tam atlaidiet."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Lietotņu pārslēgšana"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Velciet augšup no ekrāna apakšdaļas, turiet un pēc tam atlaidiet."</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Ļoti labi!"</string>
@@ -92,7 +90,7 @@
     <string name="allset_title" msgid="5021126669778966707">"Gatavs!"</string>
     <string name="allset_hint" msgid="459504134589971527">"Velciet augšup, lai pārietu uz sākuma ekrānu"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Pieskarieties pogai Sākums, lai dotos uz sākuma ekrānu"</string>
-    <string name="allset_description_generic" msgid="5385500062202019855">"Varat sākt izmantot savu ierīci (<xliff:g id="DEVICE">%1$s</xliff:g>)"</string>
+    <string name="allset_description_generic" msgid="5385500062202019855">"Jūsu <xliff:g id="DEVICE">%1$s</xliff:g> ir gatava lietošanai"</string>
     <string name="default_device_name" msgid="6660656727127422487">"ierīce"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Sistēmas navigācijas iestatījumi"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"Kopīgot"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Uzdevumu josla tiek rādīta"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Uzdevumu josla/burbuļi pa kreisi"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Uzdevumu josla/burbuļi pa labi"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Uzdevumu josla paslēpta"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Uzdevumu josla/burbuļi paslēpti"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigācijas josla"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Vienmēr rādīt uzdevumu joslu"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Mainīt navigācijas režīmu"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Uzdevumu joslas pārpilde"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Pārvietot uz augšējo/kreiso stūri"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Pārvietot uz apakšējo/labo stūri"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{papildu lietotne}zero{papildu lietotņu}one{papildu lietotne}other{papildu lietotnes}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Darbvirsma"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"izvērst “<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>”"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"sakļaut “<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>”"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Apvilkt un meklēt"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Lietotnes ikona"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Lietotnes nosaukums"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Poga Aizvērt"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-mk/strings.xml b/quickstep/res/values-mk/strings.xml
index 2634b94..96edee6 100644
--- a/quickstep/res/values-mk/strings.xml
+++ b/quickstep/res/values-mk/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Работна површина"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Префрлете се на надворешниот екран"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Затвори"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"За компјутер"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Нема неодамнешни ставки"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Поставки за користење на апликациите"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Избриши ги сите"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Неодамнешни апликации"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Задачата е затворена"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 минута"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Уште <xliff:g id="TIME">%1$s</xliff:g> за денес"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Предлозите за апликации се овозможени"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Предлозите за апликации се оневозможени"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Предвидена апликација: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Упатство за навигација со движења"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Ротирајте го уредот"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Ротирајте го уредот за да го завршите упатството за навигација со движење"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Повлечете од крајниот десен или крајниот лев раб"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"За да ја промените чувствителноста, одете во „Поставки“"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Повлечете за да се вратите назад"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"За да се вратите на последниот екран, повлечете од левиот или десниот раб кон средината на екранот."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"За да се вратите на последниот екран на кој бевте, повлечете со два прста од левиот или десниот раб кон средината на екранот."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Враќање назад"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Повлечете од левиот или десниот раб кон средината на екранот"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Повлечете нагоре од долниот раб на екранот"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Го научивте движењето за враќање на почетниот екран"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Повлечете за да одите на почетниот екран"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Повлечете нагоре од долниот раб на екранот. Ова движење секогаш ќе ве одведе на почетниот екран."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Повлечете нагоре со два прста од долниот раб на екранот. Движењево секогаш ве носи на почетниот екран."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Отворање на почетниот екран"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Повлечете нагоре од дното на екранот"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Одлично сторено!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Го научивте движењето за префрлање помеѓу апликации"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Повлечете за префрлање помеѓу апликации"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"За да смените апликација, повлечете нагоре од дното на екранот и задржете, па пуштете."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"За да се префрлате помеѓу апликации, повлечете нагоре со два прста од долниот раб на екранот, задржете и потоа отпуштете."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Променете ја апликацијата"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Повлечете нагоре од дното на екранот и задржете, па пуштете"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Браво!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Лентата со задачи е прикажана"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Лен. со зад. и бал. се лево"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Лен. со зад. и бал. се десно"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Лентата со задачи е скриена"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Лен. со зад. и бал. се скриени"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Лента за навигација"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Секогаш прикажувај „Лента“"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Променете режим на навигација"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Проширено балонче на „Лента со задачи“"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Премести горе лево"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Премести долу десно"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{дополнителна апликација}one{дополнителна апликација}other{дополнителни апликации}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Режим за компјутер"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> и <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"прошири <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"собери <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Пребарување со заокружување"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Икона за апликацијата"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Наслов на апликацијата"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Копче за затворање"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ml/strings.xml b/quickstep/res/values-ml/strings.xml
index 92cad89..628acd1 100644
--- a/quickstep/res/values-ml/strings.xml
+++ b/quickstep/res/values-ml/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"ഫ്രീഫോം"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ഡെസ്‌ക്ടോപ്പ്"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"ബാഹ്യ ഡിസ്‌പ്ലേയിലേക്ക് നീക്കുക"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"അടയ്‌ക്കുക"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ഡെസ്‌ക്ടോപ്പ്"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"സമീപകാല ഇനങ്ങൾ ഒന്നുമില്ല"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ആപ്പ് ഉപയോഗ ക്രമീകരണം"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"എല്ലാം മായ്‌ക്കുക"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"സമീപകാല ആപ്പുകൾ"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"ടാസ്ക്ക് അടച്ചു"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 മിനിറ്റ്"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"ഇന്ന് <xliff:g id="TIME">%1$s</xliff:g> ശേഷിക്കുന്നു"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ആപ്പ് നിർദ്ദേശങ്ങൾ പ്രവർത്തനക്ഷമമാക്കി"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ആപ്പ് നിർദ്ദേശങ്ങൾ പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"പ്രവചിച്ച ആപ്പ്: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"ജെസ്ച്ചർ നാവിഗേഷൻ ട്യൂട്ടോറിയൽ"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"നിങ്ങളുടെ ഉപകരണം റൊട്ടേറ്റ് ചെയ്യുക"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ജെസ്ച്ചർ നാവിഗേഷൻ ട്യൂട്ടോറിയൽ പൂർത്തിയാക്കാൻ നിങ്ങളുടെ ഉപകരണം റൊട്ടേറ്റ് ചെയ്യുക"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"വലത്തേയറ്റത്തെയോ ഇടത്തേയറ്റത്തെയോ അരികിൽ നിന്നാണ് സ്വെെപ്പ് ചെയ്യുന്നതെന്ന് ഉറപ്പാക്കുക"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"ബാക്ക്ജെസ്റ്ററിന്റെ സെൻസിറ്റിവിറ്റി മാറ്റാൻ ക്രമീകരണത്തിൽ പോകൂ"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"മടങ്ങാൻ സ്വെെപ്പ് ചെയ്യുക"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"മുൻ സ്‌ക്രീനിലേക്ക് മടങ്ങാൻ, ഇടതോ വലതോ അരികിൽ നിന്ന് സ്‌ക്രീനിന്റെ നടുവിലേക്ക് സ്വെെപ്പ് ചെയ്യുക."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"മുമ്പത്തെ സ്ക്രീനിലേക്ക് മടങ്ങാൻ, 2 വിരലുകൾ ഉപയോഗിച്ച് ഇടത് അല്ലെങ്കിൽ വലത് മൂലയിൽ നിന്ന് സ്ക്രീനിന്റെ മധ്യഭാഗത്തേക്ക് സ്വൈപ്പ് ചെയ്യുക."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"മടങ്ങുക"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"ഇടതോ വലതോ അരികിൽ നിന്ന് സ്ക്രീനിന്റെ മധ്യഭാഗത്തേക്ക് സ്വൈപ്പ് ചെയ്യുക"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"സ്‌ക്രീനിന്റെ താഴത്തെ അരികിൽ നിന്ന് മുകളിലേക്കാണ് സ്വെെപ്പ് ചെയ്യുന്നതെന്ന് ഉറപ്പാക്കുക"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"ഹോം ജെസ്ച്ചർ നിങ്ങൾ പൂർത്തിയാക്കി"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"ഹോമിലേക്ക് പോകാൻ സ്വെെപ്പ് ചെയ്യുക"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"സ്‌ക്രീനിന്റെ താഴെ നിന്ന് മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്യൂ. ഈ ജെസ്ച്ചർ എപ്പോഴും ഹോം സ്‌ക്രീനിലേക്ക് നയിക്കുന്നു."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"സ്ക്രീനിന്റെ താഴെ നിന്ന് മുകളിലേക്ക് 2 വിരലുകൾ കൊണ്ട് സ്വൈപ്പ് ചെയ്യൂ. ഈ ജെസ്ച്ചർ എല്ലായ്‌പ്പോഴും ഹോം സ്ക്രീനിലേക്ക് നയിക്കുന്നു."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"ഹോമിലേക്ക് പോകൂ"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"സ്ക്രീനിന്റെ താഴെ നിന്ന് മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്യുക"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"കൊള്ളാം!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"ആപ്പുകൾ തമ്മിൽ മാറുക ജെസ്‌ച്ചർ നിങ്ങൾ പൂർത്തിയാക്കി"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"ആപ്പുകൾ മാറാൻ സ്വെെപ്പ് ചെയ്യുക"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"ആപ്പുകൾക്കിടയിൽ മാറാൻ സ്‌ക്രീനിന്റെ താഴെ നിന്ന് മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്‌ത് പിടിച്ച ശേഷം വിടുക."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"ആപ്പുകൾക്കിടയിൽ മാറാൻ സ്ക്രീനിന്റെ താഴെ നിന്ന് മുകളിലേക്ക് 2 വിരലുകൾ കൊണ്ട് സ്വൈപ്പ് ചെയ്ത് പിടിച്ച ശേഷം വിടുക."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"ആപ്പുകൾ മാറുക"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"നിങ്ങളുടെ സ്‌ക്രീനിന്റെ താഴെ നിന്ന് മുകളിലേക്ക് സ്വൈപ്പ് ചെയ്‌ത് പിടിച്ച ശേഷം, വിടുക"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"അഭിനന്ദനങ്ങൾ!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ടാസ്‌ക്ബാർ കാണിച്ചിരിക്കുന്നു"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ടാസ്ക്ബാറും ബബിളും ഇടതുവശത്ത്"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ടാസ്ക്ബാറും ബബിളും വലതുവശത്ത്"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ടാസ്‌ക്ബാർ മറച്ചിരിക്കുന്നു"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ടാസ്ക്ബാറും ബബിളും മറച്ചു"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"നാവിഗേഷൻ ബാർ"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"ടാസ്‌ക്ബാർ എപ്പോഴും കാണിക്കൂ"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"നാവിഗേഷൻ മോഡ് മാറ്റുക"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ടാസ്‌ക്ബാർ ഓവർഫ്ലോ"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"മുകളിലേക്കോ ഇടത്തേക്കോ നീക്കുക"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"താഴേക്കോ വലത്തേക്കോ നീക്കുക"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{കൂടുതൽ ആപ്പ്}other{കൂടുതൽ ആപ്പുകൾ}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"ഡെസ്‌ക്ടോപ്പ്"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g>, <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> വികസിപ്പിക്കുക"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ചുരുക്കുക"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"തിരയാൻ വട്ടം വരയ്ക്കൽ"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ആപ്പ് ഐക്കൺ"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ആപ്പിന്റെ പേര്"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"അടയ്ക്കുക ബട്ടൺ"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-mn/strings.xml b/quickstep/res/values-mn/strings.xml
index 539e104..ac3c40e 100644
--- a/quickstep/res/values-mn/strings.xml
+++ b/quickstep/res/values-mn/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Чөлөөтэй хувьсах"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Компьютер"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Гадаад дэлгэц рүү зөөх"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Хаах"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Дэлгэц"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Сүүлийн үеийн зүйл алга"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Апп ашиглалтын тохиргоо"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Бүгдийг арилгах"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Саяхны аппууд"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Ажлыг хаасан"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 минут"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Өнөөдөр <xliff:g id="TIME">%1$s</xliff:g> үлдсэн"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Санал болгож буй аппуудыг идэвхжүүлсэн"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Санал болгож буй аппуудыг идэвхгүй болгосон"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Таамаглаж буй апп: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Зангааны навигацын заавар"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Төхөөрөмжөө эргүүлэх"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Зангааны навигацын практик хичээлийг дуусгахын тулд төхөөрөмжөө эргүүлнэ үү"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Та баруун зах эсвэл зүүн захын ирмэгээс шударна уу"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Буцах зангааны мэдрэгшлийг өөрчлөх бол Тохиргоо руу очно уу"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Буцахын тулд шудрах"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Сүүлийн дэлгэц рүү буцахын тулд дэлгэцийн зүүн эсвэл баруун булангаас дунд хэсэг рүү шударна уу."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Сүүлийн дэлгэц рүү буцахын тулд 2 хуруугаараа дэлгэцийн голоос зүүн эсвэл баруун ирмэг рүү шударна уу."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Буцах"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Зүүн эсвэл баруун ирмэгээс дэлгэцийн дунд хэсэг хүртэл шударна уу"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Та дэлгэцийн доод ирмэгээс дээш шударна уу"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Та үндсэн нүүр лүү очих зангааг гүйцэтгэлээ."</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Нүүр лүү очихын тулд шудрах"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Дэлгэцийнхээ доороос дээш шударна уу. Энэ зангаа таныг тогтмол Үндсэн нүүрэнд аваачна."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"2 хуруугаараа дэлгэцийн доороос дээш шударна уу. Энэ зангаа таныг тогтмол Үндсэн нүүрэнд аваачна."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Үндсэн нүүр лүү очих"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Дэлгэцийнхээ доороос дээш шударна уу"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Үнэхээр сайн ажиллалаа!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Та аппуудыг сэлгэх зангааг гүйцэтгэлээ"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Аппуудыг сэлгэхийн тулд шудрах"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Аппуудын хооронд сэлгэхийн тулд дэлгэцийнхээ доод хэсгээс дээш шударч, удаан дараад, суллана уу."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Аппуудын хооронд сэлгэхийн тулд 2 хуруугаараа дэлгэцийнхээ доод хэсгээс дээш шударч, удаан дараад, суллана уу."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Аппуудыг сэлгэх"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Дэлгэцийнхээ доод хэсгээс дээш шударч, удаан дараад суллана уу"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Сайн байна!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Ажлын хэсгийг харуулсан"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Ажлын хэсэг, бөмбөлгийг зүүн талд харуулсан"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Ажлын хэсэг, бөмбөлгийг баруун талд харуулсан"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Ажлын хэсгийг нуусан"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Ажлын хэсэг, бөмбөлгийг нуусан"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Навигацын самбар"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Ажлын хэсгийг үргэлж харуулах"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Навигацын горимыг өөрчлөх"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Ажлын хэсгийн урт цэс"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Зүүн дээд хэсэг рүү зөөх"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Баруун доод хэсэг рүү зөөх"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{бусад апп}other{бусад апп}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Дэлгэц"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> болон <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>-г дэлгэх"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>-г хураах"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Тойруулж зураад хай"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Aппын дүрс тэмдэг"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Аппын нэр"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Хаах товч"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-mr/strings.xml b/quickstep/res/values-mr/strings.xml
index dd2003f..6f82343 100644
--- a/quickstep/res/values-mr/strings.xml
+++ b/quickstep/res/values-mr/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"फ्रीफॉर्म"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटॉप"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"बाह्य डिस्प्लेवर हलवा"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"बंद करा"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"डेस्कटॉप"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"कोणतेही अलीकडील आयटम नाहीत"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"अ‍ॅप वापर सेटिंग्ज"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"सर्व साफ करा"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"अलीकडील अ‍ॅप्स"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"टास्क बंद केली"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"१मिहून कमी"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"आज <xliff:g id="TIME">%1$s</xliff:g>शिल्लक आहे"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"अ‍ॅप सूचना सुरू केल्या आहेत"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"अ‍ॅप सूचना बंद केल्या आहेत"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"पूर्वानुमान केलेले अ‍ॅप: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"जेश्चर नेव्हिगेशन ट्यूटोरियल"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"तुमचे डिव्हाइस फिरवा"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"कृपया जेश्चर नेव्हिगेशन ट्यूटोरियल पूर्ण करण्यासाठी तुमचे डिव्हाइस फिरवा"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"तुम्ही स्क्रीनच्या अगदी उजव्या किंवा अगदी डाव्या कडेपासून स्‍वाइप करत आहात खात्री करा"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"बॅक जेश्चरची संवेदनशीलता बदलण्यासाठी, सेटिंग्ज वर जा"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"परत जाण्यासाठी स्वाइप करा"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"मागील स्क्रीनवर परत जाण्यासाठी, स्क्रीनच्या डाव्या किंवा उजव्या कडेपासून मध्यावर स्‍वाइप करा."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"मागील स्क्रीनवर परत जाण्यासाठी, दोन बोटांनी डाव्या किंवा उजव्या कडेपासून स्क्रीनच्या मध्यभागी स्वाइप करा."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"मागे जा"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"डाव्या किंवा उजव्या कडेपासून स्क्रीनच्या मध्यभागी स्वाइप करा"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"तुम्ही स्क्रीनच्या तळाच्या कडेपासून वर स्वाइप करत आहात याची खात्री करा"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"तुम्ही गो होम जेश्चर पूर्ण केले आहे"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"होमवर जाण्यासाठी स्‍वाइप करा"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"तुमच्या स्क्रीनच्या तळाकडून वर स्वाइप करा. हे जेश्चर तुम्हाला नेहमी होम स्क्रीनवर घेऊन जाते."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"स्क्रीनच्या तळापासून दोन बोटांनी वर स्वाइप करा. हे जेश्चर तुम्हाला नेहमी होम स्क्रीनवर घेऊन जाते."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"होमवर जा"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"तुमच्या स्क्रीनच्या तळापासून वर स्‍वाइप करा"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"उत्तम कामगिरी!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"तुम्ही स्विच ॲप्स जेश्चर पूर्ण केले आहे"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"अ‍ॅप्स स्विच करण्यासाठी स्वाइप करा"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"ॲप्सदरम्यान स्विच करण्यासाठी, स्क्रीनच्या तळापासून वर स्वाइप करा, धरून ठेवा, त्यानंतर सोडून द्या."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"ॲप्सदरम्यान स्विच करण्यासाठी, स्क्रीनच्या तळापासून दोन बोटांनी वर स्वाइप करा, धरून ठेवा नंतर सोडा."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"अ‍ॅप्स स्विच करा"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"तुमच्या स्क्रीनच्या तळापासून वर स्वाइप करा, धरून ठेवा, त्यानंतर सोडा"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"छान!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"टास्कबार दाखवलेला आहे"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"टास्कबार आणि डावे बबल दाखवले"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"टास्कबार आणि उजवे बबल लपवले"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"टास्कबार लपवलेले आहे"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"टास्कबार आणि बबल लपवले आहेत"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"नेव्हिगेशन बार"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"नेहमी टास्कबार दाखवा"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"नेव्हिगेशन मोड बदला"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"टास्कबार ओव्हरफ्लो"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"सर्वात वरती/डावीकडे हलवा"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"तळाशी/उजवीकडे हलवा"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{आणखी अ‍ॅप}other{आणखी अ‍ॅप्स}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"डेस्कटॉप"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> आणि <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> चा विस्तार करा"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> कोलॅप्स करा"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"शोधण्यासाठी वर्तुळ करा"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"अ‍ॅपचा आयकन"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"अ‍ॅपचे शीर्षक"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"बंद करा बटण"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ms/strings.xml b/quickstep/res/values-ms/strings.xml
index af388bc..b85b62c 100644
--- a/quickstep/res/values-ms/strings.xml
+++ b/quickstep/res/values-ms/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Bentuk bebas"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Alihkan kepada paparan luaran"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Tutup"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Tiada item terbaharu"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Tetapan penggunaan apl"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Kosongkan semua"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Apl terbaharu"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Tugas Ditutup"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minit"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> lagi hari ini"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Cadangan apl didayakan"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Cadangan apl dilumpuhkan"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Apl yang diramalkan: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial Navigasi Gerak Isyarat"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Putarkan peranti anda"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Sila putarkan peranti anda untuk melengkapkan tutorial navigasi gerak isyarat"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pastikan anda meleret dari hujung sebelah kanan atau hujung sebelah kiri"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Utk mengubah kepekaan gerak isyarat undur, pergi ke Tetapan"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Leret untuk kembali"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Untuk kembali ke skrin terakhir, leret dari tepi sebelah kiri atau kanan ke tengah skrin."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Untuk kembali ke skrin terakhir, leret dengan 2 jari dari tepi kiri atau kanan ke tengah skrin."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Kembali"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Leret dari sisi kiri atau kanan ke bahagian tengah skrin"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Pastikan anda meleret ke atas dari sisi bahagian bawah skrin"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Anda telah melengkapkan gerak isyarat akses laman utama"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Leret untuk kembali ke laman utama"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Leret ke atas dari bahagian bawah skrin. Gerak isyarat ini sentiasa membawa anda ke Skrin utama."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Leret ke atas dengan 2 jari dari bawah skrin. Gerak isyarat ini sentiasa bawa anda ke Skrin utama."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Pergi ke skrin utama"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Leret ke atas dari bahagian bawah skrin"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Syabas!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Anda telah melengkapkan gerak isyarat menukar apl"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Leret untuk menukar apl"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Untuk beralih antara apl, leret ke atas dari bahagian bawah skrin anda, tahan, kemudian lepaskan."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Untuk beralih antara apl, leret ke atas dengan 2 jari dari bawah skrin, tahan, kemudian lepaskan."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Beralih antara apl"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Leret ke atas dari bahagian bawah skrin, tahan, kemudian lepas"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Syabas!"</string>
@@ -90,7 +88,7 @@
     <string name="gesture_tutorial_nice" msgid="2936275692616928280">"Bagus!"</string>
     <string name="gesture_tutorial_step" msgid="1279786122817620968">"Tutorial <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
     <string name="allset_title" msgid="5021126669778966707">"Siap!"</string>
-    <string name="allset_hint" msgid="459504134589971527">"Leret ke atas untuk ke laman utama"</string>
+    <string name="allset_hint" msgid="459504134589971527">"Leret ke atas untuk ke skrin utama"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Ketik butang skrin utama untuk pergi ke skrin utama anda"</string>
     <string name="allset_description_generic" msgid="5385500062202019855">"Anda sudah sedia untuk mula menggunakan <xliff:g id="DEVICE">%1$s</xliff:g> anda"</string>
     <string name="default_device_name" msgid="6660656727127422487">"peranti"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Bar Tugas dipaparkan"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Bar Tugas &amp; gelembung dipaparkan di sebelah kiri"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Bar Tugas &amp; gelembung dipaparkan di sebelah kanan"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Bar Tugas disembunyikan"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Bar Tugas &amp; gelembung disembunyikan"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Bar navigasi"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Papar Bar Tugas selalu"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Tukar mod navigasi"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Limpahan Bar Tugas"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Alihkan ke atas/kiri"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Alihkan ke bawah/kanan"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{apl lagi}other{apl lagi}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"kembangkan <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"kuncupkan <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Bulatkan untuk Membuat Carian"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikon apl"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Tajuk apl"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Butang tutup"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-my/strings.xml b/quickstep/res/values-my/strings.xml
index 55b65c8..467ef26 100644
--- a/quickstep/res/values-my/strings.xml
+++ b/quickstep/res/values-my/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"အလွတ်ပုံစံ"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ဒက်စ်တော့"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"ပြင်ပဖန်သားပြင်သို့ ရွှေ့ရန်"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"ပိတ်ရန်"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ဒက်စ်တော့"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"မကြာမီကဖွင့်ထားသည်များ မရှိပါ"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"အက်ပ်အသုံးပြုမှု ဆက်တင်များ"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"အားလုံးရှင်းရန်"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"မကြာသေးမီက အက်ပ်များ"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"လုပ်ဆောင်စရာ ပိတ်ထားသည်"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>၊ <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; ၁ မိနစ်"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"ယနေ့ <xliff:g id="TIME">%1$s</xliff:g> ခု ကျန်သည်"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"အက်ပ်အကြံပြုချက်များ ဖွင့်ထားသည်"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"အက်ပ်အကြံပြုချက်များကို ပိတ်ထားသည်"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ကြိုတင်မှန်းဆထားသော အက်ပ်− <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"လက်ဟန်ဖြင့် လမ်းညွှန်ခြင်း အသုံးပြုနည်း"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"သင့်စက်ကို လှည့်ပါ"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"လက်ဟန်ဖြင့် လမ်းညွှန်ခြင်း ရှင်းလင်းပို့ချချက်အား အပြီးသတ်ရန် သင့်စက်ကို လှည့်ပါ"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ညာ (သို့) ဘယ်ဘက်အစွန်ဆုံးမှ ပွတ်ဆွဲကြောင်း သေချာပါစေ"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"နောက်ဆုတ်လက်ဟန်၏ အာရုံခံစွမ်းကိုပြောင်းရန် ‘ဆက်တင်များ’ သို့ သွားပါ"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"နောက်ပြန်သွားရန် ပွတ်ဆွဲပါ"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"နောက်ဆုံးဖန်သားပြင်သို့ ပြန်သွားရန် ဘယ်ဘက် (သို့) ညာဘက်အစွန်မှ ဖန်သားပြင်အလယ်သို့ ပွတ်ဆွဲပါ။"</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"ပြီးခဲ့သည့်ဖန်သားပြင်သို့သွားရန် ဘယ်ဘက် (သို့) ညာဘက်အစွန်းမှ ဖန်သားပြင်အလယ်သို့ လက် ၂ ချောင်းဖြင့် ပွတ်ဆွဲပါ။"</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"နောက်သို့"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"ဖန်သားပြင်၏ ဘယ် (သို့) ညာအစွန်းမှ အလယ်သို့ ပွတ်ဆွဲပါ"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"ဖန်သားပြင် အောက်ခြေအစွန်းမှ အပေါ်သို့ ပွတ်ဆွဲကြောင်း သေချာပါစေ"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"ပင်မစာမျက်နှာသို့သွားသည့် လက်ဟန် အပြီးသတ်လိုက်ပါပြီ"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"ပင်မစာမျက်နှာသို့သွားရန် ပွတ်ဆွဲပါ"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"သင့်ဖန်သားပြင် အောက်ခြေမှ အပေါ်သို့ပွတ်ဆွဲပါ။ ဤလက်ဟန်ဖြင့် ပင်မစာမျက်နှာသို့ အမြဲပြန်သွားနိုင်သည်။"</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"ဖန်သားပြင်အောက်မှ အပေါ်သို့ လက် ၂ ချောင်းဖြင့် ပွတ်ဆွဲပါ။ ဤလက်ဟန်က ပင်မစာမျက်နှာသို့ အမြဲပို့ပေးမည်။"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"ပင်မစာမျက်နှာသွားရန်"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"သင့်ဖန်သားပြင် အောက်ခြေမှ အပေါ်သို့ ပွတ်ဆွဲပါ"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"တော်ပါပေသည်။"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"အက်ပ်များပြောင်းသည့် လက်ဟန် အပြီးသတ်လိုက်ပါပြီ"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"အက်ပ်များပြောင်းရန် ပွတ်ဆွဲပါ"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"အက်ပ်တစ်ခုမှတစ်ခုသို့ ပြောင်းရန် စခရင်အောက်ခြေမှ အပေါ်သို့ ပွတ်ဆွဲ၍ ဖိထားပြီးနောက် လွှတ်ပါ။"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"အက်ပ်များကြားပြောင်းရန် ဖန်သားပြင်အောက်မှ အပေါ်သို့ လက် ၂ ချောင်းဖြင့်ဆွဲ၍ ထိန်းထားပြီး လွှတ်ပါ။"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"အက်ပ်များ ကူးပြောင်းရန်"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"သင့်ဖန်သားပြင် အောက်ခြေမှ အပေါ်သို့ ပွတ်ဆွဲ၍ ဖိထားပြီးလွှတ်လိုက်ပါ"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"အလွန်ကောင်းပါသည်။"</string>
@@ -92,7 +90,7 @@
     <string name="allset_title" msgid="5021126669778966707">"အားလုံး အဆင်သင့်ပါ။"</string>
     <string name="allset_hint" msgid="459504134589971527">"ပင်မစာမျက်နှာသို့သွားရန် အပေါ်သို့ ပွတ်ဆွဲပါ"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"ပင်မစာမျက်နှာသို့ သွားရန် ပင်မခလုတ်ကို တို့ပါ"</string>
-    <string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> ကို စသုံးရန် အသင့်ဖြစ်ပါပြီ"</string>
+    <string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> စသုံးရန် အသင့်ဖြစ်ပါပြီ"</string>
     <string name="default_device_name" msgid="6660656727127422487">"စက်"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"စနစ် လမ်းညွှန် ဆက်တင်များ"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"မျှဝေရန်"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taskbar ပြထားသည်"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ဘယ်ဘက်၌ Taskbar နှင့် ပူဖောင်းကွက် ပြထားသည်"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ညာဘက်၌ Taskbar နှင့် ပူဖောင်းကွက် ပြထားသည်"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taskbar ဖျောက်ထားသည်"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taskbar နှင့် ပူဖောင်းကွက် ဖျောက်ထားသည်"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"လမ်းညွှန်ဘား"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Taskbar အမြဲပြရန်"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"ရွှေ့ကြည့်သည့်မုဒ် ပြောင်းရန်"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taskbar မီနူးအပို"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"အပေါ်/ဘယ်ဘက်သို့ ရွှေ့ရန်"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"အောက်ခြေ/ညာဘက်သို့ ရွှေ့ရန်"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{နောက်ထပ်အက်ပ်}other{နောက်ထပ်အက်ပ်များ}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"ဒက်စ်တော့"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> နှင့် <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ကို ပိုပြပါ"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ကို လျှော့ပြပါ"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"ရှာရန် ကွက်၍ဝိုင်းလိုက်ပါ"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"အက်ပ်သင်္ကေတ"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"အက်ပ်ခေါင်းစဉ်"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"အပိတ် ခလုတ်"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-nb/strings.xml b/quickstep/res/values-nb/strings.xml
index bf81994..2690a61 100644
--- a/quickstep/res/values-nb/strings.xml
+++ b/quickstep/res/values-nb/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Fritt format"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Skrivebord"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Flytt til ekstern skjerm"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Lukk"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Skrivebord"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Ingen nylige elementer"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Innstillinger for appbruk"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Fjern alt"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nylige apper"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Oppgaven er lukket"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minutt"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> gjenstår i dag"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Appforslag er på"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Appforslag er slått av"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Foreslått app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Veiledning for navigasjon med bevegelser"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Roter enheten"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Roter enheten for å fullføre veiledningen for navigasjon med bevegelser"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Sørg for at du sveiper fra kanten helt til høyre eller venstre"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Gå til Innstillinger for å endre tilbakebevegelsefølsomheten"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Sveip for å gå tilbake"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"For å gå tilbake til forrige skjerm, sveip fra venstre eller høyre kant til midten av skjermen."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"For å gå tilbake til den forrige skjermen, sveip med to fingre fra den venstre eller høyre kanten til midten av skjermen."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Gå tilbake"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Sveip fra venstre eller høyre kant til midten av skjermen"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Sørg for at du sveiper opp fra den nederste kanten av skjermen"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Du har fullført bevegelsen for å gå til startskjermen"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Sveip for å gå til startskjermen"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Sveip opp fra bunnen av skjermen. Denne bevegelsen tar deg alltid til startskjermen."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Sveip opp med to fingre fra bunnen av skjermen. Denne bevegelsen tar deg alltid til startskjermen."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Gå til startskjermen"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Sveip opp fra bunnen av skjermen"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Bra jobbet!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Du har fullført bevegelsen for å bytte app"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Sveip for å bytte app"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"For å bytte mellom apper, sveip opp fra bunnen av skjermen, hold, og slipp"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"For å bytte mellom apper, sveip opp med to fingre fra bunnen av skjermen, hold, og slipp."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Bytt app"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Sveip opp fra bunnen av skjermen, hold, og slipp"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Bra!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Oppgavelinjen vises"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Venstre oppgavelinje/boble vises"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Høyre oppgavelinje/bobler vises"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Oppgavelinjen er skjult"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Oppgavelinje og bobler skjult"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigasjonsrad"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Vis alltid oppgavelinjen"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Endre navigasjonsmodus"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Overflyt for oppgavelinjen"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Flytt til øverst/venstre"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Flytt til nederst/høyre"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{app til}other{apper til}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Skrivebord"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> og <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"vis <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"skjul <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Appikon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Apptittel"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Lukkeknapp"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ne/strings.xml b/quickstep/res/values-ne/strings.xml
index 0f374ba..d03fcce 100644
--- a/quickstep/res/values-ne/strings.xml
+++ b/quickstep/res/values-ne/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"फ्रिफर्म"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"डेस्कटप"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"सारेर बाह्य डिस्प्लेमा लैजानुहोस्"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"बन्द गर्नुहोस्"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"डेस्कटप"</string>
-    <string name="recents_empty_message" msgid="7040467240571714191">"हालसालैको कुनै पनि वस्तु छैन"</string>
+    <string name="recents_empty_message" msgid="7040467240571714191">"हालसालैको कुनै पनि सामग्री छैन"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"एपको उपयोगका सेटिङहरू"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"सबै मेटाउनुहोस्"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"हालसालैका एपहरू"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"कार्य बन्द गरियो"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; १ मिनेट"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"आज: <xliff:g id="TIME">%1$s</xliff:g> बाँकी"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"सिफारिस गरिएका एप देखाउने सुविधा अन गरिएको छ"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"सिफारिस गरिएका एपहरू देखाउने सुविधा असक्षम पारिएको छ"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"पूर्वानुमान गरिएको एप: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"जेस्चर नेभिगेसनसम्बन्धी ट्युटोरियल"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"आफ्नो डिभाइस रोटेट गर्नुहोस्"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"जेस्चर नेभिगेसनको ट्युटोरियल पूरा गर्न कृपया आफ्नो डिभाइस रोटेट गर्नुहोस्"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"स्क्रिनको सबैभन्दा दायाँ किनारा वा सबैभन्दा बायाँ किनाराबाट स्वाइप गर्नुहोस्"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"\'पछाडि\' नामक इसाराको संवेदनशीलता बदल्न सेटिङमा जानुहोस्"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"पछाडि जान स्वाइप गर्नुहोस्"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"यसअघिको स्क्रिनमा फर्कन स्क्रिनको बायाँ वा दायाँ किनाराबाट मध्य भागसम्म स्वाइप गर्नुहोस्।"</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"तपाईं यसअघि जुन स्क्रिनमा हुनुहुन्थ्यो त्यही स्क्रिनमा फर्कन चाहनुहुन्छ भने २ वटा औँला प्रयोग गरी स्क्रिनको बायाँ वा दायाँ किनाराबाट मध्य भागसम्म स्वाइप गर्नुहोस्।"</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"पछाडि जाने तरिका"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"स्क्रिनको बायाँ वा दायाँ किनाराबाट मध्य भागसम्म स्वाइप गर्नुहोस्"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"स्क्रिनको फेदबाट माथितिर स्वाइप गर्नुहोस्"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"तपाईंले \"होम स्क्रिनमा जानुहोस्\" नामक इसारा प्रयोग गर्ने तरिका सिक्नुभयो"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"होम स्क्रिनमा जान स्वाइप गर्नुहोस्"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"स्क्रिनको फेदबाट माथितिर स्वाइप गर्नुहोस्। यो इसारा प्रयोग गर्दा सधैँ होम स्क्रिन खुल्छ।"</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"२ वटा औँला प्रयोग गरी स्क्रिनको फेदबाट माथितिर स्वाइप गर्नुहोस्। यो जेस्चर प्रयोग गर्दा सधैँ होम स्क्रिन खुल्छ।"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"होम स्क्रिनमा जाने तरिका"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"स्क्रिनको फेदबाट माथितिर स्वाइप गर्नुहोस्"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"अद्भुत!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"तपाईंले \"एउटा एपबाट अर्को एपमा जानुहोस्\" नामक इसारा प्रयोग गर्ने तरिका सिक्नुभयो"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"एउटा एपबाट अर्को एपमा जान स्वाइप गर्नुहोस्"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"एउटा एपबाट अर्कोमा जान स्क्रिनको फेदबाट माथितिर स्वाइप गर्नुहोस्, छोइराख्नुहोस् अनि औँला उठाउनुहोस्।"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"एउटा एपबाट अर्कोमा जान २ वटा औँला प्रयोग गरी स्क्रिनको फेदबाट माथितिर स्वाइप गर्नुहोस्, छोइराख्नुहोस् अनि औँला उठाउनुहोस्।"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"एउटा एपबाट अर्को एपमा जाने तरिका"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"स्क्रिनको फेदबाट माथितिर स्वाइप गर्नुहोस्, छोइराख्नुहोस् अनि औँला उठाउनुहोस्"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"स्याबास!"</string>
@@ -128,14 +126,12 @@
     <string name="taskbar_button_back" msgid="8558862226461164514">"पछाडि जानुहोस्"</string>
     <string name="taskbar_button_ime_switcher" msgid="1730244360907588541">"IME स्विचर"</string>
     <string name="taskbar_button_recents" msgid="7273376136216613134">"हालसालैका बटनहरू"</string>
-    <string name="taskbar_button_notifications" msgid="7471740351507357318">"सूचनाहरू"</string>
+    <string name="taskbar_button_notifications" msgid="7471740351507357318">"नोटिफिकेसनहरू"</string>
     <string name="taskbar_button_quick_settings" msgid="227662894293189391">"द्रुत सेटिङ"</string>
     <string name="taskbar_a11y_title" msgid="6432169809852243110">"टास्कबार"</string>
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"टास्कबार देखाइएको छ"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"टास्कबार र बबल बार बायाँतिर देखाइएका छन्"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"टास्कबार र बबल बार दायाँतिर देखाइएका छन्"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"टास्कबार लुकाइएको छ"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"टास्कबार र बबल बार लुकाइएका छन्"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"नेभिगेसन बार"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"टास्कबार सधैँ देखाउनुहोस्"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"नेभिगेसन मोड बदल्नुहोस्"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"टास्कबार ओभरफ्लो"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"सिरान/बायाँतिर सार्नुहोस्"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"फेद/दायाँतिर सार्नुहोस्"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{थप एप}other{थप एपहरू}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"डेस्कटप"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> र <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> एक्स्पान्ड गर्नुहोस्"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> कोल्याप्स गर्नुहोस्"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"खोज्न सर्कल बनाउनुहोस्"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"एप जनाउने आइकन"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"एपको शीर्षक"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"\"बन्द गर्नुहोस्\" बटन"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-nl/strings.xml b/quickstep/res/values-nl/strings.xml
index 529516c..d265f96 100644
--- a/quickstep/res/values-nl/strings.xml
+++ b/quickstep/res/values-nl/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Vrije vorm"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Verplaatsen naar extern scherm"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Sluiten"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Geen recente items"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Instellingen voor app-gebruik"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Alles wissen"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Recente apps"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Taak gesloten"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minuut"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Nog <xliff:g id="TIME">%1$s</xliff:g> vandaag"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"App-suggesties staan aan"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"App-suggesties staan uit"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Voorspelde app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial voor navigatie met gebaren"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Het apparaat draaien"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Draai het apparaat om de tutorial voor navigatie met gebaren af te ronden"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Swipe vanaf de rechter- of linkerrand"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Open Instellingen om de gevoeligheid van Terug te wijzigen"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Swipe om terug te gaan"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Swipe vanaf de linker- of rechterrand naar het midden om terug te gaan naar het vorige scherm."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Als je wilt teruggaan naar het laatste scherm, swipe je met 2 vingers vanaf de linker- of rechterrand naar het midden van het scherm."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Terug"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Swipe vanaf de linker- of rechterrand naar het midden van het scherm"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Swipe vanaf de onderrand van het scherm omhoog"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Je weet nu hoe je teruggaat naar het startscherm"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Swipe om naar het startscherm te gaan"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Swipe omhoog vanaf de onderkant van het scherm. Met dit gebaar ga je altijd naar het startscherm."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Swipe met 2 vingers omhoog vanaf de onderkant van het scherm. Met dit gebaar ga je altijd naar het startscherm."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Naar het startscherm"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Swipe omhoog vanaf de onderkant van het scherm"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Goed gedaan"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Je weet nu hoe je het gebaar Schakelen tussen apps maakt"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Swipe om tussen apps te schakelen"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Swipe omhoog vanaf de onderkant van het scherm, houd vast en laat los om tussen apps te schakelen."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Swipe met 2 vingers omhoog vanaf de onderkant van het scherm, houd vast en laat los om tussen apps te schakelen."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Schakelen tussen apps"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Swipe omhoog vanaf de onderkant van het scherm, houd je vinger op het scherm en laat dan los"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Goed bezig"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Taakbalk wordt getoond"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taakbalk en bubbels links getoond"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taakbalk en bubbels rechts getoond"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Taakbalk is verborgen"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Taakbalk en bubbels verborgen"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigatiebalk"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Taakbalk altijd tonen"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Navigatiemodus wijzigen"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taakbalkoverloop"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Naar boven/links verplaatsen"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Naar beneden/rechts verplaatsen"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{extra app}other{extra apps}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Desktop"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> uitvouwen"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> samenvouwen"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Icoon van app"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Titel van app"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Knop Sluiten"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-or/strings.xml b/quickstep/res/values-or/strings.xml
index afc909d..e19fee5 100644
--- a/quickstep/res/values-or/strings.xml
+++ b/quickstep/res/values-or/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"ଫ୍ରିଫର୍ମ"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ଡେସ୍କଟପ"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"ଏକ୍ସଟର୍ନଲ ଡିସପ୍ଲେକୁ ମୁଭ କରନ୍ତୁ"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"ବନ୍ଦ କରନ୍ତୁ"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ଡେସ୍କଟପ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ବର୍ତ୍ତମାନର କୌଣସି ଆଇଟମ ନାହିଁ"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ଆପ ବ୍ୟବହାର ସେଟିଂସ"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ସବୁ ଖାଲି କରନ୍ତୁ"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ବର୍ତ୍ତମାନର ଆପ୍‌"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"ଟାସ୍କ ବନ୍ଦ ହୋଇଯାଇଛି"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 ମିନିଟ୍"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"ଆଜି <xliff:g id="TIME">%1$s</xliff:g> ବାକି ଅଛି"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ଆପ୍ ପରାମର୍ଶଗୁଡ଼ିକୁ ସକ୍ଷମ କରାଯାଇଛି"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ଆପ୍ ପରାମର୍ଶଗୁଡ଼ିକୁ ଅକ୍ଷମ କରାଯାଇଛି"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ପୂର୍ବାନୁମାନ କରାଯାଇଥିବା ଆପ୍: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"ଜେଶ୍ଚର ନାଭିଗେସନ ଟ୍ୟୁଟୋରିଆଲ"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"ଆପଣଙ୍କ ଡିଭାଇସକୁ ରୋଟେଟ କରନ୍ତୁ"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ଜେଶ୍ଚର ନାଭିଗେସନ ଟ୍ୟୁଟୋରିଆଲ ସମ୍ପୂର୍ଣ୍ଣ କରିବାକୁ ଦୟାକରି ଆପଣଙ୍କ ଡିଭାଇସ ରୋଟେଟ କରନ୍ତୁ"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ଆପଣ ସ୍କ୍ରିନର ଏକଦମ-ଡାହାଣ ବା ବାମ ଧାରରୁ ସ୍ୱାଇପ କରୁଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ।"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"ପଛକୁ ଫେରିବା ଜେଶ୍ଚରର ସମ୍ବେଦନଶୀଳତା ବଦଳାଇବାକୁ ସେଟିଂସକୁ ଯାଆନ୍ତୁ"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"ପଛକୁ ଫେରିବା ପାଇଁ ସ୍ୱାଇପ୍ କରନ୍ତୁ"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"ପୂର୍ବ ସ୍କ୍ରିନକୁ ଫେରିବା ପାଇଁ, ସ୍କ୍ରିନର ବାମ କିମ୍ବା ଡାହାଣ ଧାରରୁ ମଝିକୁ ସ୍ୱାଇପ କରନ୍ତୁ।"</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"ପୂର୍ବ ସ୍କ୍ରିନକୁ ଫେରିବା ପାଇଁ, ସ୍କ୍ରିନର ବାମ କିମ୍ବା ଡାହାଣ ଧାରରୁ ମଝିକୁ 2ଟି ଆଙ୍ଗୁଠିରେ ସ୍ୱାଇପ କରନ୍ତୁ।"</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"ପଛକୁ ଫେରନ୍ତୁ"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"ସ୍କ୍ରିନର ବାମ କିମ୍ବା ଡାହାଣ ଧାରରୁ ମଝିକୁ ସ୍ୱାଇପ କରନ୍ତୁ"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"ଆପଣ ସ୍କ୍ରିନର ତଳ ଧାରରୁ ଉପରକୁ ସ୍ୱାଇପ କରୁଥିବା ସୁନିଶ୍ଚିତ କରନ୍ତୁ"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"ଆପଣ \'ମୂଳପୃଷ୍ଠାକୁ ଯାଆନ୍ତୁ\' ଜେଶ୍ଚର୍ ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି।"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"ହୋମକୁ ଯିବା ପାଇଁ ସ୍ୱାଇପ କରନ୍ତୁ"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"ଆପଣଙ୍କ ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରନ୍ତୁ। ଏହି ଜେଶ୍ଚର ସର୍ବଦା ଆପଣଙ୍କୁ ହୋମ ସ୍କ୍ରିନକୁ ନେଇଥାଏ।"</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"2ଟି ଆଙ୍ଗୁଠିରେ ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରନ୍ତୁ। ଏହି ଜେଶ୍ଚର ସର୍ବଦା ଆପଣଙ୍କୁ ହୋମ ସ୍କ୍ରିନକୁ ନେଇଥାଏ।"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"ହୋମକୁ ଯାଆନ୍ତୁ"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"ଆପଣଙ୍କ ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରନ୍ତୁ"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"ବଢ଼ିଆ କାମ!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"ଆପଣ \'ଆପଗୁଡ଼ିକୁ ସ୍ୱିଚ୍ କରନ୍ତୁ\' ଜେଶ୍ଚର୍ ସମ୍ପୂର୍ଣ୍ଣ କରିଛନ୍ତି।"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"ଆପଗୁଡ଼ିକୁ ସ୍ୱିଚ୍ କରିବା ପାଇଁ ସ୍ୱାଇପ୍ କରନ୍ତୁ"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"ଆପ୍ସ ମଧ୍ୟରେ ସୁଇଚ କରିବାକୁ, ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରନ୍ତୁ, ଧରି ରଖନ୍ତୁ, ତା\'ପରେ ରିଲିଜ କରନ୍ତୁ।"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"ଆପ୍ସ ମଧ୍ୟରେ ସ୍ୱିଚ କରିବାକୁ, 2ଟି ଆଙ୍ଗୁଠିରେ ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରି ଧରି ରଖନ୍ତୁ, ତା\'ପରେ ରିଲିଜ କର।"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"ଆପ୍ସ ସୁଇଚ କରନ୍ତୁ"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"ଆପଣଙ୍କ ସ୍କ୍ରିନର ତଳୁ ଉପରକୁ ସ୍ୱାଇପ କରି ଧରି ରଖନ୍ତୁ, ତା\'ପରେ ରିଲିଜ କରନ୍ତୁ"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"ବହୁତ ବଢ଼ିଆ!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ଟାସ୍କବାର ଦେଖାଯାଇଛି"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ଟାସ୍କବାର ଓ ବବଲ ବାମରେ ଦେଖାଯାଇଛି"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ଟାସ୍କବାର ଓ ବବଲ ଡାହାଣରେ ଦେଖାଯାଇଛି"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ଟାସ୍କବାର ଲୁଚାଯାଇଛି"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ଟାସ୍କବାର ଓ ବବଲ ଲୁଚାଯାଇଛି"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"ନାଭିଗେସନ ବାର"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"ସର୍ବଦା ଟାସ୍କବାର ଦେଖାନ୍ତୁ"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"ନାଭିଗେସନ ମୋଡ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ଟାସ୍କବାର ଓଭରଫ୍ଲୋ"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ଶୀର୍ଷ/ବାମକୁ ମୁଭ କରନ୍ତୁ"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ନିମ୍ନ/ଡାହାଣକୁ ମୁଭ କରନ୍ତୁ"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ଅଧିକ ଆପ}other{ଅଧିକ ଆପ୍ସ}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"ଡେସ୍କଟପ"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ଏବଂ <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ବିସ୍ତାର କରନ୍ତୁ"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ସଙ୍କୁଚିତ କରନ୍ତୁ"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"ସର୍ଚ୍ଚ କରିବାକୁ ସର୍କଲ କରନ୍ତୁ"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ଆପ ଆଇକନ"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ଆପ ଟାଇଟେଲ"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"\"ବନ୍ଦ କରନ୍ତୁ\" ବଟନ"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-pa/strings.xml b/quickstep/res/values-pa/strings.xml
index 69b33f9..4a015e4 100644
--- a/quickstep/res/values-pa/strings.xml
+++ b/quickstep/res/values-pa/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"ਫ੍ਰੀਫਾਰਮ"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ਡੈਸਕਟਾਪ"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"ਬਾਹਰੀ ਡਿਸਪਲੇ \'ਤੇ ਜਾਓ"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"ਬੰਦ ਕਰੋ"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ਡੈਸਕਟਾਪ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ਕੋਈ ਹਾਲੀਆ ਆਈਟਮ ਨਹੀਂ"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ਐਪ ਵਰਤੋਂ ਦੀਆਂ ਸੈਟਿੰਗਾਂ"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ਸਭ ਕਲੀਅਰ ਕਰੋ"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ਹਾਲੀਆ ਐਪਾਂ"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"ਕਾਰਜ ਬੰਦ"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 ਮਿੰਟ"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"ਅੱਜ <xliff:g id="TIME">%1$s</xliff:g> ਬਾਕੀ"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ਐਪ ਸੁਝਾਵਾਂ ਨੂੰ ਚਾਲੂ ਕੀਤਾ ਗਿਆ"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ਐਪ ਸੁਝਾਵਾਂ ਨੂੰ ਬੰਦ ਕੀਤਾ ਗਿਆ"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"ਪੂਰਵ ਅਨੁਮਾਨਿਤ ਐਪ: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"ਇਸ਼ਾਰਾ ਨੈਵੀਗੇਸ਼ਨ ਸੰਬੰਧੀ ਟਿਊਟੋਰੀਅਲ"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਘੁੰਮਾਓ"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"ਕਿਰਪਾ ਕਰਕੇ ਇਸ਼ਾਰਾ ਨੈਵੀਗੇਸ਼ਨ ਟਿਊਟੋਰੀਅਲ ਨੂੰ ਪੂਰਾ ਕਰਨ ਲਈ ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਘੁੰਮਾਓ"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ਇਹ ਪੱਕਾ ਕਰੋ ਕਿ ਤੁਸੀਂ ਸੱਜੇ ਜਾਂ ਖੱਬੇ ਪਾਸੇ ਦੇ ਬਿਲਕੁਲ ਕਿਨਾਰੇ ਤੋਂ ਸਵਾਈਪ ਕਰਦੇ ਹੋ"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"ਪਿੱਛੇ ਜਾਣ ਦੇ ਸੰਕੇਤ ਦੀ ਸੰਵੇਦਨਸ਼ੀਲਤਾ ਬਦਲਣ ਲਈ, ਸੈਟਿੰਗਾਂ \'ਤੇ ਜਾਓ"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"ਪਿੱਛੇ ਜਾਣ ਲਈ ਸਵਾਈਪ ਕਰੋ"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"ਪਿਛਲੀ ਸਕ੍ਰੀਨ \'ਤੇ ਵਾਪਸ ਜਾਣ ਲਈ, ਖੱਬੇ ਜਾਂ ਸੱਜੇ ਕਿਨਾਰੇ ਤੋਂ ਸਕ੍ਰੀਨ ਦੇ ਵਿਚਕਾਰ ਤੱਕ ਸਵਾਈਪ ਕਰੋ।"</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"ਪਿਛਲੀ ਸਕ੍ਰੀਨ \'ਤੇ ਵਾਪਸ ਜਾਣ ਲਈ, ਖੱਬੇ ਜਾਂ ਸੱਜੇ ਕਿਨਾਰੇ ਤੋਂ ਸਕ੍ਰੀਨ ਦੇ ਵਿਚਕਾਰ ਤੱਕ 2 ਉਂਗਲਾਂ ਨਾਲ ਸਵਾਈਪ ਕਰੋ।"</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"ਵਾਪਸ ਜਾਓ"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"ਖੱਬੇ ਜਾਂ ਸੱਜੇ ਕਿਨਾਰੇ ਤੋਂ ਸਕ੍ਰੀਨ ਦੇ ਵਿਚਕਾਰ ਤੱਕ ਸਵਾਈਪ ਕਰੋ"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"ਇਹ ਪੱਕਾ ਕਰੋ ਕਿ ਤੁਸੀਂ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਲੇ ਕਿਨਾਰੇ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"ਤੁਸੀਂ \'ਹੋਮ \'ਤੇ ਜਾਓ\' ਦਾ ਇਸ਼ਾਰਾ ਪੂਰਾ ਕੀਤਾ"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"ਹੋਮ \'ਤੇ ਜਾਣ ਲਈ ਸਵਾਈਪ ਕਰੋ"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"ਆਪਣੀ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ। ਇਹ ਇਸ਼ਾਰਾ ਹਮੇਸ਼ਾਂ ਤੁਹਾਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਲੈ ਜਾਂਦਾ ਹੈ।"</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ 2 ਉਂਗਲਾਂ ਨਾਲ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ। ਇਹ ਇਸ਼ਾਰਾ ਹਮੇਸ਼ਾਂ ਤੁਹਾਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਲੈ ਜਾਂਦਾ ਹੈ।"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਜਾਓ"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"ਆਪਣੀ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"ਬਹੁਤ ਵਧੀਆ!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"ਤੁਸੀਂ \'ਐਪਾਂ ਵਿਚਾਲੇ ਅਦਲਾ-ਬਦਲੀ ਕਰੋ\' ਦਾ ਇਸ਼ਾਰਾ ਪੂਰਾ ਕੀਤਾ"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"ਐਪਾਂ ਵਿਚਾਲੇ ਅਦਲਾ-ਬਦਲੀ ਕਰਨ ਲਈ ਸਵਾਈਪ ਕਰੋ"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"ਐਪਾਂ ਵਿਚਾਲੇ ਸਵਿੱਚ ਕਰਨ ਲਈ, ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਤੇ ਸਵਾਈਪ ਕਰ ਕੇ ਦਬਾਈ ਰੱਖੋ ਅਤੇ ਫਿਰ ਛੱਡੋ।"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"ਐਪਾਂ ਵਿਚਾਲੇ ਸਵਿੱਚ ਕਰਨ ਲਈ, ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ 2 ਉਂਗਲਾਂ ਨਾਲ ਉੱਤੇ ਸਵਾਈਪ ਕਰ ਕੇ ਦਬਾਈ ਰੱਖੋ ਅਤੇ ਫਿਰ ਛੱਡੋ।"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"ਐਪਾਂ ਸਵਿੱਚ ਕਰੋ"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"ਆਪਣੀ ਸਕ੍ਰੀਨ ਦੇ ਹੇਠਾਂ ਤੋਂ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰ ਕੇ ਦਬਾਈ ਰੱਖੋ, ਅਤੇ ਫਿਰ ਛੱਡੋ"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"ਬਹੁਤ ਵਧੀਆ!"</string>
@@ -92,7 +90,7 @@
     <string name="allset_title" msgid="5021126669778966707">"ਪੂਰੀ ਤਰ੍ਹਾਂ ਤਿਆਰ!"</string>
     <string name="allset_hint" msgid="459504134589971527">"ਹੋਮ \'ਤੇ ਜਾਣ ਲਈ ਉੱਪਰ ਵੱਲ ਸਵਾਈਪ ਕਰੋ"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"ਆਪਣੀ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਜਾਣ ਲਈ ਹੋਮ ਬਟਨ \'ਤੇ ਟੈਪ ਕਰੋ"</string>
-    <string name="allset_description_generic" msgid="5385500062202019855">"ਤੁਸੀਂ ਆਪਣਾ <xliff:g id="DEVICE">%1$s</xliff:g> ਵਰਤਣ ਲਈ ਤਿਆਰ ਹੋ"</string>
+    <string name="allset_description_generic" msgid="5385500062202019855">"ਹੁਣ ਤੁਹਾਡਾ <xliff:g id="DEVICE">%1$s</xliff:g> ਵਰਤੋਂ ਲਈ ਤਿਆਰ ਹੈ"</string>
     <string name="default_device_name" msgid="6660656727127422487">"ਡੀਵਾਈਸ"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"ਸਿਸਟਮ ਨੈਵੀਗੇਸ਼ਨ ਸੈਟਿੰਗਾਂ"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"ਸਾਂਝਾ ਕਰੋ"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ਟਾਸਕਬਾਰ ਨੂੰ ਦਿਖਾਇਆ ਗਿਆ"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ਟਾਸਕਬਾਰ ਤੇ ਬਬਲ ਨੂੰ ਖੱਬੇ ਦਿਖਾਇਆ"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ਟਾਸਕਬਾਰ ਤੇ ਬਬਲ ਨੂੰ ਸੱਜੇ ਦਿਖਾਇਆ"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ਟਾਸਕਬਾਰ ਨੂੰ ਲੁਕਾਇਆ ਗਿਆ"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ਟਾਸਕਬਾਰ ਅਤੇ ਬਬਲ ਨੂੰ ਲੁਕਾਇਆ ਗਿਆ"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"ਨੈਵੀਗੇਸ਼ਨ ਵਾਲੀ ਪੱਟੀ"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"ਹਮੇਸ਼ਾਂ ਟਾਸਕਬਾਰ ਦਿਖਾਓ"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"ਨੈਵੀਗੇਸ਼ਨ ਮੋਡ ਬਦਲੋ"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ਟਾਸਕਬਾਰ ਓਵਰਫ਼ਲੋ"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ਸਿਖਰਲੇ/ਖੱਬੇ ਪਾਸੇ ਲੈ ਕੇ ਜਾਓ"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ਹੇਠਾਂ/ਸੱਜੇ ਪਾਸੇ ਲੈ ਕੇ ਜਾਓ"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ਹੋਰ ਐਪ}one{ਹੋਰ ਐਪ}other{ਹੋਰ ਐਪਾਂ}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"ਡੈਸਕਟਾਪ"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> ਅਤੇ <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ਦਾ ਵਿਸਤਾਰ ਕਰੋ"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ਨੂੰ ਸਮੇਟੋ"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"ਖੋਜਣ ਲਈ ਚੱਕਰ ਬਣਾਓ"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ਐਪ ਪ੍ਰਤੀਕ"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ਐਪ ਸਿਰਲੇਖ"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"\'ਬੰਦ ਕਰੋ\' ਬਟਨ"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-pl/strings.xml b/quickstep/res/values-pl/strings.xml
index 88b5053..89297a2 100644
--- a/quickstep/res/values-pl/strings.xml
+++ b/quickstep/res/values-pl/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Tryb dowolny"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Pulpit"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Przenieś na wyświetlacz zewnętrzny"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Zamknij"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Pulpit"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Brak ostatnich elementów"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ustawienia użycia aplikacji"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Wyczyść wszystko"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Ostatnie aplikacje"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Zadanie zamknięte"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&gt; 1 min"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Na dziś zostało <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Włączono sugestie aplikacji"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Sugestie aplikacji są wyłączone"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Przewidywana aplikacja: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Samouczek dotyczący nawigacji przy użyciu gestów"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Obróć urządzenie"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Obróć urządzenie, aby ukończyć samouczek nawigacji przy użyciu gestów"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pamiętaj, aby przesuwać palcem od samej krawędzi (prawej lub lewej)"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Czułość gestu cofania możesz zmienić w Ustawieniach"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Przesuń palcem, aby przejść wstecz"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Aby wrócić do poprzedniego ekranu, przesuń palcem od lewej lub prawej krawędzi do środka ekranu."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Aby wrócić do poprzedniego ekranu, przesuń 2 palcami od lewej lub prawej krawędzi do środka ekranu."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Przejście wstecz"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Przesuń palcem od lewej lub prawej krawędzi do środka ekranu"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Pamiętaj, aby przesuwać palcem od dolnej krawędzi ekranu"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Gest przechodzenia na ekran główny został opanowany"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Przesuń palcem, aby przejść na ekran główny"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Przesuń palcem od dołu ekranu. Ten gest zawsze powoduje przejście na ekran główny."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Przesuń 2 palcami od dołu ekranu. Ten gest zawsze powoduje przejście do ekranu głównego."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Przejście na ekran główny"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Przesuń palcem z dołu ekranu w górę"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Brawo!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Gest przełączania aplikacji został opanowany"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Przesuń palcem, aby przełączać aplikacje"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Aby przełączać się między aplikacjami, przesuń palcem od dołu do góry ekranu, przytrzymaj i puść."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Aby przełączać się między aplikacjami, przesuń 2 palcami od dołu ekranu, przytrzymaj i puść."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Przełącz aplikację"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Przesuń palcem z dołu ekranu w górę, przytrzymaj i puść"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Brawo!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Pasek aplikacji widoczny"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Pasek i dymki po lewej"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Pasek i dymki po prawej"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Pasek aplikacji ukryty"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Pasek i dymki są ukryte"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Pasek nawigacyjny"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Zawsze pokazuj pasek aplikacji"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Zmień tryb nawigacji"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Rozwijany pasek aplikacji"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Przesuń w górny lewy róg"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Przesuń w dolny prawy róg"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{inna aplikacja}few{inne aplikacje}many{innych aplikacji}other{innej aplikacji}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Pulpit"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"rozwiń dymek: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"zwiń dymek: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Zaznacz, aby wyszukać"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikona aplikacji"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Tytuł aplikacji"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Przycisk Zamknij"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-pt-rPT/strings.xml b/quickstep/res/values-pt-rPT/strings.xml
index 84120a1..8c38182 100644
--- a/quickstep/res/values-pt-rPT/strings.xml
+++ b/quickstep/res/values-pt-rPT/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Forma livre"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Computador"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover para o ecrã externo"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Fechar"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Computador"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nenhum item recente"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Definições de utilização de aplicações"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Limpar tudo"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Apps recentes"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Tarefa fechada"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minuto"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Resta(m) <xliff:g id="TIME">%1$s</xliff:g> hoje."</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Sugestões de apps ativadas"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"As sugestões de apps estão desativadas"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"App prevista: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial da navegação por gestos"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rode o dispositivo"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rode o seu dispositivo para concluir o tutorial de navegação por gestos"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Deslize a partir da extremidade mais à direita ou mais à esquerda"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Altere a sensibilidade do gesto para voltar nas Definições."</string>
     <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">"Retroceder"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Deslize a partir da extremidade esquerda ou direita até ao centro do ecrã"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Deslize a partir do limite inferior do ecrã"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Concluiu o gesto para aceder ao ecrã principal"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Deslize rapidamente com o dedo para aceder ao ecrã principal"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Deslize rapidamente para cima a partir da parte inferior. Este gesto abre sempre o ecrã principal."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Deslize rapidamente para cima com 2 dedos no fundo do ecrã. Este gesto abre sempre o ecrã principal."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Aceda ao ecrã principal"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Deslize para cima a partir da parte inferior do ecrã"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Muito bem!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Concluiu o gesto para alternar entre apps"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Deslize rapidamente com o dedo para alternar entre apps"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Para alternar entre apps, deslize para cima sem soltar a partir da parte inferior do ecrã e solte."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Para mudar de app, deslize rapidamente para cima com 2 dedos sem soltar no fundo do ecrã e solte."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Mude de app"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Deslize para cima a partir da parte inferior do ecrã , detenha o gesto e solte"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Muito bem!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Barra de tarefas apresentada"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Barra de tarefas/balões à esq."</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Barra de tarefas/balões à dir."</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Barra de tarefas ocultada"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Barra tarefas/balões ocultos"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barra de navegação"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Ver sempre Barra de tarefas"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Alterar modo de navegação"</string>
@@ -143,6 +139,7 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Menu adicional da Barra de tarefas"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover para a parte superior esquerda"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover para a part superior direita"</string>
+    <string name="open_app_as_a_bubble" msgid="6642626287247807473">"Abrir app como um balão"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{outra app}other{outras apps}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"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>
@@ -156,4 +153,9 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"expandir <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"reduzir <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circundar para Pesquisar"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ícone da app"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Título da app"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Botão Fechar"</string>
+    <string name="pin_to_taskbar" msgid="6607778046321626950">"Afixar na barra tar."</string>
+    <string name="unpin_from_taskbar" msgid="2178811773165572676">"Desaf. da barra tar."</string>
 </resources>
diff --git a/quickstep/res/values-pt/strings.xml b/quickstep/res/values-pt/strings.xml
index 3238c99..5ea0194 100644
--- a/quickstep/res/values-pt/strings.xml
+++ b/quickstep/res/values-pt/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Forma livre"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Modo área de trabalho"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Mover para a tela externa"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Fechar"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Computador"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nenhum item recente"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Configurações de uso do app"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Remover tudo"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Apps recentes"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Tarefa encerrada"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> restante(s) hoje"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"O recurso \"sugestões de apps\" está ativado"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"O recurso \"sugestões de apps\" está desativado"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"App previsto: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial da navegação por gestos"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Gire o dispositivo"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Gire o dispositivo para concluir o tutorial da navegação por gestos"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Deslize da borda direita ou esquerda"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Mude a sensibilidade do gesto de voltar nas configurações"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Deslize para voltar"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Para voltar à tela anterior, deslize da borda esquerda ou direita até o meio da tela."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Para voltar à tela anterior, deslize da borda esquerda ou direita até o meio da tela com dois dedos."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Volte"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Deslize da borda esquerda ou direita até o meio da tela"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Deslize da borda inferior da tela para cima"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Você concluiu o gesto para acessar a tela inicial"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Deslizar para voltar à tela inicial"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Deslize de baixo para cima na tela. Esse gesto sempre leva você para a tela inicial."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Deslize de baixo para cima na tela com dois dedos. Esse gesto sempre leva você para a tela inicial."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Vá para a página inicial"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Deslize de baixo para cima na tela"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Muito bem!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Você concluiu o gesto para mudar de app"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Deslizar para trocar de app"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Para mudar de app, deslize de baixo para cima, mantenha a tela pressionada por um tempo e solte."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Para mudar de app, deslize de baixo para cima na tela com dois dedos, segure por um tempo e solte."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Mude de app"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Deslize de baixo para cima na tela, segure e depois solte"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Muito bem!"</string>
@@ -92,7 +90,7 @@
     <string name="allset_title" msgid="5021126669778966707">"Tudo pronto!"</string>
     <string name="allset_hint" msgid="459504134589971527">"Deslize para cima para acessar a tela inicial"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Toque no botão home para acessar a tela inicial"</string>
-    <string name="allset_description_generic" msgid="5385500062202019855">"Você já pode começar a usar seu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
+    <string name="allset_description_generic" msgid="5385500062202019855">"O <xliff:g id="DEVICE">%1$s</xliff:g> já pode ser usado"</string>
     <string name="default_device_name" msgid="6660656727127422487">"dispositivo"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Configurações de navegação do sistema"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"Compartilhar"</string>
@@ -120,7 +118,7 @@
     <string name="taskbar_edu_pinning_title" msgid="210102174154211712">"Sempre mostrar a Barra de tarefas"</string>
     <string name="taskbar_edu_pinning_standalone" msgid="2636919474366410467">"Toque e pressione o divisor para sempre mostrar a Barra de tarefas na parte de baixo da tela"</string>
     <string name="taskbar_search_edu_title" msgid="5569194922234364530">"Toque na tecla de ação e pressione para pesquisar o que está na tela"</string>
-    <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"O produto usa a parte selecionada da tela para pesquisar. O uso desses dados está sujeito à <xliff:g id="BEGIN_PRIVACY_LINK">&lt;a href="%1$s"&gt;</xliff:g>Política de Privacidade<xliff:g id="END_PRIVACY_LINK">&lt;/a&gt;</xliff:g> e aos <xliff:g id="BEGIN_TOS_LINK">&lt;a href="%2$s"&gt;</xliff:g>Termos de Serviço<xliff:g id="END_TOS_LINK">&lt;/a&gt;</xliff:g> do Google."</string>
+    <string name="taskbar_edu_search_disclosure" msgid="8734536088447779686">"Este produto usa a parte selecionada da tela para pesquisar. O uso desses dados está sujeito à <xliff:g id="BEGIN_PRIVACY_LINK">&lt;a href="%1$s"&gt;</xliff:g>Política de Privacidade<xliff:g id="END_PRIVACY_LINK">&lt;/a&gt;</xliff:g> e aos <xliff:g id="BEGIN_TOS_LINK">&lt;a href="%2$s"&gt;</xliff:g>Termos de Serviço<xliff:g id="END_TOS_LINK">&lt;/a&gt;</xliff:g> do Google."</string>
     <string name="taskbar_edu_close" msgid="887022990168191073">"Fechar"</string>
     <string name="taskbar_edu_done" msgid="6880178093977704569">"Concluído"</string>
     <string name="taskbar_button_home" msgid="2151398979630664652">"Início"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Barra de tarefas visível"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Barra de tar. e balões à esq."</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Barra de tar. e balões à dir."</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Barra de tarefas oculta"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Barra de tar. e balões ocultos"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Barra de navegação"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Sempre mostrar a Barra de tarefas"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Mudar o modo de navegação"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Barra de tarefas flutuante"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mover para cima/para a esquerda"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mover para baixo/para a direita"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{outro app}one{outro app}other{outros apps}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"abrir <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"fechar <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circule para pesquisar"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ícone do app"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Título do app"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Botão \"Fechar\""</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ro/strings.xml b/quickstep/res/values-ro/strings.xml
index e6aad47..08e4081 100644
--- a/quickstep/res/values-ro/strings.xml
+++ b/quickstep/res/values-ro/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Formă liberă"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Mută pe ecranul extern"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Închide"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Computer"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Niciun element recent"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Setări de utilizare a aplicației"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Șterge tot"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplicații recente"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Activitatea s-a încheiat"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minut"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Au mai rămas <xliff:g id="TIME">%1$s</xliff:g> astăzi"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Sugestiile de aplicații au fost activate"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Sugestiile de aplicații au fost dezactivate"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Aplicația estimată: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial de navigare prin gesturi"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotește dispozitivul"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rotește dispozitivul pentru a încheia tutorialul de navigare prin gesturi"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Glisează dinspre marginea dreaptă îndepărtată sau dinspre marginea stângă îndepărtată"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Schimbă sensibilitatea gestului „Înapoi” accesând Setările"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Glisează pentru a reveni"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Pentru a reveni la ultimul ecran, glisează de la marginea stângă sau dreaptă spre mijlocul ecranului."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Pentru a reveni la ultimul ecran, glisează cu două degete dinspre marginea stângă sau dreaptă spre mijlocul ecranului."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Înapoi"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Glisează dinspre marginea stângă sau dreaptă până la jumătatea ecranului"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Glisează în sus dinspre marginea de jos a ecranului"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Ai finalizat gestul „accesează ecranul de pornire”"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Glisează pentru a accesa ecranul de pornire"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Glisează în sus din partea de jos a ecranului. Cu acest gest accesezi mereu ecranul de pornire."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Glisează în sus cu două degete din partea de jos. Cu acest gest accesezi mereu ecranul de pornire."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Înapoi la ecranul de pornire"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Glisează în sus din partea de jos a ecranului"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Excelent!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Ai finalizat gestul „comută între aplicații”"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Glisează pentru a comuta între aplicații"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Ca să comuți între aplicații, glisează de jos în sus, ține degetul pe ecran, apoi ridică-l."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Ca să comuți între aplicații, glisează cu 2 degete de jos în sus, ține-le pe ecran, apoi ridică-le."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Comută între aplicații"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Glisează în sus din partea de jos a ecranului, ține apăsat, apoi eliberează"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Felicitări!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Bara de activități este afișată"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Bară și baloane stânga afișate"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Bară &amp; baloane dreapta afișate"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Bara de activități este ascunsă"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Bară și baloane ascunse"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Bară de navigare"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Afișează mereu bara"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Schimbă modul de navigare"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Meniu suplimentar pentru bara de activități"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Mută în stânga sus"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Mută în dreapta jos"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{aplicație suplimentară}few{mai multe aplicații}other{mai multe aplicații}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"extinde <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"restrânge <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Încercuiește și caută"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Pictograma aplicației"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Titlul aplicației"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Buton de închidere"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ru/strings.xml b/quickstep/res/values-ru/strings.xml
index 297ae02..65af36f 100644
--- a/quickstep/res/values-ru/strings.xml
+++ b/quickstep/res/values-ru/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Произвольная форма"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Мультиоконный режим"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Перенести на внешний дисплей"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Закрыть"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Мультиоконный режим"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Здесь пока ничего нет."</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Настройки использования приложения"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Очистить все"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Недавние приложения"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Задача закрыта"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>: <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 мин."</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Осталось сегодня: <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Функция \"Рекомендуемые приложения\" включена."</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Функция \"Рекомендуемые приложения\" отключена."</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Рекомендуемое приложение: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Руководство: навигация с помощью жестов"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Поверните устройство"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Чтобы перейти к руководству по жестам, нужно повернуть устройство."</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Проведите справа налево или слева направо от самого края экрана."</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Уровень чувствительности можно изменить в настройках."</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Возврат к предыдущему экрану"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Чтобы вернуться к предыдущему экрану, проведите от левого или правого края дисплея к центру."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Чтобы вернуться на предыдущий экран, проведите двумя пальцами от левого или правого края экрана к центру."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Как вернуться к предыдущему экрану"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Проведите от левого или правого края экрана к центру."</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Проведите снизу вверх от самого края экрана."</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Вы выполнили жест для перехода на главный экран."</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Переход на главный экран"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Проведите вверх от нижнего края дисплея. Этот жест открывает главный экран."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Проведите двумя пальцами вверх от нижнего края экрана. Этот жест открывает главный экран."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Как перейти на главный экран"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Проведите вверх от нижнего края экрана."</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"У вас получилось!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Вы выполнили жест для переключения между приложениями."</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Переключение между приложениями"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Чтобы переключиться между приложениями‚ проведите по экрану снизу вверх, задержите палец, а затем отпустите."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Чтобы сменить приложение, проведите двумя пальцами снизу вверх, задержите пальцы, а затем отпустите."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Как переключаться между приложениями"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Проведите вверх от нижнего края экрана, задержите палец в одной точке и отпустите."</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Отлично!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Панель задач показана"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Слева панель задач, подсказки"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Справа панель задач, подсказки"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Панель задач скрыта"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Скрыты панель задач, подсказки"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Панель навигации"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Всегда показывать панель задач"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Изменить режим навигации"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Дополнительное меню панели задач"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Переместить вверх или влево"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Переместить вниз или вправо"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{дополнительное приложение}one{дополнительное приложение}few{дополнительных приложения}many{дополнительных приложений}other{дополнительного приложения}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Режим компьютера"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> и <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"Развернуто: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"Свернуто: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Обвести и найти"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Значок приложения"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Название приложения"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Кнопка \"Закрыть\""</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-si/strings.xml b/quickstep/res/values-si/strings.xml
index 2c7c672..7e00beb 100644
--- a/quickstep/res/values-si/strings.xml
+++ b/quickstep/res/values-si/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ඩෙස්ක්ටොපය"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"බාහිර සංදර්ශකය වෙත ගෙන යන්න"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"වසන්න"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ඩෙස්ක්ටොපය"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"මෑත අයිතම නැත"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"යෙදුම් භාවිත සැකසීම්"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"සියල්ල හිස් කරන්න"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"මෑත යෙදුම්"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"කාර්යය අවසන් කරන ලදි"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 විනාඩියක්"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"අද <xliff:g id="TIME">%1$s</xliff:g>ක් ඉතුරුයි"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"යෙදුම් යෝජනා සබලිතයි"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"යෙදුම් යෝජනා අබල කර ඇත"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"පුරෝකථනය කළ යෙදුම: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"අභින සංචාලන නිබන්ධනය"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"ඔබේ උපාංගය කරකවන්න"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"අභින සංචාලන නිබන්ධනය සම්පූර්ණ කිරීම සඳහා ඔබේ උපාංගය කරකවන්න"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ඔබ ඈත දකුණු හෝ ඈත වම් දාරයේ සිට ස්වයිප් කරන බව සහතික කර ගන්න"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"ආපසු ඉංගිතයෙහි සංවේදීතාව වෙනස් කිරීමට, සැකසීම් වෙත යන්න"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"ආපසු යාමට ස්වයිප් කරන්න"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"අවසාන තිරයට ආපසු යාමට, වම් හෝ දකුණු දාරයෙන් තිරයේ මැදට ස්වයිප් කරන්න."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"අවසාන තිරයට ආපසු යාමට, වම් හෝ දකුණු දාරයෙන් තිරයේ මැදට ඇඟිලි 2කින් ස්වයිප් කරන්න."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"ආපසු යන්න"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"වම් හෝ දකුණු කෙළවරේ සිට තිරයේ මැදට ස්වයිප් කරන්න"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"ඔබ තිරයේ පහළ දාරයේ සිට ඉහළට ස්වයිප් කරන බව සහතික කර ගන්න"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"ඔබ මුල් පිටුවට යාමේ ඉංගිතය සම්පූර්ණ කළා"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"මුල් පිටුවට යාමට ස්වයිප් කරන්න"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"ඔබගේ තිරයේ පහළින් උඩට ස්වයිප් කරන්න.මෙම ඉංගිතය සැම විටම ඔබව මුල් තිරයට ගෙන යයි."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"තිරයේ පහළම සිට ඇඟිලි 2කින් ඉහළට ස්වයිප් කරන්න. මෙම ඉංගිතය සැම විටම ඔබව මුල් තිරයට ගෙන යයි."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"මුල් පිටුවට යන්න"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"ඔබේ තිරයේ පහළ සිට උඩට ස්වයිප් කරන්න"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"අනර්ඝ වැඩක්!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"ඔබ යෙදුම් මාරු කිරීමේ ඉංගිතය සම්පූර්ණ කළා"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"යෙදුම් මාරු කිරීමට ස්වයිප් කරන්න"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"යෙදුම් අතර මාරු වීමට, ඔබගේ තිරයේ පහළම සිට උඩට ස්වයිප් කර, අල්ලාගෙන සිට, අනතුරුව මුදා හරින්න."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"යෙදුම් අතර මාරු වීමට, ඔබගේ තිරයේ පහළම සිට උඩට ඇඟිලි 2කින් ස්වයිප් කර, අල්ලාගෙන සිට, අනතුරුව මුදා හරින්න."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"යෙදුම් මාරු කරන්න"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"ඔබේ තිරයේ පහළ සිට ඉහළට ස්වයිප් කරන්න, රඳවා ගෙන සිට, පසුව මුදා හරින්න"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"හොඳින් කළා!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"කාර්ය තීරුව පෙන්වා ඇත"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"කාර්ය තීරුව සහ බුබුළු පෙන්වා ඇත"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"කාර්ය තීරුව සහ බුබුළු දකුණට පෙන්වා ඇත"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"කාර්ය තීරුව සඟවා ඇත"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"කාර්ය තීරුව සහ බුබුළු සඟවා ඇත"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"සංචලන තීරුව"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"සෑම විටම කාර්ය තීරුව පෙන්වන්න"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"සංචාලන ප්‍රකාරය වෙනස් කරන්න"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"කාර්ය තීරුව පිටාර යාම"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ඉහළ/වම වෙත ගෙන යන්න"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"පහළ/දකුණ වෙත ගෙන යන්න"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{තව යෙදුම}one{තවත් යෙදුම්}other{තවත් යෙදුම්}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"ඩෙස්ක්ටොපය"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> සහ <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> දිග හරින්න"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> හකුළන්න"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"සෙවීමට කවයසෙවීමට කවය අදින්න"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"යෙදුම් නිරූපකය"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"යෙදුම් මාතෘකාව"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"වැසීමේ බොත්තම"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sk/strings.xml b/quickstep/res/values-sk/strings.xml
index 638e88a..1170e71 100644
--- a/quickstep/res/values-sk/strings.xml
+++ b/quickstep/res/values-sk/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Voľný režim"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Počítač"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Presunúť na externú obrazovku"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Zavrieť"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Počítač"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Žiadne nedávne položky"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Nastavenia využívania aplikácie"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Vymazať všetko"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nedávne aplikácie"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Úloha bola zavretá"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"Menej ako 1 minúta"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Dnes ešte zostáva: <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Návrhy aplikácií zapnuté"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Návrhy aplikácií vypnuté"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predpovedaná aplikácia: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Návod na navigáciu gestami"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Otočte zariadenie"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Otočte zariadenie a dokončite tak návod, ako navigovať gestami"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Musíte potiahnuť úplne z pravého alebo ľavého okraja."</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Ak chcete zmeniť citlivosť gesta Späť, prejdite do Nastavení"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Prechod späť potiahnutím"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Na poslednú obrazovku prejdete potiahnutím z ľavého alebo pravého okraja do stredu obrazovky."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Na poslednú obrazovku sa vrátite potiahnutím dvoma prstami z ľavého alebo pravého okraja do stredu obrazovky."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Prechod späť"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Potiahnite z ľavého alebo pravého okraja do stredu obrazovky."</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Musíte potiahnuť nahor z dolného okraja obrazovky."</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Dokončili ste gesto prechodu na plochu"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Prechod na plochu potiahnutím"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Potiahnite nahor zdola obrazovky. Týmto gestom sa vždy vrátite na plochu."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Postiahnite dvoma prstami z dolnej časti obrazovky. Týmto gestom sa vždy vrátite na plochu."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Prechod na plochu"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Potiahnite z dolnej časti obrazovky nahor."</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Skvelé!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Dokončili ste gesto na prepnutie aplikácií"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Prepínanie aplikácií potiahnutím"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Aplikácie môžete prepínať potiahnutím obrazovky zdola nahor, pridržaním a následným uvoľnením."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Aplikácie prepnete potiahnutím dvoma prstami z dolnej časti obrazovky, ich pridržaním a uvoľnením."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Prepnutie aplikácií"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Potiahnite nahor z dolného okraja obrazovky, pridržte a uvoľnite."</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Výborne"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Panel aplikácií je zobrazený"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Panel aplik. a bubl. sú vľavo"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Panel aplik. a bubl. sú vpravo"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Panel aplikácií je skrytý"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Panel aplik. a bubl. sú skryté"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigačný panel"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Zobrazovať panel aplikácií"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Zmeniť režim navigácie"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Rozšírená ponuka panela aplikácií"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Presunúť hore alebo doľava"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Presunúť dole alebo doprava"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ďalšia aplikácia}few{ďalšie aplikácie}many{ďalšie aplikácie}other{ďalšie aplikácie}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"rozbaliť <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"zbaliť <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Vyhľadávanie krúžením"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikona aplikácie"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Názov aplikácie"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Tlačidlo Zavrieť"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sl/strings.xml b/quickstep/res/values-sl/strings.xml
index 30d2c03..9ed661c 100644
--- a/quickstep/res/values-sl/strings.xml
+++ b/quickstep/res/values-sl/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Prosta oblika"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Namizni računalnik"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Premik v zunanji zaslon"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Zapri"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Namizni način"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Ni nedavnih elementov"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Nastavitve uporabe aplikacij"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Počisti vse"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Nedavne aplikacije"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Opravilo je zaprto"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Danes je ostalo še <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Predlogi aplikacij so omogočeni."</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Predlogi aplikacij so onemogočeni."</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Predvidena aplikacija: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Vadnica za krmarjenje s potezami"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Zasukajte napravo"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Zasukajte napravo, če si želite ogledati vadnico za krmarjenje s potezami"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Pazite, da povlečete s skrajno desnega ali skrajno levega roba."</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Občutljivost poteze za nazaj lahko spremenite v nastavitvah."</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Povlecite za vrnitev"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Če se želite vrniti na prejšnji zaslon, povlecite z levega ali desnega roba do sredine zaslona."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Če se želite vrniti na zadnji prikazani zaslon, z dvema prstoma povlecite z levega ali desnega roba do sredine zaslona."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Pomik nazaj"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Povlecite z levega ali desnega roba do sredine zaslona."</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Pazite, da povlečete s spodnjega roba zaslona navzgor."</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Izvedli ste potezo za pomik na začetni zaslon."</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Povlecite za pomik na začetni zaslon"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Z dna zaslona s prstom povlecite navzgor. S to potezo lahko vedno odprete začetni zaslon."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Z dvema prstoma povlecite navzgor z dna zaslona. S to potezo lahko vedno odprete začetni zaslon."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Pomik na začetni zaslon"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Z dna zaslona s prstom povlecite navzgor."</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Odlično!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Izvedli ste potezo za preklapljanje med aplikacijami."</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Povlecite za preklapljanje med aplikacijami"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Za preklapljanje med aplikacijami povlecite navzgor z dna zaslona, pridržite in nato izpustite."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Za preklop med aplikacijami z dvema prstoma povlecite navzgor z dna zaslona, pridržite in spustite."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Preklop aplikacij"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Povlecite navzgor z dna zaslona, pridržite, nato izpustite."</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Odlično!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Opravilna vrstica je prikazana"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Prikazani so opravilna vrstica in oblački na levi"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Prikazani so opravilna vrstica in oblački na desni"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Opravilna vrstica je skrita"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Opravilna vrstica in oblački so skriti"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Vrstica za krmarjenje"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Stalen prikaz oprav. vrstice"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Spreminjanje načina navigacije"</string>
@@ -143,6 +139,7 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Oblaček opravilne vrstice z dodatnimi elementi"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Premakni na vrh/levo"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Premakni na dno/desno"</string>
+    <string name="open_app_as_a_bubble" msgid="6642626287247807473">"Odpri aplikacijo kot oblaček"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{dodatna aplikacija}one{dodatna aplikacija}two{dodatni aplikaciji}few{dodatne aplikacije}other{dodatnih aplikacij}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"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>
@@ -156,4 +153,9 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"razširitev oblačka <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"strnitev oblačka <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Iskanje z obkroževanjem"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikona aplikacije"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Ime aplikacije"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Gumb za zapiranje"</string>
+    <string name="pin_to_taskbar" msgid="6607778046321626950">"Pripni v opravilno vrstico"</string>
+    <string name="unpin_from_taskbar" msgid="2178811773165572676">"Odpni iz opravilne vrstice"</string>
 </resources>
diff --git a/quickstep/res/values-sq/strings.xml b/quickstep/res/values-sq/strings.xml
index b4b6711..965699d 100644
--- a/quickstep/res/values-sq/strings.xml
+++ b/quickstep/res/values-sq/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Formë e lirë"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktopi"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Zhvendose tek ekrani i jashtëm"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Mbyll"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktopi"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Nuk ka asnjë artikull të fundit"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Cilësimet e përdorimit të aplikacionit"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Pastroji të gjitha"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Aplikacionet e fundit"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Detyra u mbyll"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 minutë"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> të mbetura sot"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Aplikacionet e sugjeruara janë aktivizuar"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Sugjerimet e aplikacioneve janë çaktivizuar"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Aplikacioni i parashikuar: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Udhëzuesi për navigimin me gjeste"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rrotullo pajisjen"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rrotullo pajisjen për të përfunduar udhëzuesin e navigimit me gjeste"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Sigurohu që të rrëshqasësh shpejt nga skaji më i djathtë ose më i majtë"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Për të ndryshuar ndjeshmërinë e gjestit të kthimit prapa, shko te \"Cilësimet\""</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Rrëshqit shpejt për t\'u kthyer prapa"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Për t\'u kthyer prapa tek ekrani i fundit, rrëshqit shpejt nga skaji i majtë ose i djathtë drejt mesit të ekranit"</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Për t\'u kthyer prapa tek ekrani i fundit, rrëshqit shpejt me 2 gishta nga skaji i majtë ose i djathtë drejt mesit të ekranit."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Kthehu prapa"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Rrëshqit shpejt nga skaji i majtë ose i djathtë drejt mesit të ekranit"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Sigurohu që të rrëshqasësh shpejt lart nga skaji i poshtëm i ekranit"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"E ke përfunduar gjestin e kalimit tek ekrani bazë"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Rrëshqit shpejt për të kaluar tek ekrani bazë"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Rrëshqit shpejt lart nga fundi i ekranit tënd. Ky gjest të dërgon gjithmonë tek ekrani bazë."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Rrëshqit shpejt lart me 2 gishta nga fundi i ekranit. Ky gjest të dërgon gjithmonë tek ekrani bazë."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Shko tek ekrani bazë"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Rrëshqit shpejt lart nga pjesa e poshtme e ekranit"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Punë e shkëlqyer!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"E ke përfunduar gjestin e ndërrimit të aplikacioneve"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Rrëshqit shpejt për të ndërruar aplikacionet"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Për të ndërruar mes aplikacioneve, rrëshqit shpejt lart nga fundi i ekranit tënd, mbaj dhe pastaj lësho."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Për të ndërruar mes aplikacioneve, rrëshqit lart me 2 gishta nga fundi i ekranit, mbaje dhe lëshoje."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Ndërro aplikacionet"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Rrëshqit shpejt lart nga fundi i ekranit, mbaje të shtypur dhe më pas lëshoje"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Shumë mirë!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Shiriti i detyrave i shfaqur"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Shiriti i detyrave dhe flluskat majtas janë shfaqur"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Shiriti i detyrave dhe flluskat djathtas janë shfaqur"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Shiriti i detyrave i fshehur"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Shiriti i detyrave dhe flluskat janë fshehur"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Shiriti i navigimit"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Shfaq gjithmonë shiritin e detyrave"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Ndrysho modalitetin e navigimit"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Tejkalimi i shiritit të detyrave"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Lëviz në krye/majtas"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Lëviz në fund/djathtas"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{aplikacion tjetër}other{aplikacione të tjera}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"zgjero <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"palos <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Qarko për të kërkuar"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ikona e aplikacionit"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Titulli i aplikacionit"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Butoni i mbylljes"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sr/strings.xml b/quickstep/res/values-sr/strings.xml
index 6622217..c827cda 100644
--- a/quickstep/res/values-sr/strings.xml
+++ b/quickstep/res/values-sr/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Слободни облик"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Рачунар"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Преместите на спољни екран"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Затвори"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Рачунари"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Нема недавних ставки"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Подешавања коришћења апликације"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Обриши све"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Недавне апликације"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Задатак је затворен"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 мин"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Још <xliff:g id="TIME">%1$s</xliff:g> данас"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Предлози апликација су омогућени"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Предлози апликација су онемогућени"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Предвиђамо апликацију: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Водич за навигацију помоћу покрета"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Ротирајте уређај"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Ротирајте уређај да бисте довршили водич за навигацију помоћу покрета"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Обавезно превуците од саме десне или леве ивице"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Осетљивост пок. за назад можете да промените у Подешавањима"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Превуците да бисте се вратили уназад"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Да бисте се вратили на последњи екран, превуците од леве или десне ивице до средине екрана."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Да бисте се вратили на последњи екран, превуците помоћу два прста од леве или десне ивице до средине екрана."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Назад"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Превуците од леве или десне ивице до средине екрана"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Обавезно превуците нагоре од доње ивице екрана"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Довршили сте покрет за повратак на почетну страницу."</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Превуците да бисте отишли на почетну страницу"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Превуците нагоре од дна екрана. Овај покрет вас увек води на почетни екран."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Превуците помоћу два прста нагоре од дна екрана. Овим покретом увек отварате почетни екран."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Идите на почетни екран"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Превуците нагоре са доњег дела екрана"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Одлично!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Довршили сте покрет за промену апликација"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Превуците да бисте заменили апликације"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"За прелазак са једне апликације на другу превуците нагоре од дна екрана, задржите, па пустите."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"За прелазак између апликација превуците помоћу два прста нагоре од дна екрана, задржите, па пустите."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Пређите на другу апликацију"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Превуците нагоре од дна екрана, задржите, па пустите"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Одлично!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Трака задатака је приказана"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Приказ задатака/облачића лево"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Приказ задатака/облачића десно"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Трака задатака је скривена"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Скривени задаци/облачићи"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Трака за навигацију"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Увек приказуј траку задатака"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Промени режим навигације"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Преклопна трака задатака"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Премести горе лево"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Премести доле десно"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{додатна апликација}one{додатна апликација}few{додатне апликације}other{додатних апликација}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Рачунар"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> и <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"проширите облачић <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"скупите облачић <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Претрага заокруживањем"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Икона апликације"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Назив апликације"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Дугме Затвори"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sv/strings.xml b/quickstep/res/values-sv/strings.xml
index aae3cce..5713036 100644
--- a/quickstep/res/values-sv/strings.xml
+++ b/quickstep/res/values-sv/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Fritt format"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Skrivbordsläge"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Flytta till extern skärm"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Stäng"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Skrivbordsläge"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Listan är tom"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Inställningar för appanvändning"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Rensa alla"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Senaste apparna"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Uppgiften har stängts"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> kvar i dag"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Appförslag har aktiverats"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Appförslag har inaktiverats"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Appförslag: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Guide för navigering med rörelser"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Rotera enheten"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Rotera enheten för att slutföra guiden för navigering med rörelser"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Se till att du sveper ända från högerkanten eller vänsterkanten"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Öppna inställningarna om du vill ändra rörelsens känslighet"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Svep för att återgå"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Återgå till den senaste skärmen genom att svepa från skärmens vänster- eller högerkant till mitten."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Gå tillbaka till den senaste skärmen genom att med två fingrar svepa mot mitten av skärmen från vänster eller höger kant."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Tillbaka"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Svep från den högra eller vänstra kanten till mitten av skärmen"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Se till att du sveper uppåt från nederkanten av skärmen"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Du är klar med rörelsen för att öppna startskärmen"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Svep för att öppna startskärmen"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Svep uppåt från skärmens nederkant. Du kan alltid återgå till startskärmen med den här rörelsen."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Svep uppåt med två fingrar från skärmens nederkant. Så kommer du alltid tillbaka till startskärmen."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Öppna startskärmen"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Svep uppåt från skärmens nederkant"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Bra jobbat!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Du är klar med rörelsen för att byta mellan appar"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Svep för att byta mellan appar"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Byt mellan appar genom att svepa uppåt från skärmens nederkant. Håll fingret nedtryckt och släpp."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Byta mellan appar: Svep uppåt med två fingrar från skärmens nederkant, håll kvar och släpp sedan."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Byt app"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Svep uppåt från skärmens nederkant. Håll fingret nedtryckt och släpp sedan"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Bra gjort!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Aktivitetsfältet visas"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Vänster fält och bubblor visas"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Höger fält och bubblor visas"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Aktivitetsfältet är dolt"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Fält och bubblor dolda"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigeringsfält"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Visa alltid aktivitetsfältet"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Ändra navigeringsläge"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Fler alternativ för aktivitetsfältet"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Flytta högst upp/till vänster"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Flytta längst ned/till höger"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{app till}other{appar till}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Skrivbordsläge"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> och <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"utöka <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"komprimera <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Appikon"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Apptitel"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Knappen Stäng"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sw/strings.xml b/quickstep/res/values-sw/strings.xml
index d872287..f1dbcbc 100644
--- a/quickstep/res/values-sw/strings.xml
+++ b/quickstep/res/values-sw/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Muundo huru"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Kompyuta ya mezani"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Hamishia programu kwenye skrini ya nje"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Funga"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Kompyuta ya Mezani"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Hakuna vipengee vya hivi karibuni"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Mipangilio ya matumizi ya programu"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Ondoa zote"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Programu za hivi karibuni"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Jukumu Limefungwa"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; dak 1"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Umebakisha <xliff:g id="TIME">%1$s</xliff:g> leo"</string>
@@ -47,8 +47,9 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Mapendekezo ya programu yamewashwa"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Umezima mapendekezo ya programu"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Programu iliyotabiriwa: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Mafunzo ya Usogezaji kwa Kutumia Miguso"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Zungusha kifaa chako"</string>
-    <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Tafadhali zungusha kifaa chako ili ukamilishe mafunzo ya usogezaji kwa kutumia ishara"</string>
+    <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Tafadhali zungusha kifaa chako ili ukamilishe mafunzo ya usogezaji kwa kutumia miguso"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Hakikisha unatelezesha kidole kutoka ukingo wa kulia au kushoto kabisa"</string>
     <string name="back_gesture_feedback_cancelled" msgid="762621530959111290">"Hakikisha unatelezesha kidole kutoka ukingo wa kulia au kushoto hadi katikati ya skrini na uachilie"</string>
     <string name="back_gesture_feedback_complete_with_overview_follow_up" msgid="9176400654037014471">"Umejifunza jinsi ya kutelezesha kidole kuanzia kulia ili kurudi nyuma. Sasa jifunze jinsi ya kubadilisha programu."</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Kubadilisha hisi ya ishara ya nyuma, nenda kwenye Mipangilio"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Telezesha kidole ili urudi nyuma"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Ili urudi kwenye skrini iliyotangulia, telezesha kidole kuanzia ukingo wa kushoto au wa kulia kuelekea katikati ya skrini."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Ili urudi kwenye skrini iliyopita, telezesha vidole viwili kuanzia ukingo wa kushoto au wa kulia kuelekea katikati ya skrini."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Rudi nyuma"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Telezesha kidole kutoka ukingo wa kushoto au kulia hadi katikati ya skrini"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Hakikisha unatelezesha kidole juu kuanzia ukingo wa chini wa skrini"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Umeweka ishara ya kwenda kwenye skrini ya kwanza"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Telezesha kidole ili uende kwenye skrini ya kwanza"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Telezesha kidole juu kuanzia chini ya skrini yako. Ishara hii kila wakati hukupeleka kwenye Skrini ya kwanza."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Telezesha vidole viwili kuelekea juu kuanzia sehemu ya chini ya skrini. Ishara hii kila wakati hukupeleka kwenye Skrini ya kwanza."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Nenda kwenye ukurasa wa mwanzo"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Telezesha kidole juu kutoka sehemu ya chini ya skrini yako"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Kazi nzuri!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Umeweka ishara ya kubadilisha programu"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Telezesha kidole ili ubadilishe programu"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Ili ubadili kati ya programu, telezesha kidole juu kuanzia sehemu ya chini ya skrini yako, ushikilie, kisha uachilie."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Ili ubadilishe kati ya programu, telezesha vidole viwili kuelekea juu kuanzia sehemu ya chini ya skrini yako, ushikilie, kisha uachilie."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Badilisha programu"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Telezesha kidole juu kutoka sehemu ya chini ya skrini yako, shikilia kisha uachilie"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Hongera!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Upauzana umeonyeshwa"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Upauzana na viputo vinaonyeshwa kushoto"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Upauzana na viputo vinaonyeshwa kulia"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Upauzana umefichwa"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Upauzana na viputo vimefichwa"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Sehemu ya viungo muhimu"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Onyesha Zana kila wakati"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Badilisha hali ya usogezaji"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Upauzana wa Vipengele vya Ziada"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Sogeza juu/kushoto"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Sogeza chini/kulia"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{programu nyingine}other{programu zingine}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"panua <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"kunja <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Chora Mviringo ili Kutafuta"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Aikoni ya programu"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Kichwa cha programu"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Kitufe cha kufunga"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-sw600dp-land/dimens.xml b/quickstep/res/values-sw600dp-land/dimens.xml
index 49239aa..cf7ba00 100644
--- a/quickstep/res/values-sw600dp-land/dimens.xml
+++ b/quickstep/res/values-sw600dp-land/dimens.xml
@@ -16,7 +16,7 @@
 -->
 <resources>
     <!-- All Set page -->
-    <dimen name="allset_page_margin_horizontal">48dp</dimen>
+    <dimen name="allset_page_padding_horizontal">48dp</dimen>
 
     <!-- Gesture Tutorial menu page -->
     <dimen name="gesture_tutorial_menu_padding_horizontal">48dp</dimen>
@@ -27,12 +27,6 @@
     <dimen name="gesture_tutorial_menu_done_button_top_spacing">40dp</dimen>
     <dimen name="gesture_tutorial_menu_back_shape_bottom_margin">49dp</dimen>
 
-    <!-- Grid Only Overview -->
-    <!-- The top margin above the top row of tasks in grid only overview -->
-    <dimen name="overview_top_margin_grid_only">24dp</dimen>
-    <!-- The bottom margin above the bottom row of tasks in grid only overview -->
-    <dimen name="overview_bottom_margin_grid_only">40dp</dimen>
-
     <dimen name="taskbar_suw_insets">24dp</dimen>
 
 </resources>
diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml
index e24d8fe..3e72651 100644
--- a/quickstep/res/values-sw600dp/dimens.xml
+++ b/quickstep/res/values-sw600dp/dimens.xml
@@ -33,20 +33,11 @@
     <dimen name="overview_page_spacing">36dp</dimen>
     <!--  The space to the left and to the right of the "Clear all" button  -->
     <dimen name="overview_grid_side_margin">64dp</dimen>
-    <!-- The top margin above the top row of tasks in grid only overview -->
-    <dimen name="overview_top_margin_grid_only">80dp</dimen>
-    <!-- The bottom margin above the bottom row of tasks in grid only overview -->
-    <dimen name="overview_bottom_margin_grid_only">80dp</dimen>
     <!--  Overview actions  -->
     <dimen name="overview_actions_top_margin">24dp</dimen>
 
     <!-- All Set page -->
-    <dimen name="allset_page_margin_horizontal">120dp</dimen>
+    <dimen name="allset_page_padding_horizontal">120dp</dimen>
     <dimen name="allset_page_allset_text_size">38sp</dimen>
     <dimen name="allset_page_swipe_up_text_size">15sp</dimen>
-
-    <!-- Splitscreen -->
-    <!-- Max width of the split instructions view -->
-    <dimen name="split_instructions_view_max_width">300dp</dimen>
-
 </resources>
diff --git a/quickstep/res/values-ta/strings.xml b/quickstep/res/values-ta/strings.xml
index 7bbfaba..04dfc30 100644
--- a/quickstep/res/values-ta/strings.xml
+++ b/quickstep/res/values-ta/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"குறிப்பிட்ட வடிவமில்லாத பயன்முறை"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"டெஸ்க்டாப்"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"வெளிப்புற டிஸ்ப்ளேவிற்கு நகர்த்துதல்"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"மூடு"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"டெஸ்க்டாப்"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"சமீபத்தியவை எதுவுமில்லை"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ஆப்ஸ் உபயோக அமைப்புகள்"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"எல்லாம் அழி"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"சமீபத்திய ஆப்ஸ்"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"பணி முடிந்தது"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 நி"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"இன்று <xliff:g id="TIME">%1$s</xliff:g> மீதமுள்ளது"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ஆப்ஸ் பரிந்துரைகள் இயக்கப்பட்டுள்ளன"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ஆப்ஸ் பரிந்துரைகள் முடக்கப்பட்டுள்ளன"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"கணித்த ஆப்ஸ்: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"சைகை வழிசெலுத்தலுக்கான பயிற்சி"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"உங்கள் சாதனத்தைச் சுழற்றுங்கள்"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"சைகை வழிசெலுத்தல் பயிற்சியை நிறைவுசெய்ய உங்கள் சாதனத்தைச் சுழற்றுங்கள்"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"வலது அல்லது இடது ஓரத்தின் விளிம்பிலிருந்து ஸ்வைப் செய்வதை உறுதிசெய்துகொள்ளுங்கள்"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"பின்செல் சைகையின் உணர்திறனை மாற்ற அமைப்புகளுக்குச் செல்க"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"பின்செல்ல ஸ்வைப் செய்யுங்கள்"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"முந்தைய திரைக்கு மீண்டும் செல்ல, இடது/வலது ஓரத்திலிருந்து திரையின் மையப் பகுதிக்கு ஸ்வைப் செய்க."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"முந்தைய திரைக்கு மீண்டும் செல்ல, 2 விரல்களால் இடது அல்லது வலது ஓரத்திலிருந்து திரையின் மையப் பகுதிக்கு ஸ்வைப் செய்யுங்கள்."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"பின்செல்லுதல்"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"வலது அல்லது இடது ஓரத்திலிருந்து திரையின் மையப் பகுதிக்கு ஸ்வைப் செய்யுங்கள்"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"திரையின் கீழ் ஓரத்திலிருந்து மேல்நோக்கி ஸ்வைப் செய்வதை உறுதிசெய்துகொள்ளுங்கள்"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"முகப்புக்குச் செல் சைகைப் பயிற்சியை நிறைவுசெய்துவிட்டீர்கள்"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"முகப்புக்குச் செல்ல ஸ்வைப் செய்யுங்கள்"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"திரையின் கீழிருந்து மேலாக ஸ்வைப் செய்க. இந்தச் சைகை எப்போதும் முகப்புத் திரைக்கு அழைத்துச் செல்லும்."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"2 விரலால் திரையின் கீழிருந்து மேலாக ஸ்வைப் செய்க. இந்தச் சைகை முகப்புத் திரைக்கு அழைத்துச் செல்லும்."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"முகப்புக்குச் செல்லுதல்"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"திரையின் கீழிருந்து மேல்நோக்கி ஸ்வைப் செய்யுங்கள்"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"அருமை!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"ஆப்ஸுக்கிடையே மாறும் சைகைப் பயிற்சியை நிறைவுசெய்துவிட்டீர்கள்"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"ஆப்ஸுக்கிடையே மாற ஸ்வைப் செய்யுங்கள்"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"ஆப்ஸுக்கு இடையே மாற, திரையின் கீழிலிருந்து மேலாக ஸ்வைப் செய்து, பிடித்திருந்து, பிறகு விடுவிக்கவும்."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"ஆப்ஸுக்கிடையே மாற, திரையின் கீழிருந்து மேலாக 2 விரலால் ஸ்வைப் செய்து, பிடித்து, பிறகு விடுவிக்கவும்."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"ஆப்ஸுக்கிடையே மாறுதல்"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"உங்கள் திரையின் கீழ்ப்பகுதியில் இருந்து மேலே ஸ்வைப் செய்து, பிடித்து, பிறகு விடுவியுங்கள்"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"அருமை!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"செயல் பட்டி காட்டப்படுகிறது"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"செயல் பட்டி &amp; குமிழை இடதுபுறம் காட்டும்"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"செயல் பட்டி &amp; குமிழை வலதுபுறம் காட்டும்"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"செயல் பட்டி மறைக்கப்பட்டுள்ளது"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"செயல் பட்டி &amp; குமிழை மறைக்கும்"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"வழிசெலுத்தல் பட்டி"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"செயல் பட்டியை எப்போதும் காட்டு"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"வழிசெலுத்தல் பயன்முறையை மாற்று"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"செயல் பட்டிக்கான கூடுதல் விருப்பங்கள்"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"மேலே/இடதுபுறம் நகர்த்தும்"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"கீழே/வலதுபுறம் நகர்த்தும்"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{கூடுதல் ஆப்ஸ்}other{கூடுதல் ஆப்ஸ்}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"டெஸ்க்டாப்"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> மற்றும் <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ஐ விரிவாக்கும்"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> ஐச் சுருக்கும்"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"வட்டமிட்டுத் தேடல்"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ஆப்ஸ் ஐகான்"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ஆப்ஸ் தலைப்பு"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"மூடுவதற்கான பட்டன்"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-te/strings.xml b/quickstep/res/values-te/strings.xml
index 5439e80..516c2f7 100644
--- a/quickstep/res/values-te/strings.xml
+++ b/quickstep/res/values-te/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"సంప్రదాయేతర"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"డెస్క్‌టాప్"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"ఎక్స్‌టర్నల్ డిస్‌ప్లేకు తరలించండి"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"మూసివేయండి"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"డెస్క్‌టాప్"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ఇటీవలి ఐటెమ్‌లు ఏవీ లేవు"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"యాప్ వినియోగ సెట్టింగ్‌లు"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"అన్నీ తీసివేయండి"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"ఇటీవలి యాప్‌లు"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"టాస్క్ మూసివేయబడింది"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 నిమిషం"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"నేటికి <xliff:g id="TIME">%1$s</xliff:g> మిగిలి ఉంది"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"యాప్ సలహాలు ఎనేబుల్ చేయబడ్డాయి"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"యాప్ సూచ‌న‌లు డిజేబుల్‌ చేయబడ్డాయి"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"సూచించబడిన యాప్: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"సంజ్ఞ నావిగేషన్ ట్యుటోరియల్"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"మీ పరికరాన్ని రొటేట్ చేయండి"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"సంజ్ఞ నావిగేషన్ ట్యుటోరియల్‌ను పూర్తి చేయడానికి దయచేసి మీ పరికరాన్ని రొటేట్ చేయండి"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"కుడి వైపు చిట్ట చివరి లేదా ఎడమ వైపు చిట్ట చివరి అంచు నుండి స్వైప్ చేస్తున్నారని నిర్ధారించుకోండి"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"వెనుక సంజ్ఞ సున్నితత్వం మార్చడానికి, సెట్టింగ్‌లకు వెళ్లండి"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"వెనుకకు వెళ్ళడం కోసం స్వైప్ చేయండి"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"మునుపటి స్క్రీన్‌కు తిరిగి వెళ్లడానికి, ఎడమ లేదా కుడి అంచు నుండి స్క్రీన్ మధ్యలోకి స్వయిప్ చేయండి."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"గత స్క్రీన్‌కు తిరిగి వెళ్లడానికి, ఎడమ లేదా కుడి అంచు నుండి స్క్రీన్ మధ్యలోకి 2 వేళ్లతో స్వైప్ చేయండి."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"వెనుకకు వెళ్లండి"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"ఎడమ లేదా కుడి అంచు నుండి స్క్రీన్ మధ్యకు స్వైప్ చేయండి"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"మీరు స్క్రీన్ దిగువ అంచు నుండి పైకి స్వైప్ చేశారని నిర్ధారించుకోండి"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"మీరు మొదటి స్క్రీన్‌కు వెళ్లే సంజ్ఞను పూర్తి చేశారు"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"మొదటి స్క్రీన్‌కు వెళ్లడానికి స్వైప్ చేయండి"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"స్క్రీన్ కింది నుండి పైకి స్వైప్ చేయండి. ఈ సంజ్ఞ ఎప్పుడూ మిమ్మల్ని మొదటి స్క్రీన్‌కు తీసుకెళ్తుంది."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"స్క్రీన్ కింది నుండి 2 వేళ్లతో పైకి స్వైప్ చేయండి. సంజ్ఞ ఎల్లప్పుడూ మొదటి స్క్రీన్‌కు తీసుకెళ్తుంది."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"మొదటి ట్యాబ్‌కు వెళ్లండి"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"స్క్రీన్ కింది భాగం నుండి పైకి స్వైప్ చేయండి"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"బాగా చేశారు!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"మీరు \'యాప్‌ల మధ్య మార్పు\' సంజ్ఞను పూర్తి చేశారు"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"యాప్‌ల మధ్య మార్చడం కోసం స్వైప్ చేయండి"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"యాప్‌ల మధ్య మారడానికి, మీ స్క్రీన్ కింది వైపు నుండి పైకి స్వైప్ చేసి, పట్టుకుని, తర్వాత వదలండి."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"యాప్‌ల మధ్య మారడానికి, మీ స్క్రీన్ కింది నుండి 2 వేళ్లతో పైకి స్వైప్ చేసి, నొక్కి పట్టి, వదలండి."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"యాప్‌ల మధ్య స్విచ్ అవ్వండి"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"మీ స్క్రీన్ కింది వైపు నుండి పైకి స్వైప్ చేసి, పట్టుకుని, తర్వాత వదలండి"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"చాలా బాగా చేశారు!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"టాస్క్‌బార్ చూపబడింది"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"టాస్క్‌బార్, బబుల్స్ ఎడమవైపున చూపబడ్డాయి"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"టాస్క్‌బార్, బబుల్స్ కుడివైపున చూపబడ్డాయి"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"టాస్క్‌బార్ దాచబడింది"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"టాస్క్‌బార్, బబుల్స్ దాచబడినవి"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"నావిగేషన్ బార్"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"టాస్క్‌బార్‌ను నిరంతరం చూపండి"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"నావిగేషన్ మోడ్‌ను మార్చండి"</string>
@@ -143,6 +139,7 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"టాస్క్‌బార్ ఓవర్‌ఫ్లో"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ఎగువ/ఎడమ వైపునకు తరలించండి"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"దిగువ/కుడి వైపునకు తరలించండి"</string>
+    <string name="open_app_as_a_bubble" msgid="6642626287247807473">"యాప్‌ను బబుల్‌లాగా తెరవండి"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{మరో యాప్‌}other{మరిన్ని యాప్‌లు}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"డెస్క్‌టాప్"</string>
     <string name="quick_switch_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>
@@ -156,4 +153,9 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ను విస్తరించండి"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ను కుదించండి"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"సెర్చ్ చేయడానికి సర్కిల్ గీయండి"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"యాప్ చిహ్నం"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"యాప్ టైటిల్"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"\'మూసివేయండి\' బటన్"</string>
+    <string name="pin_to_taskbar" msgid="6607778046321626950">"టాస్క్‌బార్‌కు పిన్"</string>
+    <string name="unpin_from_taskbar" msgid="2178811773165572676">"టాస్క్‌బార్ అన్‌పిన్"</string>
 </resources>
diff --git a/quickstep/res/values-th/strings.xml b/quickstep/res/values-th/strings.xml
index 30e73e6..04eac38 100644
--- a/quickstep/res/values-th/strings.xml
+++ b/quickstep/res/values-th/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"รูปแบบอิสระ"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"เดสก์ท็อป"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"ย้ายไปยังจอแสดงผลภายนอก"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"ปิด"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"เดสก์ท็อป"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"ไม่มีรายการล่าสุด"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"การตั้งค่าการใช้แอป"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"ล้างทั้งหมด"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"แอปล่าสุด"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"ปิดงานแล้ว"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt;1 นาที"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"วันนี้เหลืออีก <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"เปิดใช้แอปแนะนำแล้ว"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ปิดใช้คำแนะนำเกี่ยวกับแอปอยู่"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"แอปที่คาดว่าจะใช้: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"บทแนะนำการไปยังส่วนต่างๆ ด้วยท่าทางสัมผัส"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"หมุนอุปกรณ์ของคุณ"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"โปรดหมุนอุปกรณ์เพื่อทำตามบทแนะนำการนำทางด้วยท่าทางสัมผัสให้เสร็จสมบูรณ์"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"ปัดจากขอบด้านขวาสุดหรือซ้ายสุด"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"เปลี่ยนความไวของท่าทางสัมผัสเพื่อย้อนกลับได้ที่การตั้งค่า"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"ปัดเพื่อย้อนกลับ"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"หากต้องการย้อนกลับไปที่หน้าจอล่าสุด ให้ปัดจากขอบด้านซ้ายหรือขวาไปตรงกลางหน้าจอ"</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"หากต้องการย้อนกลับไปที่หน้าจอล่าสุด ให้ใช้ 2 นิ้วปัดจากขอบด้านซ้ายหรือขวาไปตรงกลางหน้าจอ"</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"ย้อนกลับ"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"ปัดจากขอบด้านซ้ายหรือขวาไปตรงกลางหน้าจอ"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"ปัดขึ้นจากขอบด้านล่างของหน้าจอ"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"คุณทำท่าทางสัมผัสเพื่อไปที่หน้าแรกสำเร็จแล้ว"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"ปัดเพื่อไปที่หน้าแรก"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"ปัดขึ้นจากด้านล่างของหน้าจอ ท่าทางสัมผัสนี้จะนำคุณไปที่หน้าจอหลักเสมอ"</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"ใช้ 2 นิ้วปัดขึ้นจากด้านล่างของหน้าจอ ท่าทางสัมผัสนี้จะนำคุณไปที่หน้าจอหลักเสมอ"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"ไปที่หน้าจอหลัก"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"ปัดขึ้นจากด้านล่างของหน้าจอ"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"เก่งมาก"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"คุณทำท่าทางสัมผัสเพื่อสลับแอปสำเร็จแล้ว"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"ปัดเพื่อสลับแอป"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"หากต้องการสลับระหว่างแอปต่างๆ ให้ปัดขึ้นจากด้านล่างของหน้าจอ ค้างไว้ แล้วปล่อย"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"หากต้องการสลับระหว่างแอป ให้ใช้ 2 นิ้วปัดขึ้นจากด้านล่างของหน้าจอค้างไว้แล้วปล่อย"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"เปลี่ยนแอป"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"ปัดขึ้นจากด้านล่างของหน้าจอ ค้างไว้ แล้วปล่อย"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"เยี่ยมมาก"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"แถบงานแสดงอยู่"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"แถบงานและบับเบิลแสดงไว้ทางซ้าย"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"แถบงานและบับเบิลแสดงไว้ทางขวา"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"แถบงานซ่อนอยู่"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"แถบงานและบับเบิลซ่อนอยู่"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"แถบนำทาง"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"แสดงแถบงานเสมอ"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"เปลี่ยนโหมดการนําทาง"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"การดำเนินการเพิ่มเติมของแถบงาน"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"ย้ายไปที่ด้านบนหรือด้านซ้าย"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"ย้ายไปที่ด้านล่างหรือด้านขวา"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{แอปเพิ่มเติม}other{แอปเพิ่มเติม}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"เดสก์ท็อป"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> และ <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"ขยาย <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"ยุบ <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"วงเพื่อค้นหา"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ไอคอนแอป"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ชื่อแอป"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"ปุ่มปิด"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-tl/strings.xml b/quickstep/res/values-tl/strings.xml
index 583f419..89ead25 100644
--- a/quickstep/res/values-tl/strings.xml
+++ b/quickstep/res/values-tl/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Freeform"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Ilipat sa external na display"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Isara"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Walang kamakailang item"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Mga setting ng paggamit ng app"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"I-clear lahat"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Mga kamakailang app"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Isinara ang Gawain"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 min"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> na lang ngayon"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Naka-enable ang mga iminumungkahing app"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Naka-disable ang mga iminumungkahing app"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Hinulaang app: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Tutorial sa Navigation gamit ang Galaw"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"I-rotate ang iyong device"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Paki-rotate ang iyong device para tapusin ang tutorial sa navigation gamit ang galaw"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Tiyaking magsa-swipe ka mula sa dulong kanan o dulong kaliwang gilid"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Pumunta sa Settings para baguhin ang sensitivity ng pagbalik"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Mag-swipe para bumalik"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Para bumalik sa nakaraang screen, mag-swipe mula sa kaliwa o kanang gilid patungo sa gitna ng screen."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Para bumalik sa huling screen, mag-swipe gamit ang 2 daliri mula sa kaliwa o kanang gilid hanggang sa gitna ng screen."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Bumalik"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Mag-swipe mula sa kaliwa o kanang gilid papunta sa gitna ng screen"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Tiyaking magsa-swipe ka pataas mula sa pinakaibaba ng screen"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Nakumpleto mo na ang galaw para pumunta sa home"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Mag-swipe para pumunta sa home"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Mag-swipe pataas mula sa ibaba ng iyong screen. Dadalhin ka palagi ng galaw na ito sa Home screen."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Mag-swipe pataas gamit ang 2 daliri mula sa ibaba ng screen. Dadalhin ka palagi nito sa Home screen."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Pumunta sa home"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Mag-swipe pataas mula sa ibabang bahagi ng iyong screen"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Magaling!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Nakumpleto mo na ang galaw para magpalipat-lipat sa mga app"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Mag-swipe para lumipat ng app"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Para lumipat ng app, mag-swipe pataas mula sa ibaba ng iyong screen, mag-hold, at iangat ang daliri."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Para lumipat ng app, mag-swipe pataas gamit ang 2 daliri mula sa ibaba, mag-hold, at bumitaw."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Lumipat ng app"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Mag-swipe pataas mula sa ibaba ng iyong screen, i-hold ito saka bitawan"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Magaling!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Ipinapakita ang taskbar"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Taskbar at bubble sa kaliwa"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Taskbar at bubble sa kanan"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Nakatago ang taskbar"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Nakatago ang taskbar at bubble"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigation bar"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Ipakita lagi ang Taskbar"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Magpalit ng navigation mode"</string>
@@ -143,6 +139,7 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Taskbar Overflow"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Ilipat sa itaas/kaliwa"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Ilipat sa ibaba/kanan"</string>
+    <string name="open_app_as_a_bubble" msgid="6642626287247807473">"Buksan ang app bilang bubble"</string>
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{pang app}one{pang app}other{pang app}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Desktop"</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>
@@ -156,4 +153,9 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"i-expand ang <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"i-collapse ang <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Circle to Search"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Icon ng app"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Pamagat ng app"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Button na isara"</string>
+    <string name="pin_to_taskbar" msgid="6607778046321626950">"I-pin sa taskbar"</string>
+    <string name="unpin_from_taskbar" msgid="2178811773165572676">"I-unpin sa taskbar"</string>
 </resources>
diff --git a/quickstep/res/values-tr/strings.xml b/quickstep/res/values-tr/strings.xml
index 6a0b78f..f2c42d0 100644
--- a/quickstep/res/values-tr/strings.xml
+++ b/quickstep/res/values-tr/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Serbest çalışma"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Masaüstü"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Harici ekrana taşı"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Kapat"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Masaüstü"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Yeni öğe yok"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Uygulama kullanım ayarları"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Tümünü temizle"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Son uygulamalar"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Görev Kapatıldı"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 dk."</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Bugün <xliff:g id="TIME">%1$s</xliff:g> kaldı"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Uygulama önerileri etkinleştirildi"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Uygulama önerileri devre dışı bırakıldı"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Tahmin edilen uygulama: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Hareketle Gezinme Eğitimi"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Cihazınızı döndürün"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Hareketle gezinme eğitimini tamamlamak için lütfen cihazınızı döndürün"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"En sağ veya en sol kenardan kaydırdığınızdan emin olun"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Geri hareketinin hassasiyetini değiştirmek için Ayarlar\'a gidin"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Geri dönmek için kaydırma"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Son ekrana geri gitmek için sol veya sağ kenardan ekranın ortasına doğru kaydırın."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Son ekrana geri gitmek için sol veya sağ kenardan ekranın ortasına doğru 2 parmağınızla kaydırın."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Geri dönme"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Sol veya sağ kenardan ekranın ortasına doğru kaydırın"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Ekranın alt kenarından yukarı kaydırdığınızdan emin olun"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Ana ekrana git hareketini tamamladınız"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Ana ekrana gitmek için kaydırma"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Ekranın alt kısmından yukarıya doğru kaydırın. Bu hareket sizi her zaman Ana ekrana götürür."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Ekranın alt kısmından 2 parmağınızla yukarı kaydırın. Bu hareket sizi her zaman Ana ekrana götürür."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Ana sayfaya gitme"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Parmağınızı ekranın alt kısmından yukarıya doğru kaydırın"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Tebrikler!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Uygulamalar arasında geçiş yapma hareketini tamamladınız"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Uygulamalar arasında geçiş yapmak için kaydırma"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Uygulamalar arasında geçiş yapmak için ekranınızın altından yukarı kaydırıp basılı tutun ve sonra bırakın."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Uygulamalara geçiş yapmak için ekranın altından 2 parmakla yukarı kaydırıp basılı tutun ve bırakın."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Uygulamalar arasında geçiş yapma"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Ekranınızın alt tarafından yukarı doğru kaydırın, tutun ve sonra bırakın"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Tebrikler!"</string>
@@ -92,7 +90,7 @@
     <string name="allset_title" msgid="5021126669778966707">"Kurulum tamamlandı"</string>
     <string name="allset_hint" msgid="459504134589971527">"Ana ekrana gitmek için yukarı kaydırın"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Ana ekranınıza gitmek için ana sayfa düğmesine dokunun"</string>
-    <string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> adlı cihazınızı kullanmaya hazırsınız"</string>
+    <string name="allset_description_generic" msgid="5385500062202019855">"Artık <xliff:g id="DEVICE">%1$s</xliff:g> kullanılmak için hazır"</string>
     <string name="default_device_name" msgid="6660656727127422487">"cihaz"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Sistem gezinme ayarları"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"Paylaş"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Görev çubuğu gösteriliyor"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Görev çubuğu ve baloncuklar solda gösteriliyor"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Görev çubuğu ve baloncuklar sağda gösteriliyor"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Görev çubuğu gizlendi"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Görev çubuğu ve baloncuklar gizli"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Gezinme çubuğu"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Görev çubuğunu daima göster"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Gezinme modunu değiştir"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Görev Çubuğu Taşması"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Sol üste taşı"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Sağ alta taşı"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{uygulama daha}other{uygulama daha}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Masaüstü"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"genişlet: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"daralt: <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Seçerek Arat"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Uygulama simgesi"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Uygulama başlığı"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Kapat düğmesi"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-uk/strings.xml b/quickstep/res/values-uk/strings.xml
index d75f8b4..82d8a60 100644
--- a/quickstep/res/values-uk/strings.xml
+++ b/quickstep/res/values-uk/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Довільна форма"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Робочий стіл"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Перемістити на зовнішній екран"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Закрити"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Комп’ютер"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Немає нещодавніх додатків"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Налаштування використання додатка"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Очистити все"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Нещодавні додатки"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Завдання закрито"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 хв"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Сьогодні залишилося <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Рекомендовані додатки ввімкнено"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Рекомендовані додатки вимкнено"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Передбачений додаток: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Посібник із навігації за допомогою жестів"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Оберніть пристрій"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Обертайте пристрій, щоб ознайомитися з посібником із навігації за допомогою жестів"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Проведіть пальцем від самого краю екрана (правого або лівого)"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Щоб змінити чутливість жесту \"Назад\", відкрийте налаштування"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Щоб повернутися, проведіть пальцем по екрану"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Щоб перейти на попередній екран, проведіть пальцем від лівого чи правого краю до середини екрана."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Щоб перейти на попередній екран, проведіть двома пальцями від лівого чи правого краю до середини екрана."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Повернення на попередній екран"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Проведіть пальцем від лівого чи правого краю до середини екрана"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Проведіть пальцем угору від нижнього краю екрана"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Ви виконали жест переходу на головний екран"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Проведіть пальцем, щоб перейти на головний екран"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Проведіть пальцем по екрану знизу вгору. Цей жест завжди повертатиме вас на головний екран."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Проведіть двома пальцями вгору від низу екрана. Цей жест завжди спрямовує вас на головний екран."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Перехід на головний екран"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Проведіть пальцем угору від низу екрана"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Чудово!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Ви виконали жест переходу в інший додаток"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Проведіть пальцем, щоб перейти в інший додаток"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Щоб переключатися між додатками, проведіть знизу вгору по екрану, утримуйте палець, а потім відпустіть."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Щоб перейти в інший додаток, проведіть 2 пальцями від низу екрана, потримайте й відпустіть палець."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Перемикання між додатками"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Проведіть пальцем знизу вгору, утримуйте палець на екрані, а потім відпустіть"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Чудово!"</string>
@@ -94,7 +92,7 @@
     <string name="allset_button_hint" msgid="2395219947744706291">"Натисніть кнопку головного екрана, щоб відкрити його"</string>
     <string name="allset_description_generic" msgid="5385500062202019855">"Тепер ви можете використовувати <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
     <string name="default_device_name" msgid="6660656727127422487">"пристрій"</string>
-    <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Системні налаштування навігації"</annotation></string>
+    <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>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Панель завдань показано"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Панель завдань і чати – зліва"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Панель завдань і чати – справа"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Панель завдань приховано"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Панель і чати приховано"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Панель навігації"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Завжди показув. панель завдань"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Змінити режим навігації"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Додаткове меню панелі завдань"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Перемістити вгору або вліво"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Перемістити вниз або вправо"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{інший додаток}one{інший додаток}few{інші додатки}many{інших додатків}other{іншого додатка}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Комп’ютер"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> та <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"розгорнути \"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>\""</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"згорнути \"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>\""</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Обвести й знайти"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Значок додатка"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Назва додатка"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Кнопка \"Закрити\""</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-ur/strings.xml b/quickstep/res/values-ur/strings.xml
index 68f838f..65436ae 100644
--- a/quickstep/res/values-ur/strings.xml
+++ b/quickstep/res/values-ur/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"فری فارم"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"ڈیسک ٹاپ"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"بیرونی ڈسپلے پر متقل کریں"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"بند کریں"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"ڈیسک ٹاپ"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"کوئی حالیہ آئٹم نہیں"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"ایپ کے استعمال کی ترتیبات"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"سبھی کو صاف کریں"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"حالیہ ایپس"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"ٹاسک بند ہے"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>،<xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"‏&lt; 1 منٹ"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"آج <xliff:g id="TIME">%1$s</xliff:g> بچا ہے"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"ایپ کی تجاویز فعال ہیں"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"ایپ کی تجاویز غیر فعال ہیں"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"پیشن گوئی کردہ ایپ: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"اشاروں والی نیویگیشن ٹیوٹوریل"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"اپنا آلہ گھمائیں"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"براہ کرم اشاروں والی نیویگیشن کا ٹیوٹوریل مکمل کرنے کے لیے اپنا آلہ گھمائیں"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"یقینی بنائیں کہ آپ دائیں یا بائیں کنارے سے دور سے سوائپ کریں"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"پچھلے اشارے کی حساسیت تبدیل کرنے کے لیے ترتیبات پر جائیں"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"واپس جانے کے لیے سوائپ کریں"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"پچھلی اسکرین پر واپس جانے کے لیے بائیں یا دائیں کنارے سے اسکرین کے وسط تک سوائپ کریں۔"</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"آخری اسکرین پر واپس جانے کے لیے، 2 انگلیوں سے بائیں یا دائیں کنارے سے اسکرین کے وسط تک سوائپ کریں۔"</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"واپس جائیں"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"دائیں یا بائیں کنارے سے اسکرین کے وسط تک سوائپ کریں"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"اس بات کو یقینی بنائیں کہ آپ اسکرین کے نچلے کنارے سے اوپر کی طرف سوائپ کریں"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"آپ نے ہوم پر جانے کا اشارہ مکمل کر لیا"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"ہوم پر جانے کے لیے سوائپ کریں"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"اپنی اسکرین کے نیچے سے اوپر کی طرف سوائپ کریں۔ یہ اشارہ آپ کو ہمیشہ ہوم اسکرین پر لے جاتا ہے۔"</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"اسکرین کے نیچے سے 2 انگلیوں سے اوپر سوائپ کریں۔ یہ اشارہ آپ کو ہمیشہ ہوم اسکرین پر لے جاتا ہے۔"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"ہوم پر جائیں"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"اپنی اسکرین کے نچلے حصے سے اوپر کی طرف سوائپ کریں"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"بہترین!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"آپ نے ایپس کو سوئچ کرنے کا اشارہ مکمل کر لیا"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"ایپس سوئچ کرنے کے لیے سوائپ کریں"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"ایپس کے مابین سوئچ کرنے کے لیے، اپنی اسکرین کے نچلے حصے سے اوپر کی جانب سوائپ کریں، پکڑے رکھیں، پھر چھوڑ دیں۔"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"ایپس کے مابین سوئچ کرنے کیلئے، اپنی اسکرین کے نیچے سے 2 انگلیوں سے اوپر سوائپ کریں، دبائے رکھیں پھر چھوڑ دیں۔"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"ایپس سوئچ کریں"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"اپنی اسکرین کے نچلے حصے سے اوپر کی جانب سوائپ کریں، دبائے رکھیں، پھر چھوڑ دیں"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"بہت خوب!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"ٹاشک بار دکھایا گیا"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ٹاسک بار و بلبلے بائیں طرف ہیں"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ٹاسک بار و بلبلے دائیں طرف ہیں"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"ٹاسک بار چھپایا گیا"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ٹاسک بار اور بلبلے پوشیدہ ہیں"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"نیویگیشن بار"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"ہمیشہ ٹاسک بار دکھائیں"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"نیویگیشن موڈ تبدیل کریں"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"ٹاسک بار اوورفلو"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"اوپر/بائیں طرف منتقل کریں"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"نیچے/دائیں طرف منتقل کریں"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{مزید ایپ}other{مزید ایپس}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"ڈیسک ٹاپ"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g> اور <xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> کو پھیلائیں"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g> کو سکیڑیں"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"تلاش کرنے کیلئے دائرہ بنائیں"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"ایپ آئیکن"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"ایپ کا عنوان"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"\'بند کریں\' بٹن"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-uz/strings.xml b/quickstep/res/values-uz/strings.xml
index 294be84..b761b5d 100644
--- a/quickstep/res/values-uz/strings.xml
+++ b/quickstep/res/values-uz/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Erkin shakl"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Desktop"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Tashqi displeyga olish"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Yopish"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Desktop"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Yaqinda ishlatilgan ilovalar yo‘q"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Ilovadan foydalanish sozlamalari"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Hammasini tozalash"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Oxirgi ilovalar"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Vazifalar yopildi"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 daqiqa"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Bugun <xliff:g id="TIME">%1$s</xliff:g> qoldi"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Ilova tavsiyalari yoqildi"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Endi ilova takliflari chiqmaydi"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Taklif etilgan ilova: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Ishorali navigatsiya darsligi"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Qurilmangizni buring"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Ishorali navigatsiya darsligini tugatish uchun qurilmani buring"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Ekran chetidan boshlab oʻngdan yoki chapdan suring"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Orqaga ishorasi sezuvchanligi Sozlamalardan oʻzgartiriladi"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Orqaga qaytish"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Ortga qaytish uchun barmoqni ekranning yon chekkalaridan oʻrtasigacha suring."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Oxirgi ekranga qaytish uchun 2 barmoq bilan ekranning chap yoki oʻng chekkasidan oʻrtasigacha suring."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Orqaga"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Chap yoki oʻng chetidan ekranning oʻrtasiga suring"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Barmoqni ekranning pastki chetidan yuqoriga suring."</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Bosh ekranni ochish ishorasi darsini tamomladingiz"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Svayp bilan bosh ekranni ochish"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Ekranning pastidan tepaga qarab suring. Bu ishora doim Bosh ekranni ochadi."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"2 barmoq bilan ekranning quyidan tepasiga suring. Bu ishora har doim Bosh ekranni ochadi."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Boshiga"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Ekranning quyi qismidan tepaga torting"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Barakalla!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Ilovalarni almashtirish darsini tamomladingiz"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Ilovalar orasida almashish"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Ilovalarni ochish uchun ekranning pastidan tepaga qarab suring, biroz ushlab turing va qoʻyib yuboring"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Ilovalarni almashtirish uchun 2 barmoq bilan ekranning quyidan tepasiga surib turib, qoʻyib yuboring"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Ilovalarni almashtirish"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Ekranning pastidan tepaga qarab suring, biroz ushlab turing va qoʻyib yuboring"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Juda soz!"</string>
@@ -92,7 +90,7 @@
     <string name="allset_title" msgid="5021126669778966707">"Hammasi tayyor!"</string>
     <string name="allset_hint" msgid="459504134589971527">"Boshiga qaytish uchun tepaga suring"</string>
     <string name="allset_button_hint" msgid="2395219947744706291">"Bosh ekranga oʻtish uchun bosh ekran tugmasini bosing"</string>
-    <string name="allset_description_generic" msgid="5385500062202019855">"<xliff:g id="DEVICE">%1$s</xliff:g> xizmatga tayyor"</string>
+    <string name="allset_description_generic" msgid="5385500062202019855">"Sizning <xliff:g id="DEVICE">%1$s</xliff:g> xizmatga tayyor"</string>
     <string name="default_device_name" msgid="6660656727127422487">"qurilma"</string>
     <string name="allset_navigation_settings" msgid="4713404605961476027"><annotation id="link">"Tizim navigatsiya sozlamalari"</annotation></string>
     <string name="action_share" msgid="2648470652637092375">"Ulashish"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Vazifalar paneli ochiq"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Panel va bulutchalar chapda"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Panel va bulutchalar oʻngda"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Vazifalar paneli yopiq"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Panel va bulutchalar berk"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Navigatsiya paneli"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Vazifalar paneli doim chiqarilsin"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Navigatsiya rejimini oʻzgartirish"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Vazifalar panelini kengaytirish"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Yuqoriga yoki chapga oʻtkazish"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Pastga yoki oʻngga oʻtkazish"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{boshqa ilova}other{boshqa ilovalar}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Kompyuter"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ni yoyish"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>ni yigʻish"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Chizib qidirish"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Ilova belgisi"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Ilova nomi"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Yopish tugmasi"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-vi/strings.xml b/quickstep/res/values-vi/strings.xml
index 7eeacde..383f915 100644
--- a/quickstep/res/values-vi/strings.xml
+++ b/quickstep/res/values-vi/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"Dạng tự do"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Máy tính"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Chuyển sang màn hình ngoài"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Đóng"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Máy tính"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Không có mục gần đây nào"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Cài đặt mức sử dụng ứng dụng"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Xóa tất cả"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Ứng dụng gần đây"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Đã đóng tác vụ"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 phút"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"Hôm nay còn <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Đã bật tính năng Ứng dụng đề xuất"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Tính năng Ứng dụng đề xuất bị tắt"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Ứng dụng dự đoán: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Hướng dẫn thực hiện thao tác bằng cử chỉ"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Xoay thiết bị của bạn"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Vui lòng xoay thiết bị của bạn để hoàn tất hướng dẫn thao tác bằng cử chỉ"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Hãy vuốt từ mép ngoài cùng bên phải hoặc ngoài cùng bên trái"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Để thay đổi độ nhạy của cử chỉ quay lại, hãy vào mục Cài đặt"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Vuốt để quay lại"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Để quay lại màn hình gần đây nhất, hãy vuốt từ mép trái hoặc mép phải tới chính giữa màn hình."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Để quay lại màn hình trước đó, hãy vuốt 2 ngón tay từ cạnh trái hoặc phải vào giữa màn hình."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Quay lại"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Hãy vuốt từ mép trái hoặc mép phải tới giữa màn hình"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Hãy vuốt lên từ mép dưới cùng của màn hình"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Bạn đã thực hiện xong cử chỉ chuyển đến Màn hình chính"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Vuốt để chuyển đến Màn hình chính"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Vuốt lên từ cuối màn hình. Cử chỉ này luôn đưa bạn đến Màn hình chính."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Vuốt 2 ngón tay lên từ cuối màn hình. Cử chỉ này luôn đưa bạn về Màn hình chính."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Chuyển đến màn hình chính"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Vuốt lên từ mép dưới cùng của màn hình"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Tuyệt vời!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Bạn đã thực hiện xong cử chỉ chuyển đổi ứng dụng"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Vuốt để chuyển đổi ứng dụng"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Để chuyển đổi ứng dụng, hãy vuốt lên từ cuối màn hình, giữ rồi thả ra."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Để chuyển đổi giữa các ứng dụng, hãy vuốt 2 ngón tay lên từ cuối màn hình, giữ rồi thả ra."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Chuyển đổi ứng dụng"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Vuốt lên từ mép dưới cùng của màn hình, giữ rồi nhấc ngón tay ra"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Rất tốt!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Đã hiện thanh thao tác"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"Hiện thanh tác vụ, b.bóng trái"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"Hiện thanh tác vụ, b.bóng phải"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Đã ẩn thanh thao tác"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"Đã ẩn thanh tác vụ &amp; bong bóng"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Thanh điều hướng"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Luôn hiện Thanh tác vụ"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Thay đổi chế độ điều hướng"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Trình đơn mục bổ sung trên thanh tác vụ"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Chuyển lên trên cùng/sang bên trái"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Chuyển xuống dưới cùng/sang bên phải"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{ứng dụng khác}other{ứng dụng khác}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"mở rộng <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"thu gọn <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Khoanh tròn để tìm kiếm"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Biểu tượng ứng dụng"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Tên ứng dụng"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Nút đóng"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index dc16036..17d022e 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"自由窗口"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"桌面"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"移至外接显示屏"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"关闭"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"桌面设备"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"近期没有任何内容"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"应用使用设置"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"全部清除"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"最近用过的应用"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"任务已关闭"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>(<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"不到 1 分钟"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"今天还可使用 <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"已启用应用建议"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"已停用应用建议"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"预测的应用:<xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"手势导航教程"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"请旋转设备"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"请旋转设备,完成手势导航教程"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"确保从最右侧或最左侧边缘开始滑动"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"如要调节“返回”手势的灵敏度,请转到“设置”"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"滑动即可返回"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"如要返回上一个屏幕,请从屏幕左侧或右侧边缘往屏幕中间滑动。"</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"若要返回上一个屏幕,请用两根手指从屏幕左侧或右侧边缘向中间滑动。"</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"返回"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"从屏幕左侧或右侧边缘滑动到中间"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"确保从屏幕底部边缘向上滑动"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"您完成了“转到主屏幕”手势"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"上滑可转到主屏幕"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"从屏幕底部向上滑动,即可随时回到主屏幕。"</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"用两根手指从屏幕底部向上滑动,这个手势会一律使您回到主屏幕。"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"前往主屏幕"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"从屏幕底部向上滑动"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"太棒了!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"您完成了应用切换手势"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"滑动即可切换应用"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"如需在应用之间切换,请从屏幕底部向上滑动,按住,然后松开。"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"如需在应用之间切换,请从屏幕底部向上滑动,按住,然后松开。"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"切换应用"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"从屏幕底部向上滑动后按住,然后松开"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"恭喜!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"任务栏已显示"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"已显示任务栏和左侧消息气泡"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"已显示任务栏和右侧消息气泡"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"任务栏已隐藏"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"已隐藏任务栏和消息气泡"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"导航栏"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"始终显示任务栏"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"更改导航模式"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"任务栏溢出图标"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"移到顶部/左侧"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"移到底部/右侧"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{多个应用}other{多个应用}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"桌面模式"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"<xliff:g id="APP_NAME_1">%1$s</xliff:g>和<xliff:g id="APP_NAME_2">%2$s</xliff:g>"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"展开“<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>”"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"收起“<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>”"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"圈定即搜"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"应用图标"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"应用名称"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"“关闭”按钮"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-zh-rHK/strings.xml b/quickstep/res/values-zh-rHK/strings.xml
index c8d18eb..47bb6d4 100644
--- a/quickstep/res/values-zh-rHK/strings.xml
+++ b/quickstep/res/values-zh-rHK/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"自由形式"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"桌面"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"移至外部顯示屏"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"關閉"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"桌面"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"最近沒有任何項目"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"應用程式使用情況設定"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"全部清除"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"最近使用的應用程式"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"閂咗工作"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>,<xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"少於 1 分鐘"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"今天剩餘時間:<xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"已啟用應用程式建議"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"已停用應用程式建議"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"預測應用程式:<xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"手勢導覽教學課程"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"旋轉裝置方向"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"請旋轉裝置方向以完成手勢導覽教學課程"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"請確保從螢幕最右側或最左側邊緣滑動"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"如要變更「返回」手勢的敏感度,請前往「設定」"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"滑動即可返回"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"如要返回上一個畫面,請從螢幕左側或右側邊緣往中央滑動。"</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"如要返回上一個畫面,請用兩指從螢幕左側或右側邊緣往中央滑動。"</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"返回"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"從螢幕左側或右側邊緣往中央滑動"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"請確保從螢幕底部邊緣向上滑動"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"你已完成「返回主畫面」手勢的教學課程"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"向上滑動即可返回主畫面"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"從螢幕底部向上滑動。這個手勢在所有畫面下都可讓你返回主畫面。"</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"請用兩指從螢幕底部向上滑動。這個手勢在所有畫面下都可讓你返回主畫面。"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"返回主畫面"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"從螢幕底部向上滑動"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"太好了!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"你已完成「切換應用程式」手勢的教學課程"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"滑動即可切換應用程式"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"如要切換應用程式,請從螢幕底部向上滑動並按住,然後放開。"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"如要切換應用程式,請用兩指從螢幕底部向上滑動並按住,然後放開手指。"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"切換應用程式"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"從螢幕底部向上滑動並按住,然後放開"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"做得好!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"顯示咗工作列"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"工作列和對話氣泡在左邊顯示"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"工作列和對話氣泡在右邊顯示"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"隱藏咗工作列"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"工作列和對話氣泡已隱藏"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"導覽列"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"一律顯示工作列"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"變更導覽模式"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"工作列溢位"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"移至上方/左側"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"移至底部/右側"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{個其他應用程式}other{個其他應用程式}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"桌面"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"「<xliff:g id="APP_NAME_1">%1$s</xliff:g>」和「<xliff:g id="APP_NAME_2">%2$s</xliff:g>」"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"打開<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"收埋<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"一圈即搜"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"應用程式圖示"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"應用程式名稱"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"關閉按鈕"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-zh-rTW/strings.xml b/quickstep/res/values-zh-rTW/strings.xml
index 8e06e56..58c7bb5 100644
--- a/quickstep/res/values-zh-rTW/strings.xml
+++ b/quickstep/res/values-zh-rTW/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"自由形式"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"電腦模式"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"移至外接螢幕"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"關閉"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"電腦模式"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"最近沒有任何項目"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"應用程式使用情況設定"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"全部清除"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"最近使用的應用程式"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"工作已關閉"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g> (<xliff:g id="REMAINING_TIME">%2$s</xliff:g>)"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 分鐘"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"今天還能使用 <xliff:g id="TIME">%1$s</xliff:g>"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"應用程式建議功能已啟用"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"應用程式建議功能已停用"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"預測的應用程式:<xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"手勢操作教學課程"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"旋轉裝置螢幕方向"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"如要完成手勢操作教學課程,請旋轉裝置螢幕方向"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"請務必從螢幕最右側或最左側滑動"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"如要變更「返回」手勢的敏感度,請前往「設定」"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"滑動即可返回"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"如要返回上一個畫面,請從螢幕左側或右側邊緣往中央滑動。"</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"如要返回上一個畫面,請用 2 指從螢幕左側或右側邊緣往中央滑動。"</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"返回"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"從螢幕右側或左側往中央滑動"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"請務必從螢幕底部向上滑動"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"你已完成「返回主畫面」手勢的教學課程"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"使用滑動手勢返回主畫面"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"從螢幕底部向上滑動,即可返回主畫面。"</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"用 2 指從螢幕底部向上滑動,即可回到主畫面。"</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"返回主畫面"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"從螢幕底部向上滑動"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"太棒了!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"你已完成「切換應用程式」手勢的教學課程"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"使用滑動手勢切換應用程式"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"如要切換不同的應用程式,請從螢幕底部向上滑動並按住,然後放開手指。"</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"如要切換應用程式,請用 2 指從螢幕底部向上滑動並按住,然後放開手指。"</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"切換應用程式"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"從螢幕底部向上滑動並按住,然後放開手指"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"非常好!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"已顯示工作列"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"工作列和對話框顯示在左側"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"工作列和對話框顯示在右側"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"已隱藏工作列"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"已隱藏工作列和對話框"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"導覽列"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"一律顯示工作列"</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"變更操作模式"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"工作列溢位"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"移到上方/左側"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"移到底部/右側"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{個其他應用程式}other{個其他應用程式}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"電腦模式"</string>
     <string name="quick_switch_split_task" msgid="5598194724255333896">"「<xliff:g id="APP_NAME_1">%1$s</xliff:g>」和「<xliff:g id="APP_NAME_2">%2$s</xliff:g>」"</string>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"展開「<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>」"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"收合「<xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>」"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"畫圈搜尋"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"應用程式圖示"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"應用程式標題"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"關閉按鈕"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values-zu/strings.xml b/quickstep/res/values-zu/strings.xml
index 46dbbd5..8d0a6db 100644
--- a/quickstep/res/values-zu/strings.xml
+++ b/quickstep/res/values-zu/strings.xml
@@ -23,12 +23,12 @@
     <string name="recent_task_option_freeform" msgid="48863056265284071">"I-Freeform"</string>
     <string name="recent_task_option_desktop" msgid="8280879717125435668">"Ideskithophu"</string>
     <string name="recent_task_option_external_display" msgid="4533840664313389484">"Hambisa esibonisini sangaphandle"</string>
+    <string name="recent_task_option_close" msgid="942942499021777264">"Vala"</string>
     <string name="recent_task_desktop" msgid="8081113562549637334">"Ideskithophu"</string>
     <string name="recents_empty_message" msgid="7040467240571714191">"Azikho izinto zakamuva"</string>
     <string name="accessibility_app_usage_settings" msgid="6312864233673544149">"Izilungiselelo zokusetshenziswa kohlelo lokusebenza"</string>
     <string name="recents_clear_all" msgid="5328176793634888831">"Sula konke"</string>
     <string name="accessibility_recent_apps" msgid="4058661986695117371">"Izinhlelo zokusebenza zakamuva"</string>
-    <string name="task_view_closed" msgid="9170038230110856166">"Umsebenzi Uvaliwe"</string>
     <string name="task_contents_description_with_remaining_time" msgid="4479688746574672685">"<xliff:g id="TASK_DESCRIPTION">%1$s</xliff:g>, <xliff:g id="REMAINING_TIME">%2$s</xliff:g>"</string>
     <string name="shorter_duration_less_than_one_minute" msgid="4722015666335015336">"&lt; 1 iminithi"</string>
     <string name="time_left_for_app" msgid="3111996412933644358">"<xliff:g id="TIME">%1$s</xliff:g> esele namhlanje"</string>
@@ -47,6 +47,7 @@
     <string name="hotsaet_tip_prediction_enabled" msgid="2233554377501347650">"Iziphakamiso zohlelo lokusebenza zinikwe amandla"</string>
     <string name="hotsaet_tip_prediction_disabled" msgid="1506426298884658491">"Iziphakamiso zohlelo lokusebenza zikhutshaziwe"</string>
     <string name="hotseat_prediction_content_description" msgid="4582028296938078419">"Uhlelo lokusebenza olubikezelwe: <xliff:g id="TITLE">%1$s</xliff:g>"</string>
+    <string name="gesture_tutorial_title" msgid="2750751261768388354">"Okokufundisa Kokuzulazula Kokuthinta"</string>
     <string name="gesture_tutorial_rotation_prompt_title" msgid="7537946781362766964">"Zungezisa idivayisi yakho"</string>
     <string name="gesture_tutorial_rotation_prompt" msgid="1664493449851960691">"Sicela uzungezise idivayisi yakho ukuze uqedele okokufundisa kokufuna ngokuthinta"</string>
     <string name="back_gesture_feedback_swipe_too_far_from_edge" msgid="4175100312909721217">"Qinisekisa ukuthi uswayipha ukusuka onqenqemeni olukude ngakwesokudla noma olukude ngakwesokunxele"</string>
@@ -58,7 +59,6 @@
     <string name="back_gesture_tutorial_confirm_subtitle" msgid="5181305411668713250">"Ukuze ushintshe ukuzwela kokuthinta emuva, iya Kumasethingi"</string>
     <string name="back_gesture_intro_title" msgid="19551256430224428">"Swayipha ukuze uye emuva"</string>
     <string name="back_gesture_intro_subtitle" msgid="7912576483031802797">"Ukuze ubuyele emuva esikrinini sokugcina, swapha kusuka emngceleni wesobunxele noma wesokudla kuya phakathi kwesikrini."</string>
-    <string name="back_gesture_spoken_intro_subtitle" msgid="2162043199263088592">"Ukuze ubuyele esikrinini sokugcina, swayipha ngeminwe emi-2 ukusuka kwesokunxele noma kwesokudla emphethweni uye phakathi kwesikrini."</string>
     <string name="back_gesture_tutorial_title" msgid="1944737946101059789">"Iya emuva"</string>
     <string name="back_gesture_tutorial_subtitle" msgid="6639993416000920142">"Swayipha ukusuka kwesokunxele noma kwesokudla ukuya phakathi kwesikrini"</string>
     <string name="home_gesture_feedback_swipe_too_far_from_edge" msgid="4816365433160895458">"Qiniseka ukuthi uswayiphela phezulu ukusuka emngceleni ophansi wesikrini"</string>
@@ -68,7 +68,6 @@
     <string name="home_gesture_feedback_complete_without_follow_up" msgid="2978063221383413443">"Ukuqedile ukuthinta kokuya ekhaya"</string>
     <string name="home_gesture_intro_title" msgid="836590312858441830">"Swayipha ukuze uye ekhaya"</string>
     <string name="home_gesture_intro_subtitle" msgid="2632238748497975326">"Swayiphela phezulu kusuka phansi kwesikrini sakho.Lokhu kuthinta kuhlala kukusa esikrinini sasekhaya."</string>
-    <string name="home_gesture_spoken_intro_subtitle" msgid="1030987707382031750">"Swayiphela phezulu ngeminwe emi-2 kusukela phansi esikrinini. Lesi senzo sihlala sikuyisa esikrinini Sasekhaya."</string>
     <string name="home_gesture_tutorial_title" msgid="3126834347496917376">"Iya ekhasini lokuqala"</string>
     <string name="home_gesture_tutorial_subtitle" msgid="7245995490408668778">"Swayiphela phezulu ukusuka phansi esikrinini sakho"</string>
     <string name="home_gesture_tutorial_success" msgid="1736295017642244751">"Umsebenzi omuhle!"</string>
@@ -79,7 +78,6 @@
     <string name="overview_gesture_feedback_complete_without_follow_up" msgid="2903050864432331629">"Ukuqedile ukuthinta kokushintsha ama-app"</string>
     <string name="overview_gesture_intro_title" msgid="2902054412868489378">"Swayipha ukuze ushintshe ama-app"</string>
     <string name="overview_gesture_intro_subtitle" msgid="4968091015637850859">"Ukuze ushintshe phakathi kwama-app, swayiphela phezulu kusuka ngezansi kwesikrini sakho, bese uyadedela."</string>
-    <string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Ukuze ushintshe phakathi kwama-app, swayiphela phezulu ngeminwe emi-2 kusukela phansi esikrinini sakho, ubambe, bese uyakhulula."</string>
     <string name="overview_gesture_tutorial_title" msgid="4125835002668708720">"Shintsha ama-app"</string>
     <string name="overview_gesture_tutorial_subtitle" msgid="5253549754058973071">"Swayiphela phezulu ukusuka phansi esikrinini sakho, ubambe, bese uyadedela"</string>
     <string name="overview_gesture_tutorial_success" msgid="1910267697807973076">"Wenze kahle!"</string>
@@ -134,8 +132,6 @@
     <string name="taskbar_a11y_shown_title" msgid="6842833581088937713">"Ibha yomsebenzi ibonisiwe"</string>
     <string name="taskbar_a11y_shown_with_bubbles_left_title" msgid="4242431789851790046">"ITaskbar namabhamuza aboniswe kwesokunxele"</string>
     <string name="taskbar_a11y_shown_with_bubbles_right_title" msgid="8219065376188180113">"ITaskbar namabhamuza aboniswe kwesokudla"</string>
-    <string name="taskbar_a11y_hidden_title" msgid="9154903639589659284">"Ibha yomsebenzi ifihliwe"</string>
-    <string name="taskbar_a11y_hidden_with_bubbles_title" msgid="7397395993149508087">"ITaskbar namabhamuza afihliwe"</string>
     <string name="taskbar_phone_a11y_title" msgid="4933360237131229395">"Ibha yokufuna"</string>
     <string name="always_show_taskbar" msgid="3608801276107751229">"Bonisa i-Taskbar njalo."</string>
     <string name="change_navigation_mode" msgid="9088393078736808968">"Shintsha imodi yokufuna"</string>
@@ -143,6 +139,8 @@
     <string name="taskbar_overflow_a11y_title" msgid="7960342079198820179">"Ukuphuphuma Kwetaskbar"</string>
     <string name="move_drop_target_top_or_left" msgid="2988702185049595807">"Hamba phezulu/kwesokunxele"</string>
     <string name="move_drop_target_bottom_or_right" msgid="5431393418797620162">"Hamba phansi/kwesokudla"</string>
+    <!-- no translation found for open_app_as_a_bubble (6642626287247807473) -->
+    <skip />
     <string name="quick_switch_overflow" msgid="3679780650881041632">"{count,plural, =1{i-app eyengeziwe}one{ama-app engeziwe}other{ama-app engeziwe}}"</string>
     <string name="quick_switch_desktop" msgid="8393802056024499749">"Ideskithophu"</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>
@@ -156,4 +154,11 @@
     <string name="bubble_bar_accessibility_announce_expand" msgid="1503192695527477102">"nweba <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="bubble_bar_accessibility_announce_collapse" msgid="928284600086798791">"goqa <xliff:g id="BUBBLE_DESCRIPTION">%1$s</xliff:g>"</string>
     <string name="search_gesture_feature_title" msgid="1294044108313175306">"Khethela Ukusesha"</string>
+    <string name="header_app_icon_description" msgid="2184625881433608027">"Isithonjana se-app"</string>
+    <string name="header_default_app_title" msgid="8308052350689531566">"Isihloko se-app"</string>
+    <string name="header_close_icon_description" msgid="5400033616675911319">"Inkinobho yokuvala"</string>
+    <!-- no translation found for pin_to_taskbar (6607778046321626950) -->
+    <skip />
+    <!-- no translation found for unpin_from_taskbar (2178811773165572676) -->
+    <skip />
 </resources>
diff --git a/quickstep/res/values/attrs.xml b/quickstep/res/values/attrs.xml
index 7fd6b5c..28c0d5c 100644
--- a/quickstep/res/values/attrs.xml
+++ b/quickstep/res/values/attrs.xml
@@ -36,6 +36,11 @@
         <attr name="focusBorderColor" />
     </declare-styleable>
 
+    <declare-styleable name="AddDesktopButton">
+        <!-- focus border color for overview add desktop button views -->
+        <attr name="focusBorderColor" />
+    </declare-styleable>
+
     <!--
          Gesture nav edu specific attributes. These attributes are used to customize Gesture nav edu
          view lottie animation colors in XML files.
diff --git a/quickstep/res/values/config.xml b/quickstep/res/values/config.xml
index e8c8505..e69fa4d 100644
--- a/quickstep/res/values/config.xml
+++ b/quickstep/res/values/config.xml
@@ -23,19 +23,16 @@
 
     <string name="stats_log_manager_class" translatable="false">com.android.quickstep.logging.StatsLogCompatManager</string>
     <string name="test_information_handler_class" translatable="false">com.android.quickstep.QuickstepTestInformationHandler</string>
-    <string name="window_manager_proxy_class" translatable="false">com.android.quickstep.util.SystemWindowManagerProxy</string>
     <string name="widget_holder_factory_class" translatable="false">com.android.launcher3.uioverrides.QuickstepWidgetHolder$QuickstepHolderFactory</string>
     <string name="instant_app_resolver_class" translatable="false">com.android.quickstep.InstantAppResolverImpl</string>
     <string name="app_launch_tracker_class" translatable="false">com.android.launcher3.appprediction.PredictionAppTracker</string>
     <string name="main_process_initializer_class" translatable="false">com.android.quickstep.QuickstepProcessInitializer</string>
-    <string name="model_delegate_class" translatable="false">com.android.launcher3.model.QuickstepModelDelegate</string>
     <string name="secondary_display_predictions_class" translatable="false">com.android.launcher3.secondarydisplay.SecondaryDisplayPredictionsImpl</string>
     <string name="taskbar_model_callbacks_factory_class" translatable="false">com.android.launcher3.taskbar.TaskbarModelCallbacksFactory</string>
     <string name="taskbar_view_callbacks_factory_class" translatable="false">com.android.launcher3.taskbar.TaskbarViewCallbacksFactory</string>
     <string name="launcher_restore_event_logger_class" translatable="false">com.android.quickstep.LauncherRestoreEventLoggerImpl</string>
     <string name="taskbar_edu_tooltip_controller_class" translatable="false">com.android.launcher3.taskbar.TaskbarEduTooltipController</string>
     <string name="nav_handle_long_press_handler_class" translatable="false"></string>
-    <string name="contextual_search_state_manager_class" translatable="false"></string>
 
     <!-- The number of thumbnails and icons to keep in the cache. The thumbnail cache size also
          determines how many thumbnails will be fetched in the background. -->
@@ -59,6 +56,7 @@
     <!-- Accessibility actions -->
     <item type="id" name="action_move_to_top_or_left" />
     <item type="id" name="action_move_to_bottom_or_right" />
+    <item type="id" name="action_create_application_bubble" />
 
     <!-- The max scale for the wallpaper when it's zoomed in -->
     <item name="config_wallpaperMaxScale" format="float" type="dimen">
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index b221b22..36e6902 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -34,9 +34,6 @@
     <!--  Overview Task Views  -->
     <!--  The thumbnail uses up to this much of the total screen height/width in Overview -->
     <item name="overview_max_scale" format="float" type="dimen">0.7</item>
-    <!--  The thumbnail should not go smaller than this much of the total screen height/width in
-             tablet app to Overview carousel -->
-    <item name="overview_carousel_min_scale" format="float" type="dimen">0.46</item>
     <!--  A touch target for icons, sometimes slightly larger than the icons themselves  -->
     <dimen name="task_thumbnail_icon_size">48dp</dimen>
     <!--  The icon size for the focused task, placed in center of touch target  -->
@@ -84,6 +81,15 @@
     <!--  The size of the icon menu's icon touch target  -->
     <dimen name="task_thumbnail_icon_menu_drawable_touch_size">44dp</dimen>
     <dimen name="task_thumbnail_icon_menu_elevation">4dp</dimen>
+    <!--  The size of the task thumbnail header  -->
+    <dimen name="task_thumbnail_header_height">30dp</dimen>
+    <dimen name="task_thumbnail_header_margin_edge">18dp</dimen>
+    <dimen name="task_thumbnail_header_margin_between_views">9dp</dimen>
+    <dimen name="task_thumbnail_header_icon_size">18dp</dimen>
+    <dimen name="task_thumbnail_header_round_corner_radius">16dp</dimen>
+
+    <!--  How much a task being dragged for dismissal can undershoot the origin when dragged back to its start position.  -->
+    <dimen name="task_dismiss_max_undershoot">25dp</dimen>
 
     <dimen name="task_icon_cache_default_icon_size">72dp</dimen>
     <item name="overview_modal_max_scale" format="float" type="dimen">1.1</item>
@@ -103,6 +109,10 @@
     <dimen name="recents_clear_all_outline_radius">24dp</dimen>
     <dimen name="recents_clear_all_outline_padding">2dp</dimen>
 
+    <!-- Recents add desktop button -->
+    <dimen name="add_desktop_button_size">56dp</dimen>
+    <dimen name="add_desktop_button_outline_padding">2dp</dimen>
+
     <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
              loading full resolution screenshots. -->
     <dimen name="recents_fast_fling_velocity">600dp</dimen>
@@ -267,7 +277,7 @@
     <dimen name="gesture_tutorial_taskbar_margin_bottom">24dp</dimen>
 
     <!-- All Set page -->
-    <dimen name="allset_page_margin_horizontal">40dp</dimen>
+    <dimen name="allset_page_padding_horizontal">40dp</dimen>
     <dimen name="allset_page_allset_text_size">36sp</dimen>
     <dimen name="allset_page_swipe_up_text_size">14sp</dimen>
 
@@ -361,7 +371,6 @@
     <dimen name="taskbar_running_app_indicator_height">2dp</dimen>
     <dimen name="taskbar_running_app_indicator_width">12dp</dimen>
     <dimen name="taskbar_running_app_indicator_top_margin">4dp</dimen>
-    <dimen name="taskbar_minimized_app_indicator_width">6dp</dimen>
 
     <!-- Transient taskbar -->
     <dimen name="transient_taskbar_padding">12dp</dimen>
@@ -465,11 +474,12 @@
     <dimen name="bubblebar_icon_spacing_persistent_taskbar">@dimen/bubblebar_icon_spacing</dimen>
     <dimen name="bubblebar_expanded_icon_spacing">12dp</dimen>
     <dimen name="bubblebar_icon_elevation">1dp</dimen>
+    <dimen name="bubblebar_transient_taskbar_min_distance">12dp</dimen>
 
     <!-- Bubble bar dismiss view -->
-    <dimen name="bubblebar_dismiss_target_size">96dp</dimen>
+    <dimen name="bubblebar_dismiss_target_size">@dimen/floating_dismiss_background_size</dimen>
     <dimen name="bubblebar_dismiss_target_small_size">60dp</dimen>
-    <dimen name="bubblebar_dismiss_target_icon_size">24dp</dimen>
+    <dimen name="bubblebar_dismiss_target_icon_size">@dimen/floating_dismiss_icon_size</dimen>
     <dimen name="bubblebar_dismiss_target_bottom_margin">50dp</dimen>
     <dimen name="bubblebar_dismiss_floating_gradient_height">548dp</dimen>
     <dimen name="bubblebar_dismiss_zone_width">192dp</dimen>
@@ -519,12 +529,12 @@
     <dimen name="keyboard_quick_switch_text_button_radius">360dp</dimen>
     <dimen name="keyboard_quick_switch_text_button_horizontal_padding">16dp</dimen>
     <dimen name="keyboard_quick_switch_text_button_fade_edge_length">20dp</dimen>
+    <dimen name="keyboard_quick_switch_scroll_button_width">36dp</dimen>
+    <dimen name="keyboard_quick_switch_scroll_button_height">56dp</dimen>
+    <dimen name="keyboard_quick_switch_scroll_button_horizontal_padding">12dp</dimen>
+    <dimen name="keyboard_quick_switch_scroll_button_vertical_padding">32dp</dimen>
+    <dimen name="keyboard_quick_switch_scroll_button_corner_radius">18dp</dimen>
 
     <!-- Digital Wellbeing -->
     <dimen name="digital_wellbeing_toast_height">48dp</dimen>
-
-    <!-- Splitscreen -->
-    <!-- Max width of the split instructions view -->
-    <dimen name="split_instructions_view_max_width">220dp</dimen>
-
 </resources>
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 026e25c..7578bd5 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -30,6 +30,8 @@
     <string name="recent_task_option_desktop">Desktop</string>
     <!-- Title and content description for an option to move app to external display. -->
     <string name="recent_task_option_external_display">Move to external display</string>
+    <!-- Title and content description for an option to close the app [CHAR LIMIT=30] -->
+    <string name="recent_task_option_close">Close</string>
 
     <!-- Title and content description for Desktop tile in Recents screen that contains apps opened inside desktop windowing mode [CHAR LIMIT=NONE] -->
     <string name="recent_task_desktop">Desktop</string>
@@ -49,9 +51,6 @@
     <!-- Accessibility title for the list of recent apps [CHAR_LIMIT=none] -->
     <string name="accessibility_recent_apps">Recent apps</string>
 
-    <!-- Accessibility confirmation for task closed -->
-    <string name="task_view_closed">Task Closed</string>
-
     <!-- Accessibility title for an app card in Recents for apps that have time limit set
      [CHAR_LIMIT=none] -->
     <string name="task_contents_description_with_remaining_time"><xliff:g id="task_description" example="GMail">%1$s</xliff:g>, <xliff:g id="remaining_time" example="7 minutes left today">%2$s</xliff:g></string>
@@ -101,6 +100,9 @@
     <!-- content description for hotseat items -->
     <string name="hotseat_prediction_content_description">Predicted app: <xliff:g id="title" example="Chrome">%1$s</xliff:g></string>
 
+    <!-- Title of the Gesture Navigation Tutorial page [CHAR LIMIT=NONE] -->
+    <string name="gesture_tutorial_title">Gesture Navigation Tutorial</string>
+
     <!-- Title of prompt shown before the gesture navigation tutorial to users who need to rotate their screen. [CHAR LIMIT=100] -->
     <string name="gesture_tutorial_rotation_prompt_title">Rotate your device</string>
     <!-- Prompt shown before the gesture navigation tutorial to users who need to rotate their screen to begin. [CHAR LIMIT=100] -->
@@ -125,8 +127,6 @@
     <string name="back_gesture_intro_title">Swipe to go back</string>
     <!-- Introduction subtitle for the Back gesture tutorial. [CHAR LIMIT=200] -->
     <string name="back_gesture_intro_subtitle">To go back to the last screen, swipe from the left or right edge to the middle of the screen.</string>
-    <!-- Introduction subtitle for the Back gesture tutorial that will be spoken by screen readers. [CHAR LIMIT=200] -->
-    <string name="back_gesture_spoken_intro_subtitle">To go back to the last screen, swipe with 2 fingers from the left or right edge to the middle of the screen.</string>
     <!-- Title of the gesture tutorial section educating users on how to go back to the previous screen. [CHAR LIMIT=100] -->
     <string name="back_gesture_tutorial_title">Go back</string>
     <!-- Subtitle of the gesture tutorial section educating users on how to go to back to the previous screen [CHAR LIMIT=100] -->
@@ -145,8 +145,6 @@
     <string name="home_gesture_intro_title">Swipe to go home</string>
     <!-- Introduction subtitle for the Home gesture tutorial. [CHAR LIMIT=100] -->
     <string name="home_gesture_intro_subtitle">Swipe up from the bottom of your screen. This gesture always takes you to the Home screen.</string>
-    <!-- Introduction subtitle for the Home gesture tutorial that will be spoken by screen readers. [CHAR LIMIT=100] -->
-    <string name="home_gesture_spoken_intro_subtitle">Swipe up with 2 fingers from the bottom of the screen. This gesture always takes you to the Home screen.</string>
     <!-- Title of the gesture tutorial section educating users on how to go to the home screen. [CHAR LIMIT=100] -->
     <string name="home_gesture_tutorial_title">Go home</string>
     <!-- Subtitle of the gesture tutorial section educating users on how to go to the home screen [CHAR LIMIT=100] -->
@@ -168,8 +166,6 @@
     <string name="overview_gesture_intro_title">Swipe to switch apps</string>
     <!-- Introduction subtitle for the Overview gesture tutorial. [CHAR LIMIT=100] -->
     <string name="overview_gesture_intro_subtitle">To switch between apps, swipe up from the bottom of your screen, hold, then release.</string>
-    <!-- Introduction subtitle for the Overview gesture tutorial that will be spoken by screen readers. [CHAR LIMIT=100] -->
-    <string name="overview_gesture_spoken_intro_subtitle">To switch between apps, swipe up with 2 fingers from the bottom of your screen, hold, then release.</string>
     <!-- Title of the gesture tutorial section educating users on how to switch between apps. [CHAR LIMIT=100] -->
     <string name="overview_gesture_tutorial_title">Switch apps</string>
     <!-- Subtitle of the gesture tutorial section educating users on how to switch between apps [CHAR LIMIT=100] -->
@@ -310,10 +306,6 @@
     <string name="taskbar_a11y_shown_with_bubbles_left_title">Taskbar &#38; bubbles left shown</string>
     <!-- Accessibility title for the Taskbar window appearing together with bubble bar on right. [CHAR_LIMIT=30] -->
     <string name="taskbar_a11y_shown_with_bubbles_right_title">Taskbar &#38; bubbles right shown</string>
-    <!-- Accessibility title for the Taskbar window being closed. [CHAR_LIMIT=30] -->
-    <string name="taskbar_a11y_hidden_title">Taskbar hidden</string>
-    <!-- Accessibility title for the Taskbar window being closed together with bubble bar. [CHAR_LIMIT=30] -->
-    <string name="taskbar_a11y_hidden_with_bubbles_title">Taskbar &#38; bubbles hidden</string>
     <!-- Accessibility title for the Taskbar window on phones. [CHAR_LIMIT=NONE] -->
     <string name="taskbar_phone_a11y_title">Navigation bar</string>
     <!-- Text in popup dialog for user to switch between always showing Taskbar or not. [CHAR LIMIT=30] -->
@@ -330,6 +322,14 @@
     <string name="move_drop_target_top_or_left">Move to top&#47;left</string>
     <!-- Label for moving drop target to the bottom or right side of the screen, depending on orientation (from the Taskbar only). -->
     <string name="move_drop_target_bottom_or_right">Move to bottom&#47;right</string>
+    <!-- Label for creating an application bubble (from the Taskbar only). -->
+    <string name="open_app_as_a_bubble">Open app as a bubble</string>
+
+    <!-- Accessibility pane title for quick switch view, which lists apps opened by the user, ordered by how recently the app was opened. -->
+    <string name="quick_switch_pane_title">Recent apps</string>
+
+    <!-- Content description for the quick switch view, which lists apps opened by the user, ordered by how recently the app was opened. -->
+    <string name="quick_switch_content_description">Recent app list</string>
 
     <!-- Label for quick switch tile showing how many more apps are available. The number will be displayed above this text. [CHAR LIMIT=NONE] -->
     <string name="quick_switch_overflow">{count, plural,
@@ -342,6 +342,15 @@
 
     <!-- Accessibility label for quick switch tiles showing split tasks [CHAR LIMIT=NONE] -->
     <string name="quick_switch_split_task"><xliff:g id="app_name_1" example="Chrome">%1$s</xliff:g> and <xliff:g id="app_name_2" example="Gmail">%2$s</xliff:g></string>
+    <!-- Accessibility label for quick switch tiles that include information about the tile's position in the parent list [CHAR LIMIT=NONE] -->
+    <string name="quick_switch_task_with_position_in_parent"><xliff:g id="task_description" example="Chrome">%1$s</xliff:g>, item <xliff:g id="index_in_parent" example="1">%2$d</xliff:g> of <xliff:g id="total_tasks" example="5">%3$d</xliff:g></string>
+
+    <!-- Accessibility label for an arrow button within quick switch UI that scrolls the quick switch content left
+        TODO(b/397975686): Make these translatable when verified by UX. -->
+    <string name="quick_switch_scroll_arrow_left" translatable="false">Scroll left</string>
+    <!-- Accessibility label for an arrow button within quick switch UI that scrolls the quick switch content right
+        TODO(b/397975686): Make these translatable when verified by UX. -->
+    <string name="quick_switch_scroll_arrow_right" translatable="false">Scroll right</string>
 
     <!-- Strings for bubble bar -->
     <!-- Fallback name for a bubble if it does have a title [CHAR_LIMIT=none] -->
@@ -366,4 +375,17 @@
     <!-- Name of Google's new feature to circle to search anything on your phone screen, without
      switching apps. [CHAR_LIMIT=60] -->
     <string name="search_gesture_feature_title">Circle to Search</string>
+
+    <!-- Strings for task thumbnail header in Overview -->
+    <!-- Content description for the header app icon. [CHAR LIMIT=NONE] -->
+    <string name="header_app_icon_description">App icon</string>
+    <!-- Default app title for a task view in Overview. [CHAR LIMIT=NONE] -->
+    <string name="header_default_app_title">App title</string>
+    <!-- Content description for the header close button. [CHAR LIMIT=NONE] -->
+    <string name="header_close_icon_description">Close button</string>
+
+    <!-- Label for pinning an item to the taskbar. [CHAR_LIMIT=20] -->
+    <string name="pin_to_taskbar">Pin to taskbar</string>
+    <!-- Label for unpinning an item from the taskbar. [CHAR_LIMIT=20] -->
+    <string name="unpin_from_taskbar">Unpin from taskbar</string>
 </resources>
diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml
index 5f2a63d..f8ca8d9 100644
--- a/quickstep/res/values/styles.xml
+++ b/quickstep/res/values/styles.xml
@@ -321,6 +321,7 @@
         <item name="android:fontFamily">google-sans-text-medium</item>
         <item name="android:textSize">@dimen/task_thumbnail_icon_menu_text_size</item>
         <item name="android:textColor">@color/materialColorOnSurface</item>
+        <item name="android:includeFontPadding">false</item>
         <item name="android:letterSpacing">0.025</item>
         <item name="android:lineHeight">20sp</item>
     </style>
diff --git a/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
index 1161720..08ef8fe 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAccessibilityDelegate.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3;
 
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
+
 import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
 
 import android.view.KeyEvent;
@@ -48,7 +50,11 @@
     public void onPopulateAccessibilityEvent(View view, AccessibilityEvent event) {
         super.onPopulateAccessibilityEvent(view, event);
         // Scroll to the position if focused view in main allapps list and not completely visible.
-        scrollToPositionIfNeeded(view);
+        // Gate based on TYPE_VIEW_ACCESSIBILITY_FOCUSED for unintended scrolling with external
+        // mouse.
+        if (event.getEventType() == TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
+            scrollToPositionIfNeeded(view);
+        }
     }
 
     private void scrollToPositionIfNeeded(View view) {
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 2759816..aae8a56 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -56,7 +56,6 @@
 import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE;
 import static com.android.launcher3.util.DisplayController.isTransientTaskbar;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR;
 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
@@ -79,7 +78,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Resources;
-import android.database.ContentObserver;
 import android.graphics.Color;
 import android.graphics.Matrix;
 import android.graphics.Point;
@@ -94,7 +92,6 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.provider.Settings.Global;
 import android.util.Pair;
 import android.util.Size;
 import android.view.CrossWindowBlurListeners;
@@ -179,6 +176,7 @@
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map.Entry;
 
 /**
  * Manages the opening and closing app transitions from Launcher
@@ -226,7 +224,6 @@
     private static final int TASKBAR_TO_HOME_DURATION_FAST = 300;
     private static final int TASKBAR_TO_HOME_DURATION_SLOW = 1000;
     protected static final int CONTENT_SCALE_DURATION = 350;
-    protected static final int CONTENT_SCRIM_DURATION = 350;
 
     private static final int MAX_NUM_TASKS = 5;
 
@@ -244,16 +241,12 @@
 
     private final StartingWindowListener mStartingWindowListener =
             new StartingWindowListener(this);
-    private ContentObserver mAnimationRemovalObserver = new ContentObserver(
-            ORDERED_BG_EXECUTOR.getHandler()) {
-        @Override
-        public void onChange(boolean selfChange) {
-            mAreAnimationsEnabled = Global.getFloat(mLauncher.getContentResolver(),
-                    Global.ANIMATOR_DURATION_SCALE, 1f) > 0
-                    || Global.getFloat(mLauncher.getContentResolver(),
-                    Global.TRANSITION_ANIMATION_SCALE, 1f) > 0;
-        }
-    };
+
+    // TODO(b/397690719): Investigate the memory leak from TaskStackChangeListeners#mImpl
+    // This is a temporary fix of memory leak b/397690719. We track registered
+    // {@link TaskRestartedDuringLaunchListener}, and remove them on activity destroy.
+    private final List<TaskRestartedDuringLaunchListener> mRegisteredTaskStackChangeListener =
+            new ArrayList<>();
 
     private DeviceProfile mDeviceProfile;
 
@@ -282,7 +275,6 @@
     // Pairs of window starting type and starting window background color for starting tasks
     // Will never be larger than MAX_NUM_TASKS
     private LinkedHashMap<Integer, Pair<Integer, Integer>> mTaskStartParams;
-    private boolean mAreAnimationsEnabled = true;
 
     private final Interpolator mOpeningXInterpolator;
     private final Interpolator mOpeningInterpolator;
@@ -293,7 +285,6 @@
         mHandler = new Handler(Looper.getMainLooper());
         mDeviceProfile = mLauncher.getDeviceProfile();
         mBackAnimationController = new LauncherBackAnimationController(mLauncher, this);
-        checkAndMonitorIfAnimationsAreEnabled();
 
         Resources res = mLauncher.getResources();
         mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
@@ -338,7 +329,14 @@
         TaskRestartedDuringLaunchListener restartedListener =
                 new TaskRestartedDuringLaunchListener();
         restartedListener.register(onEndCallback::executeAllAndDestroy);
-        onEndCallback.add(restartedListener::unregister);
+        mRegisteredTaskStackChangeListener.add(restartedListener);
+        onEndCallback.add(new Runnable() {
+            @Override
+            public void run() {
+                restartedListener.unregister();
+                mRegisteredTaskStackChangeListener.remove(restartedListener);
+            }
+        });
 
         RemoteAnimationRunnerCompat runner = createAppLaunchRunner(v, onEndCallback);
 
@@ -354,7 +352,7 @@
                 new RemoteAnimationAdapter(runner, duration, statusBarTransitionDelay),
                 new RemoteTransition(runner.toRemoteTransition(),
                         mLauncher.getIApplicationThread(), "QuickstepLaunch"));
-        IRemoteCallback endCallback = completeRunnableListCallback(onEndCallback);
+        IRemoteCallback endCallback = completeRunnableListCallback(onEndCallback, mLauncher);
         options.setOnAnimationAbortListener(endCallback);
         options.setOnAnimationFinishedListener(endCallback);
         options.setLaunchCookie(StableViewInfo.toLaunchCookie(itemInfo));
@@ -411,7 +409,8 @@
             @NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing) {
         TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets,
                 nonAppTargets, launcherClosing, mLauncher.getStateManager(),
-                mLauncher.getOverviewPanel(), mLauncher.getDepthController());
+                mLauncher.getOverviewPanel(), mLauncher.getDepthController(),
+                /* transitionInfo= */ null);
     }
 
     private boolean areAllTargetsTranslucent(@NonNull RemoteAnimationTarget[] targets) {
@@ -1207,8 +1206,12 @@
         unregisterRemoteTransitions();
         mLauncher.removeOnDeviceProfileChangeListener(this);
         SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null);
-        ORDERED_BG_EXECUTOR.execute(() -> mLauncher.getContentResolver()
-                .unregisterContentObserver(mAnimationRemovalObserver));
+        if (BuildConfig.IS_STUDIO_BUILD && !mRegisteredTaskStackChangeListener.isEmpty()) {
+            throw new IllegalStateException("Failed to run onEndCallback created from"
+                    + " getActivityLaunchOptions()");
+        }
+        mRegisteredTaskStackChangeListener.forEach(TaskRestartedDuringLaunchListener::unregister);
+        mRegisteredTaskStackChangeListener.clear();
     }
 
     /**
@@ -1256,17 +1259,6 @@
         }
     }
 
-    private void checkAndMonitorIfAnimationsAreEnabled() {
-        ORDERED_BG_EXECUTOR.execute(() -> {
-            mAnimationRemovalObserver.onChange(true);
-            mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
-                    Global.ANIMATOR_DURATION_SCALE), false, mAnimationRemovalObserver);
-            mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
-                    Global.TRANSITION_ANIMATION_SCALE), false, mAnimationRemovalObserver);
-
-        });
-    }
-
     private boolean launcherIsATargetWithMode(RemoteAnimationTarget[] targets, int mode) {
         for (RemoteAnimationTarget target : targets) {
             if (target.mode == mode && target.taskInfo != null
@@ -1351,9 +1343,9 @@
                 ? Collections.EMPTY_LIST
                 : runningTaskTarget.taskInfo.launchCookies;
 
-        return mLauncher.getFirstMatchForAppClose(
+        return mLauncher.getFirstVisibleElementForAppClose(
                 StableViewInfo.fromLaunchCookies(launchCookies), packageName,
-                UserHandle.of(runningTaskTarget.taskInfo.userId), true /* supportsAllAppsState */);
+                UserHandle.of(runningTaskTarget.taskInfo.userId));
     }
 
     private @NonNull RectF getDefaultWindowTargetRect() {
@@ -1402,7 +1394,8 @@
                     (LauncherAppWidgetHostView) launcherView, targetRect, windowSize,
                     mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher),
                     isTransluscent, fallbackBackgroundColor);
-        } else if (launcherView != null && mAreAnimationsEnabled) {
+        } else if (launcherView != null && !RemoveAnimationSettingsTracker.INSTANCE.get(
+                mLauncher).isRemoveAnimationEnabled()) {
             floatingIconView = getFloatingIconView(mLauncher, launcherView, null,
                     mLauncher.getTaskbarUIController() == null
                             ? null
@@ -1567,8 +1560,7 @@
 
     private boolean isFreeformAnimation(RemoteAnimationTarget[] appTargets) {
         return DesktopModeStatus.canEnterDesktopMode(mLauncher.getApplicationContext())
-                && (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS.isTrue()
-                    || DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX.isTrue())
+                && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX.isTrue()
                 && Arrays.stream(appTargets)
                         .anyMatch(app -> app.taskInfo != null && app.taskInfo.isFreeform());
     }
@@ -1670,7 +1662,10 @@
                 || mLauncher.getWorkspace().isOverlayShown()
                 || shouldPlayFallbackClosingAnimation(appTargets);
 
-        boolean playWorkspaceReveal = !fromPredictiveBack;
+        boolean playWorkspaceReveal = true;
+        if (!Flags.predictiveBackToHomePolish()) {
+            playWorkspaceReveal = !fromPredictiveBack;
+        }
         boolean skipAllAppsScale = false;
         if (!playFallBackAnimation) {
             PointF velocity;
@@ -1689,12 +1684,12 @@
                 // Skip scaling all apps, otherwise FloatingIconView will get wrong
                 // layout bounds.
                 skipAllAppsScale = true;
-            } else if (!fromPredictiveBack) {
+            } else if (Flags.predictiveBackToHomePolish() || !fromPredictiveBack) {
                 if (enableScalingRevealHomeAnimation()) {
                     anim.play(
-                            new ScalingWorkspaceRevealAnim(
-                                    mLauncher, rectFSpringAnim,
-                                    rectFSpringAnim.getTargetRect()).getAnimators());
+                            new ScalingWorkspaceRevealAnim(mLauncher, rectFSpringAnim,
+                                    rectFSpringAnim.getTargetRect(),
+                                    !fromPredictiveBack /* playAlphaReveal */).getAnimators());
                 } else {
                     anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
                             true /* animateOverviewScrim */, launcherView).getAnimators());
@@ -1713,15 +1708,7 @@
             anim.play(getFallbackClosingWindowAnimators(appTargets));
         }
 
-        // Normally, we run the launcher content animation when we are transitioning
-        // home, but if home is already visible, then we don't want to animate the
-        // contents of launcher unless we know that we are animating home as a result
-        // of the home button press with quickstep, which will result in launcher being
-        // started on touch down, prior to the animation home (and won't be in the
-        // targets list because it is already visible). In that case, we force
-        // invisibility on touch down, and only reset it after the animation to home
-        // is initialized.
-        if (launcherIsForceInvisibleOrOpening || fromPredictiveBack) {
+        if (Flags.predictiveBackToHomePolish()) {
             AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
@@ -1730,25 +1717,55 @@
                             mLauncher, WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE);
                 }
             };
+            if (rectFSpringAnim != null) {
+                rectFSpringAnim.addAnimatorListener(endListener);
+            } else {
+                anim.addListener(endListener);
+            }
+        }
 
+        // Normally, we run the launcher content animation when we are transitioning
+        // home, but if home is already visible, then we don't want to animate the
+        // contents of launcher unless we know that we are animating home as a result
+        // of the home button press with quickstep, which will result in launcher being
+        // started on touch down, prior to the animation home (and won't be in the
+        // targets list because it is already visible). In that case, we force
+        // invisibility on touch down, and only reset it after the animation to home
+        // is initialized.
+        boolean legacyFromPredictiveBack =
+                !Flags.predictiveBackToHomePolish() && fromPredictiveBack;
+        if (launcherIsForceInvisibleOrOpening || legacyFromPredictiveBack) {
             if (rectFSpringAnim != null && anim.getChildAnimations().isEmpty()) {
                 addCujInstrumentation(rectFSpringAnim, Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
             } else {
+                if (isFreeformAnimation(appTargets)) {
+                    addCujInstrumentation(anim,
+                            Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE);
+                }
                 addCujInstrumentation(anim, playFallBackAnimation
                         ? Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK
                         : Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
             }
-
-            if (fromPredictiveBack && rectFSpringAnim != null) {
-                rectFSpringAnim.addAnimatorListener(endListener);
-            } else {
-                anim.addListener(endListener);
+            if (!Flags.predictiveBackToHomePolish()) {
+                AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        super.onAnimationEnd(animation);
+                        AccessibilityManagerCompat.sendTestProtocolEventToTest(
+                                mLauncher, WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE);
+                    }
+                };
+                if (fromPredictiveBack && rectFSpringAnim != null) {
+                    rectFSpringAnim.addAnimatorListener(endListener);
+                } else {
+                    anim.addListener(endListener);
+                }
             }
 
             // Only register the content animation for cancellation when state changes
             mLauncher.getStateManager().setCurrentAnimation(anim);
 
-            if (mLauncher.isInState(LauncherState.ALL_APPS) && !fromPredictiveBack) {
+            if (mLauncher.isInState(LauncherState.ALL_APPS) && !legacyFromPredictiveBack) {
                 Pair<AnimatorSet, Runnable> contentAnimator =
                         getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY,
                                 skipAllAppsScale);
@@ -1768,8 +1785,8 @@
     }
 
     /** Get animation duration for taskbar for going to home. */
-    public static int getTaskbarToHomeDuration(boolean isPinnedTaskbar) {
-        return getTaskbarToHomeDuration(false, isPinnedTaskbar);
+    public static int getTaskbarToHomeDuration(boolean isPinnedTaskbarAndNotInDesktopMode) {
+        return getTaskbarToHomeDuration(false, isPinnedTaskbarAndNotInDesktopMode);
     }
 
     /**
@@ -1778,8 +1795,8 @@
      * @param shouldOverrideToFastAnimation should overwrite scaling reveal home animation duration
      */
     public static int getTaskbarToHomeDuration(boolean shouldOverrideToFastAnimation,
-            boolean isPinnedTaskbar) {
-        if (isPinnedTaskbar) {
+            boolean isPinnedTaskbarAndNotInDesktopMode) {
+        if (isPinnedTaskbarAndNotInDesktopMode) {
             return PINNED_TASKBAR_TRANSITION_DURATION;
         } else if (enableScalingRevealHomeAnimation() && !shouldOverrideToFastAnimation) {
             return TASKBAR_TO_HOME_DURATION_SLOW;
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index dc0f899..4d3e3be 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -23,7 +23,8 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
-import static java.util.Collections.emptyList;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
 
 import android.appwidget.AppWidgetManager;
 import android.appwidget.AppWidgetProviderInfo;
@@ -46,13 +47,13 @@
 import com.android.launcher3.model.StringCache;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.WidgetPredictionsRequester;
-import com.android.launcher3.model.WidgetsFilterDataProvider;
 import com.android.launcher3.model.WidgetsModel;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.widget.WidgetCell;
 import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
+import com.android.launcher3.widget.picker.WidgetCategoryFilter;
 import com.android.launcher3.widget.picker.WidgetsFullSheet;
 import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
 import com.android.systemui.animation.back.FlingOnBackAnimationCallback;
@@ -67,7 +68,8 @@
 import java.util.regex.Pattern;
 
 /** An Activity that can host Launcher's widget picker. */
-public class WidgetPickerActivity extends BaseActivity {
+public class WidgetPickerActivity extends BaseActivity implements
+        WidgetPredictionsRequester.WidgetPredictionsListener {
     private static final String TAG = "WidgetPickerActivity";
     /**
      * Name of the extra that indicates that a widget being dragged.
@@ -81,6 +83,10 @@
     // the intent, then widgets will not be filtered for size.
     private static final String EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width";
     private static final String EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height";
+    // Unlike the AppWidgetManager.EXTRA_CATEGORY_FILTER, this filter removes certain categories.
+    // Filter is ignore if it is not a negative value.
+    // Example usage: WIDGET_CATEGORY_HOME_SCREEN.inv() and WIDGET_CATEGORY_NOT_KEYGUARD.inv()
+    private static final String EXTRA_CATEGORY_EXCLUSION_FILTER = "category_exclusion_filter";
     /**
      * Widgets currently added by the user in the UI surface.
      * <p>This allows widget picker to exclude existing widgets from suggestions.</p>
@@ -114,13 +120,12 @@
     private LauncherAppState mApp;
     private StringCache mStringCache;
     private WidgetPredictionsRequester mWidgetPredictionsRequester;
-    private final WidgetPickerDataProvider mWidgetPickerDataProvider =
-            new WidgetPickerDataProvider();
-    private WidgetsFilterDataProvider mWidgetsFilterDataProvider;
+    private WidgetPickerDataProvider mWidgetPickerDataProvider;
 
     private int mDesiredWidgetWidth;
     private int mDesiredWidgetHeight;
-    private int mWidgetCategoryFilter;
+    private WidgetCategoryFilter mWidgetCategoryInclusionFilter;
+    private WidgetCategoryFilter mWidgetCategoryExclusionFilter;
     @Nullable
     private String mUiSurface;
     // Widgets existing on the host surface.
@@ -162,7 +167,7 @@
         InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
         mDeviceProfile = idp.getDeviceProfile(this);
         mModel = new WidgetsModel();
-        mWidgetsFilterDataProvider = WidgetsFilterDataProvider.Companion.newInstance(this);
+        mWidgetPickerDataProvider = new WidgetPickerDataProvider(this);
 
         setContentView(R.layout.widget_picker_activity);
         mDragLayer = findViewById(R.id.drag_layer);
@@ -194,8 +199,19 @@
                 getIntent().getIntExtra(EXTRA_DESIRED_WIDGET_HEIGHT, 0);
 
         // Defaults to '0' to indicate that there isn't a category filter.
-        mWidgetCategoryFilter =
-                getIntent().getIntExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER, 0);
+        // Negative value indicates it's an exclusion filter (e.g. NOT_KEYGUARD_CATEGORY.inv())
+        // Positive value indicates it's inclusion filter (e.g. HOME_SCREEN or KEYGUARD)
+        // Note: A filter can either be inclusion or exclusion filter; not both.
+        int inclusionFilter = getIntent().getIntExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER, 0);
+        if (inclusionFilter < 0) {
+            Log.w(TAG, "Invalid EXTRA_CATEGORY_FILTER: " + inclusionFilter);
+        }
+        mWidgetCategoryInclusionFilter = new WidgetCategoryFilter(max(0, inclusionFilter));
+        int exclusionFilter = getIntent().getIntExtra(EXTRA_CATEGORY_EXCLUSION_FILTER, 0);
+        if (exclusionFilter > 0) {
+            Log.w(TAG, "Invalid EXTRA_CATEGORY_EXCLUSION_FILTER: " + exclusionFilter);
+        }
+        mWidgetCategoryExclusionFilter = new WidgetCategoryFilter(min(0 , exclusionFilter));
 
         String uiSurfaceParam = getIntent().getStringExtra(EXTRA_UI_SURFACE);
         if (uiSurfaceParam != null && UI_SURFACE_PATTERN.matcher(uiSurfaceParam).matches()) {
@@ -294,23 +310,20 @@
     private void refreshAndBindWidgets() {
         MODEL_EXECUTOR.execute(() -> {
             LauncherAppState app = LauncherAppState.getInstance(this);
-            // Don't have to setup filters - its setup when launcher loads
-            // Just refresh filters with available cached info.
-            mModel.updateWidgetFilters(mWidgetsFilterDataProvider);
             mModel.update(app, null);
 
             StringCache stringCache = new StringCache();
             stringCache.loadStrings(this);
 
             bindStringCache(stringCache);
-            bindWidgets(mModel.getWidgetsByPackageItem(), mModel.getDefaultWidgetsFilter());
+            bindWidgets(mModel.getWidgetsByPackageItemForPicker());
             // Open sheet once widgets are available, so that it doesn't interrupt the open
             // animation.
             openWidgetsSheet();
             if (mUiSurface != null) {
                 mWidgetPredictionsRequester = new WidgetPredictionsRequester(app.getContext(),
-                        mUiSurface, mModel.getWidgetsByComponentKey());
-                mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets);
+                        mUiSurface, mModel.getWidgetsByComponentKeyForPicker());
+                mWidgetPredictionsRequester.request(mAddedWidgets, /*listener=*/ this);
             }
         });
     }
@@ -319,26 +332,19 @@
         MAIN_EXECUTOR.execute(() -> mStringCache = stringCache);
     }
 
-    private void bindWidgets(Map<PackageItemInfo, List<WidgetItem>> widgets,
-            @Nullable Predicate<WidgetItem> defaultWidgetsFilter) {
+    private void bindWidgets(Map<PackageItemInfo, List<WidgetItem>> widgets) {
         WidgetsListBaseEntriesBuilder builder = new WidgetsListBaseEntriesBuilder(
                 mApp.getContext());
-
         final List<WidgetsListBaseEntry> allWidgets = builder.build(widgets, mNoShortcutsFilter);
 
-        // Default list is shown if either defaultWidgetsFilter exists or host has additionally
-        // enforced size filtering.
+        // Default list is shown if host has additionally enforced size filtering.
         @Nullable Predicate<WidgetItem> defaultListFilter =
                 hasHostSizeFilters() ? mHostSizeAndNoShortcutsFilter : null;
-        if (defaultWidgetsFilter != null) {
-            defaultListFilter = defaultListFilter != null ? defaultListFilter.and(
-                    defaultWidgetsFilter) : defaultWidgetsFilter;
-        }
-        final List<WidgetsListBaseEntry> defaultWidgets = defaultListFilter != null ? builder.build(
-                widgets, defaultListFilter) : emptyList();
 
-        MAIN_EXECUTOR.execute(
-                () -> mWidgetPickerDataProvider.setWidgets(allWidgets, defaultWidgets));
+        MAIN_EXECUTOR.execute(() -> {
+            mWidgetPickerDataProvider.setHostSpecifiedDefaultWidgetsFilter(defaultListFilter);
+            mWidgetPickerDataProvider.setWidgets(allWidgets);
+        });
     }
 
     private void openWidgetsSheet() {
@@ -350,7 +356,8 @@
         });
     }
 
-    private void bindRecommendedWidgets(List<ItemInfo> recommendedWidgets) {
+    @Override
+    public void onPredictionsAvailable(List<ItemInfo> recommendedWidgets) {
         // Bind recommendations once picker has finished open animation.
         MAIN_EXECUTOR.getHandler().postDelayed(
                 () -> mWidgetPickerDataProvider.setWidgetRecommendations(recommendedWidgets),
@@ -360,7 +367,7 @@
     @Override
     protected void onDestroy() {
         super.onDestroy();
-        MODEL_EXECUTOR.execute(() -> mWidgetsFilterDataProvider.destroy());
+        mWidgetPickerDataProvider.destroy();
         if (mWidgetPredictionsRequester != null) {
             mWidgetPredictionsRequester.clear();
         }
@@ -436,11 +443,13 @@
                     widget.user.getIdentifier());
         }
 
-        if (mWidgetCategoryFilter > 0 && (info.widgetCategory & mWidgetCategoryFilter) == 0) {
+        if (!mWidgetCategoryInclusionFilter.matches(info.widgetCategory)
+                || !mWidgetCategoryExclusionFilter.matches(info.widgetCategory)) {
             return rejectWidget(
                     widget,
-                    "doesn't match category filter [filter=%d, widget=%d]",
-                    mWidgetCategoryFilter,
+                    "doesn't match category filter [inclusion=%d, exclusion=%d, widget=%d]",
+                    mWidgetCategoryInclusionFilter.getCategoryMask(),
+                    mWidgetCategoryExclusionFilter.getCategoryMask(),
                     info.widgetCategory);
         }
 
@@ -463,7 +472,7 @@
                             mDesiredWidgetWidth);
                 }
 
-                final int minWidth = Math.min(info.minResizeWidth, info.minWidth);
+                final int minWidth = min(info.minResizeWidth, info.minWidth);
                 if (minWidth > mDesiredWidgetWidth) {
                     return rejectWidget(
                             widget,
@@ -487,7 +496,7 @@
                             mDesiredWidgetHeight);
                 }
 
-                final int minHeight = Math.min(info.minResizeHeight, info.minHeight);
+                final int minHeight = min(info.minResizeHeight, info.minHeight);
                 if (minHeight > mDesiredWidgetHeight) {
                     return rejectWidget(
                             widget,
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 8e80aa5..b329156 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -16,12 +16,16 @@
 
 package com.android.launcher3.appprediction;
 
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+
 import android.content.Context;
 import android.graphics.Canvas;
+import android.os.Build;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.LinearLayout;
 
 import androidx.annotation.NonNull;
@@ -31,7 +35,6 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.Flags;
-import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.FloatingHeaderRow;
@@ -91,6 +94,14 @@
     }
 
     @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        if (Build.VERSION.SDK_INT >= UPSIDE_DOWN_CAKE) {
+            info.setContainerTitle(mActivityContext.getString(R.string.title_app_suggestions));
+        }
+    }
+
+    @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
         mActivityContext.addOnDeviceProfileChangeListener(this);
@@ -138,8 +149,7 @@
         int totalHeight = iconHeight + iconPadding + textHeight + mVerticalPadding * 2;
         // Prediction row height will be 4dp bigger than the regular apps in A-Z list when two line
         // is not enabled. Otherwise, the extra height will increase by just the textHeight.
-        int extraHeight = (Flags.enableTwolineToggle() &&
-                LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(getContext()))
+        int extraHeight = deviceProfile.inv.enableTwoLinesInAllApps
                 ? (textHeight + mTopRowExtraHeight) : mTopRowExtraHeight;
         totalHeight += extraHeight;
         return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom();
diff --git a/quickstep/src/com/android/launcher3/dagger/Modules.kt b/quickstep/src/com/android/launcher3/dagger/Modules.kt
new file mode 100644
index 0000000..52be413
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/dagger/Modules.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.dagger
+
+import com.android.launcher3.uioverrides.SystemApiWrapper
+import com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl
+import com.android.launcher3.util.ApiWrapper
+import com.android.launcher3.util.PluginManagerWrapper
+import com.android.launcher3.util.window.WindowManagerProxy
+import com.android.quickstep.util.GestureExclusionManager
+import com.android.quickstep.util.SystemWindowManagerProxy
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+
+private object Modules {}
+
+@Module
+abstract class WindowManagerProxyModule {
+    @Binds abstract fun bindWindowManagerProxy(proxy: SystemWindowManagerProxy): WindowManagerProxy
+}
+
+@Module
+abstract class ApiWrapperModule {
+    @Binds abstract fun bindApiWrapper(systemApiWrapper: SystemApiWrapper): ApiWrapper
+}
+
+@Module
+abstract class PluginManagerWrapperModule {
+    @Binds
+    abstract fun bindPluginManagerWrapper(impl: PluginManagerWrapperImpl): PluginManagerWrapper
+}
+
+@Module
+object StaticObjectModule {
+
+    @Provides
+    @JvmStatic
+    fun provideGestureExclusionManager(): GestureExclusionManager = GestureExclusionManager.INSTANCE
+}
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
new file mode 100644
index 0000000..1438edf
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.desktop
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Rect
+import android.util.Log
+import android.view.Choreographer
+import android.view.SurfaceControl.Transaction
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_BACK
+import android.window.DesktopModeFlags
+import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
+import androidx.core.animation.addListener
+import androidx.core.util.Supplier
+import com.android.app.animation.Interpolators
+import com.android.internal.jank.Cuj
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType
+import com.android.launcher3.desktop.DesktopAppLaunchTransition.Companion.LAUNCH_CHANGE_MODES
+import com.android.wm.shell.shared.animation.MinimizeAnimator
+import com.android.wm.shell.shared.animation.WindowAnimator
+
+/**
+ * Helper class responsible for creating and managing animators for desktop app launch and related
+ * transitions.
+ *
+ * <p>This class handles the complex logic of creating various animators, including launch,
+ * minimize, and trampoline close animations, based on the provided transition information and
+ * launch type. It also utilizes {@link InteractionJankMonitor} to monitor animation jank.
+ *
+ * @param context The application context.
+ * @param launchType The type of app launch, containing animation parameters.
+ * @param cujType The CUJ (Critical User Journey) type for jank monitoring.
+ */
+class DesktopAppLaunchAnimatorHelper(
+    private val context: Context,
+    private val launchType: AppLaunchType,
+    @Cuj.CujType private val cujType: Int,
+    private val transactionSupplier: Supplier<Transaction>,
+) {
+
+    private val interactionJankMonitor = InteractionJankMonitor.getInstance()
+
+    fun createAnimators(info: TransitionInfo, finishCallback: (Animator) -> Unit): List<Animator> {
+        val launchChange = getLaunchChange(info)
+        if (launchChange == null) {
+            val tasksInfo =
+                info.changes.joinToString(", ") { change ->
+                    "${change.taskInfo?.taskId}:${change.taskInfo?.isFreeform}"
+                }
+            Log.e(TAG, "No launch change found: Transition info=$info, tasks state=$tasksInfo")
+            return emptyList()
+        }
+
+        val transaction = transactionSupplier.get()
+
+        val minimizeChange = getMinimizeChange(info)
+        val trampolineCloseChange = getTrampolineCloseChange(info)
+
+        val launchAnimator =
+            createLaunchAnimator(
+                launchChange,
+                transaction,
+                finishCallback,
+                isTrampoline = trampolineCloseChange != null,
+            )
+        val animatorsList = mutableListOf(launchAnimator)
+        if (minimizeChange != null) {
+            val minimizeAnimator =
+                createMinimizeAnimator(minimizeChange, transaction, finishCallback)
+            animatorsList.add(minimizeAnimator)
+        }
+        if (trampolineCloseChange != null) {
+            val trampolineCloseAnimator =
+                createTrampolineCloseAnimator(trampolineCloseChange, transaction)
+            animatorsList.add(trampolineCloseAnimator)
+        }
+        return animatorsList
+    }
+
+    private fun getLaunchChange(info: TransitionInfo): Change? =
+        info.changes.firstOrNull { change ->
+            change.mode in LAUNCH_CHANGE_MODES && change.taskInfo?.isFreeform == true
+        }
+
+    private fun getMinimizeChange(info: TransitionInfo): Change? =
+        info.changes.firstOrNull { change ->
+            change.mode == TRANSIT_TO_BACK && change.taskInfo?.isFreeform == true
+        }
+
+    private fun getTrampolineCloseChange(info: TransitionInfo): Change? {
+        if (
+            info.changes.size < 2 ||
+                !DesktopModeFlags.ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX.isTrue
+        ) {
+            return null
+        }
+        val openChange =
+            info.changes.firstOrNull { change ->
+                change.mode == TRANSIT_OPEN && change.taskInfo?.isFreeform == true
+            }
+        val closeChange =
+            info.changes.firstOrNull { change ->
+                change.mode == TRANSIT_CLOSE && change.taskInfo?.isFreeform == true
+            }
+        val openPackage = openChange?.taskInfo?.baseIntent?.component?.packageName
+        val closePackage = closeChange?.taskInfo?.baseIntent?.component?.packageName
+        return if (openPackage != null && closePackage != null && openPackage == closePackage) {
+            closeChange
+        } else {
+            null
+        }
+    }
+
+    private fun createLaunchAnimator(
+        change: Change,
+        transaction: Transaction,
+        onAnimFinish: (Animator) -> Unit,
+        isTrampoline: Boolean,
+    ): Animator {
+        val boundsAnimator =
+            WindowAnimator.createBoundsAnimator(
+                context.resources.displayMetrics,
+                launchType.boundsAnimationParams,
+                change,
+                transaction,
+            )
+        val alphaAnimator =
+            ValueAnimator.ofFloat(0f, 1f).apply {
+                duration = launchType.alphaDurationMs
+                interpolator = Interpolators.LINEAR
+                addUpdateListener { animation ->
+                    transaction
+                        .setAlpha(change.leash, animation.animatedValue as Float)
+                        .setFrameTimeline(Choreographer.getInstance().vsyncId)
+                        .apply()
+                }
+            }
+        val clipRect = Rect(change.endAbsBounds).apply { offsetTo(0, 0) }
+        transaction.setCrop(change.leash, clipRect)
+        transaction.setCornerRadius(
+            change.leash,
+            ScreenDecorationsUtils.getWindowCornerRadius(context),
+        )
+        return AnimatorSet().apply {
+            interactionJankMonitor.begin(change.leash, context, context.mainThreadHandler, cujType)
+            if (isTrampoline) {
+                play(alphaAnimator)
+            } else {
+                playTogether(boundsAnimator, alphaAnimator)
+            }
+            addListener(
+                onEnd = { animation ->
+                    onAnimFinish(animation)
+                    interactionJankMonitor.end(cujType)
+                }
+            )
+        }
+    }
+
+    private fun createMinimizeAnimator(
+        change: Change,
+        transaction: Transaction,
+        onAnimFinish: (Animator) -> Unit,
+    ): Animator {
+        return MinimizeAnimator.create(
+            context,
+            change,
+            transaction,
+            onAnimFinish,
+            interactionJankMonitor,
+            context.mainThreadHandler,
+        )
+    }
+
+    private fun createTrampolineCloseAnimator(change: Change, transaction: Transaction): Animator {
+        return ValueAnimator.ofFloat(1f, 0f).apply {
+            duration = 100L
+            interpolator = Interpolators.LINEAR
+            addUpdateListener { animation ->
+                transaction.setAlpha(change.leash, animation.animatedValue as Float).apply()
+            }
+        }
+    }
+
+    private companion object {
+        const val TAG = "DesktopAppLaunchAnimatorHelper"
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
index 87a82f0..5a8934b 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
@@ -17,24 +17,19 @@
 package com.android.launcher3.desktop
 
 import android.animation.Animator
-import android.animation.AnimatorSet
-import android.animation.ValueAnimator
 import android.content.Context
-import android.graphics.Rect
 import android.os.IBinder
+import android.util.Log
 import android.view.SurfaceControl.Transaction
 import android.view.WindowManager.TRANSIT_OPEN
-import android.view.WindowManager.TRANSIT_TO_BACK
 import android.view.WindowManager.TRANSIT_TO_FRONT
 import android.window.IRemoteTransitionFinishedCallback
 import android.window.RemoteTransitionStub
 import android.window.TransitionInfo
-import android.window.TransitionInfo.Change
-import androidx.core.animation.addListener
+import androidx.core.util.Supplier
 import com.android.app.animation.Interpolators
-import com.android.internal.policy.ScreenDecorationsUtils
+import com.android.internal.jank.Cuj
 import com.android.quickstep.RemoteRunnable
-import com.android.wm.shell.shared.animation.MinimizeAnimator
 import com.android.wm.shell.shared.animation.WindowAnimator
 import java.util.concurrent.Executor
 
@@ -45,12 +40,19 @@
  * ([android.view.WindowManager.TRANSIT_TO_BACK]) this transition will apply a minimize animation to
  * that window.
  */
-class DesktopAppLaunchTransition(
-    private val context: Context,
-    private val mainExecutor: Executor,
+class DesktopAppLaunchTransition
+@JvmOverloads
+constructor(
+    context: Context,
     private val launchType: AppLaunchType,
+    @Cuj.CujType private val cujType: Int,
+    private val mainExecutor: Executor,
+    transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
 ) : RemoteTransitionStub() {
 
+    private val animatorHelper: DesktopAppLaunchAnimatorHelper =
+        DesktopAppLaunchAnimatorHelper(context, launchType, cujType, transactionSupplier)
+
     enum class AppLaunchType(
         val boundsAnimationParams: WindowAnimator.BoundsAnimationParams,
         val alphaDurationMs: Long,
@@ -62,15 +64,16 @@
     override fun startAnimation(
         token: IBinder,
         info: TransitionInfo,
-        t: Transaction,
+        transaction: Transaction,
         transitionFinishedCallback: IRemoteTransitionFinishedCallback,
     ) {
+        Log.v(TAG, "startAnimation: launchType=$launchType, cujType=$cujType")
         val safeTransitionFinishedCallback = RemoteRunnable {
             transitionFinishedCallback.onTransitionFinished(/* wct= */ null, /* sct= */ null)
         }
         mainExecutor.execute {
             runAnimators(info, safeTransitionFinishedCallback)
-            t.apply()
+            transaction.apply()
         }
     }
 
@@ -80,69 +83,16 @@
             animators -= animator
             if (animators.isEmpty()) finishedCallback.run()
         }
-        animators += createAnimators(info, animatorFinishedCallback)
+        animators += animatorHelper.createAnimators(info, animatorFinishedCallback)
+        if (animators.isEmpty()) {
+            finishedCallback.run()
+            return
+        }
         animators.forEach { it.start() }
     }
 
-    private fun createAnimators(
-        info: TransitionInfo,
-        finishCallback: (Animator) -> Unit,
-    ): List<Animator> {
-        val transaction = Transaction()
-        val launchAnimator =
-            createLaunchAnimator(getLaunchChange(info), transaction, finishCallback)
-        val minimizeChange = getMinimizeChange(info) ?: return listOf(launchAnimator)
-        val minimizeAnimator =
-            MinimizeAnimator.create(
-                context.resources.displayMetrics,
-                minimizeChange,
-                transaction,
-                finishCallback,
-            )
-        return listOf(launchAnimator, minimizeAnimator)
-    }
-
-    private fun getLaunchChange(info: TransitionInfo): Change =
-        requireNotNull(info.changes.firstOrNull { change -> change.mode in LAUNCH_CHANGE_MODES }) {
-            "expected an app launch Change"
-        }
-
-    private fun getMinimizeChange(info: TransitionInfo): Change? =
-        info.changes.firstOrNull { change -> change.mode == TRANSIT_TO_BACK }
-
-    private fun createLaunchAnimator(
-        change: Change,
-        transaction: Transaction,
-        onAnimFinish: (Animator) -> Unit,
-    ): Animator {
-        val boundsAnimator =
-            WindowAnimator.createBoundsAnimator(
-                context.resources.displayMetrics,
-                launchType.boundsAnimationParams,
-                change,
-                transaction,
-            )
-        val alphaAnimator =
-            ValueAnimator.ofFloat(0f, 1f).apply {
-                duration = launchType.alphaDurationMs
-                interpolator = Interpolators.LINEAR
-                addUpdateListener { animation ->
-                    transaction.setAlpha(change.leash, animation.animatedValue as Float).apply()
-                }
-            }
-        val clipRect = Rect(change.endAbsBounds).apply { offsetTo(0, 0) }
-        transaction.setCrop(change.leash, clipRect)
-        transaction.setCornerRadius(
-            change.leash,
-            ScreenDecorationsUtils.getWindowCornerRadius(context),
-        )
-        return AnimatorSet().apply {
-            playTogether(boundsAnimator, alphaAnimator)
-            addListener(onEnd = { animation -> onAnimFinish(animation) })
-        }
-    }
-
     companion object {
+        const val TAG = "DesktopAppLaunchTransition"
         /** Change modes that represent a task becoming visible / launching in Desktop mode. */
         val LAUNCH_CHANGE_MODES = intArrayOf(TRANSIT_OPEN, TRANSIT_TO_FRONT)
 
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
index 6e36305..a72b5c4 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManager.kt
@@ -23,6 +23,7 @@
 import android.window.RemoteTransition
 import android.window.TransitionFilter
 import android.window.TransitionFilter.CONTAINER_ORDER_TOP
+import com.android.internal.jank.Cuj
 import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType
 import com.android.launcher3.util.Executors.MAIN_EXECUTOR
 import com.android.quickstep.SystemUiProxy
@@ -45,8 +46,13 @@
         }
         remoteWindowLimitUnminimizeTransition =
             RemoteTransition(
-                DesktopAppLaunchTransition(context, MAIN_EXECUTOR, AppLaunchType.UNMINIMIZE),
-                "DesktopWindowLimitUnminimize"
+                DesktopAppLaunchTransition(
+                    context,
+                    AppLaunchType.UNMINIMIZE,
+                    Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_INTENT,
+                    MAIN_EXECUTOR,
+                ),
+                "DesktopWindowLimitUnminimize",
             )
         systemUiProxy.registerRemoteTransition(
             remoteWindowLimitUnminimizeTransition,
@@ -68,8 +74,7 @@
 
     private fun shouldRegisterTransitions(): Boolean =
         DesktopModeStatus.canEnterDesktopMode(context) &&
-            (DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue ||
-                DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue)
+            DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue
 
     companion object {
         private fun buildAppLaunchFilter(): TransitionFilter {
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
index 8b064d3..a01846d 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopRecentsTransitionController.kt
@@ -30,6 +30,7 @@
 import com.android.launcher3.util.Executors.MAIN_EXECUTOR
 import com.android.quickstep.SystemUiProxy
 import com.android.quickstep.TaskViewUtils
+import com.android.quickstep.util.DesksUtils.Companion.areMultiDesksFlagsEnabled
 import com.android.quickstep.views.DesktopTaskView
 import com.android.quickstep.views.TaskContainer
 import com.android.quickstep.views.TaskView
@@ -60,15 +61,24 @@
                 callback,
             )
         val transition = RemoteTransition(animRunner, appThread, "RecentsToDesktop")
-        systemUiProxy.showDesktopApps(desktopTaskView.display.displayId, transition)
+        if (areMultiDesksFlagsEnabled()) {
+            systemUiProxy.activateDesk(desktopTaskView.deskId, transition)
+        } else {
+            systemUiProxy.showDesktopApps(desktopTaskView.displayId, transition)
+        }
     }
 
     /** Launch desktop tasks from recents view */
-    fun moveToDesktop(taskContainer: TaskContainer, transitionSource: DesktopModeTransitionSource) {
+    fun moveToDesktop(
+        taskContainer: TaskContainer,
+        transitionSource: DesktopModeTransitionSource,
+        successCallback: Runnable,
+    ) {
         systemUiProxy.moveToDesktop(
             taskContainer.task.key.id,
             transitionSource,
             /* transition = */ null,
+            successCallback,
         )
     }
 
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index c50e82d..c2cabd0 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -229,9 +229,7 @@
                     (WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
             if (isPredictedIcon(child) && child.isEnabled()) {
                 PredictedAppIcon icon = (PredictedAppIcon) child;
-                boolean animateIconChange = icon.shouldAnimateIconChange(predictedItem);
-                icon.applyFromWorkspaceItem(predictedItem, animateIconChange, numViewsAnimated);
-                if (animateIconChange) {
+                if (icon.applyFromWorkspaceItemWithAnimation(predictedItem, numViewsAnimated)) {
                     numViewsAnimated++;
                 }
                 icon.finishBinding(mPredictionLongClickListener);
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
index 56945ba..70868c5 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionModel.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.model.PredictionHelper.getAppTargetFromItemInfo;
-import static com.android.launcher3.model.PredictionHelper.isTrackedForHotseatPrediction;
 import static com.android.launcher3.model.PredictionHelper.wrapAppTargetWithItemLocation;
 
 import android.app.prediction.AppTarget;
@@ -27,9 +26,12 @@
 
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
+import com.android.launcher3.model.PredictionHelper;
 import com.android.launcher3.model.data.ItemInfo;
 
 import java.util.ArrayList;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 /**
  * Model helper for app predictions in workspace
@@ -43,13 +45,18 @@
      */
     public static Bundle convertDataModelToAppTargetBundle(Context context, BgDataModel dataModel) {
         Bundle bundle = new Bundle();
-        ArrayList<AppTargetEvent> events = new ArrayList<>();
-        ArrayList<ItemInfo> workspaceItems = dataModel.getAllWorkspaceItems();
-        for (ItemInfo item : workspaceItems) {
-            AppTarget target = getAppTargetFromItemInfo(context, item);
-            if (target != null && !isTrackedForHotseatPrediction(item)) continue;
-            events.add(wrapAppTargetWithItemLocation(target, AppTargetEvent.ACTION_PIN, item));
-        }
+        ArrayList<AppTargetEvent> events = dataModel.itemsIdMap
+                .stream()
+                .filter(PredictionHelper::isTrackedForHotseatPrediction)
+                .map(item -> {
+                    AppTarget target = getAppTargetFromItemInfo(context, item);
+                    return target != null
+                            ? wrapAppTargetWithItemLocation(target, AppTargetEvent.ACTION_PIN, item)
+                            : null;
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toCollection(ArrayList::new));
+
         ArrayList<AppTarget> currentTargets = new ArrayList<>();
         FixedContainerItems hotseatItems = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
         if (hotseatItems != null) {
diff --git a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
index d604742..fd71151 100644
--- a/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/PredictionUpdateTask.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.EncryptionType.ENCRYPTED;
 import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
 
 import android.app.prediction.AppTarget;
@@ -95,7 +96,7 @@
                 itemInfo = apps.data.stream()
                         .filter(info -> user.equals(info.user) && cn.equals(info.componentName))
                         .map(ai -> {
-                            app.getIconCache().getTitleAndIcon(ai, false);
+                            app.getIconCache().getTitleAndIcon(ai, mPredictorState.lookupFlag);
                             return ai.makeWorkspaceItem(context);
                         })
                         .findAny()
@@ -106,7 +107,7 @@
                                 return null;
                             }
                             AppInfo ai = new AppInfo(context, lai, user);
-                            app.getIconCache().getTitleAndIcon(ai, lai, false);
+                            app.getIconCache().getTitleAndIcon(ai, lai, DEFAULT_LOOKUP_FLAG);
                             return ai.makeWorkspaceItem(context);
                         });
 
diff --git a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
index 2f4c6f6..74b73d4 100644
--- a/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
+++ b/quickstep/src/com/android/launcher3/model/QuickstepModelDelegate.java
@@ -23,9 +23,11 @@
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
+import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
 import static com.android.launcher3.hybridhotseat.HotseatPredictionModel.convertDataModelToAppTargetBundle;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.model.PredictionHelper.getAppTargetFromItemInfo;
 import static com.android.launcher3.model.PredictionHelper.wrapAppTargetWithItemLocation;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -45,6 +47,7 @@
 import android.content.pm.ShortcutInfo;
 import android.os.Bundle;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.StatsEvent;
 
@@ -59,6 +62,8 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.icons.cache.CacheLookupFlag;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.InstanceIdSequence;
@@ -86,6 +91,9 @@
 import java.util.Objects;
 import java.util.stream.IntStream;
 
+import javax.inject.Inject;
+import javax.inject.Named;
+
 /**
  * Model delegate which loads prediction items
  */
@@ -101,28 +109,39 @@
             nonRestorableItem("LAST_SNAPSHOT_TIME_MILLIS", 0L, ENCRYPTED);
 
     @VisibleForTesting
-    final PredictorState mAllAppsState =
-            new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
+    final PredictorState mAllAppsState = new PredictorState(
+            CONTAINER_PREDICTION, "all_apps_predictions", DEFAULT_LOOKUP_FLAG);
     @VisibleForTesting
-    final PredictorState mHotseatState =
-            new PredictorState(CONTAINER_HOTSEAT_PREDICTION, "hotseat_predictions");
+    final PredictorState mHotseatState = new PredictorState(
+            CONTAINER_HOTSEAT_PREDICTION, "hotseat_predictions", DESKTOP_ICON_FLAG);
     @VisibleForTesting
-    final PredictorState mWidgetsRecommendationState =
-            new PredictorState(CONTAINER_WIDGETS_PREDICTION, "widgets_prediction");
+    final PredictorState mWidgetsRecommendationState = new PredictorState(
+            CONTAINER_WIDGETS_PREDICTION, "widgets_prediction", DESKTOP_ICON_FLAG);
 
     private final InvariantDeviceProfile mIDP;
+    private final PackageManagerHelper mPmHelper;
     private final AppEventProducer mAppEventProducer;
+
     private final StatsManager mStatsManager;
 
     protected boolean mActive = false;
 
-    public QuickstepModelDelegate(Context context) {
+    @Inject
+    public QuickstepModelDelegate(@ApplicationContext Context context,
+            InvariantDeviceProfile idp,
+            PackageManagerHelper pmHelper,
+            @Nullable @Named("ICONS_DB") String dbFileName) {
         super(context);
-        mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
+        mIDP = idp;
+        mPmHelper = pmHelper;
 
-        mIDP = InvariantDeviceProfile.INSTANCE.get(context);
+        mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
         StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer);
-        mStatsManager = context.getSystemService(StatsManager.class);
+
+        // Only register for launcher snapshot logging if this is the primary ModelDelegate
+        // instance, as there will be additional instances that may be destroyed at any time.
+        mStatsManager = TextUtils.isEmpty(dbFileName)
+                ? null : context.getSystemService(StatsManager.class);
     }
 
     @CallSuper
@@ -151,10 +170,10 @@
         // TODO: Implement caching and preloading
 
         WorkspaceItemFactory factory =
-                new WorkspaceItemFactory(mApp, ums, mPmHelper, pinnedShortcuts, numColumns,
-                        state.containerId);
+                new WorkspaceItemFactory(mContext, ums, mPmHelper, pinnedShortcuts, numColumns,
+                        state.containerId, state.lookupFlag);
         FixedContainerItems fci = new FixedContainerItems(state.containerId,
-                state.storage.read(mApp.getContext(), factory, ums.allUsers::get));
+                state.storage.read(mContext, factory, ums.allUsers::get));
         mDataModel.extraItems.put(state.containerId, fci);
     }
 
@@ -217,7 +236,7 @@
         super.modelLoadComplete();
 
         // Log snapshot of the model
-        LauncherPrefs prefs = LauncherPrefs.get(mApp.getContext());
+        LauncherPrefs prefs = LauncherPrefs.get(mContext);
         long lastSnapshotTimeMillis = prefs.get(LAST_SNAPSHOT_TIME_MILLIS);
         // Log snapshot only if previous snapshot was older than a day
         long now = System.currentTimeMillis();
@@ -242,11 +261,7 @@
             prefs.put(LAST_SNAPSHOT_TIME_MILLIS, now);
         }
 
-        // Only register for launcher snapshot logging if this is the primary ModelDelegate
-        // instance, as there will be additional instances that may be destroyed at any time.
-        if (mIsPrimaryInstance) {
-            registerSnapshotLoggingCallback();
-        }
+        registerSnapshotLoggingCallback();
     }
 
     protected void additionalSnapshotEvents(InstanceId snapshotInstanceId){}
@@ -254,9 +269,9 @@
     /**
      * Registers a callback to log launcher workspace layout using Statsd pulled atom.
      */
-    protected void registerSnapshotLoggingCallback() {
+    private void registerSnapshotLoggingCallback() {
         if (mStatsManager == null) {
-            Log.d(TAG, "Failed to get StatsManager");
+            Log.d(TAG, "Skipping snapshot logging");
         }
 
         try {
@@ -329,7 +344,7 @@
         super.destroy();
         mActive = false;
         StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer);
-        if (mIsPrimaryInstance && mStatsManager != null) {
+        if (mStatsManager != null) {
             try {
                 mStatsManager.clearPullAtomCallback(SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT);
             } catch (RuntimeException e) {
@@ -351,25 +366,24 @@
         if (!mActive) {
             return;
         }
-        Context context = mApp.getContext();
-        AppPredictionManager apm = context.getSystemService(AppPredictionManager.class);
+        AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
         if (apm == null) {
             return;
         }
 
         registerPredictor(mAllAppsState, apm.createAppPredictionSession(
-                new AppPredictionContext.Builder(context)
+                new AppPredictionContext.Builder(mContext)
                         .setUiSurface("home")
                         .setPredictedTargetCount(mIDP.numDatabaseAllAppsColumns)
                         .build()));
 
         // TODO: get bundle
-        registerHotseatPredictor(apm, context);
+        registerHotseatPredictor(apm, mContext);
 
         registerWidgetsPredictor(apm.createAppPredictionSession(
-                new AppPredictionContext.Builder(context)
+                new AppPredictionContext.Builder(mContext)
                         .setUiSurface("widgets")
-                        .setExtras(getBundleForWidgetsOnWorkspace(context, mDataModel))
+                        .setExtras(getBundleForWidgetsOnWorkspace(mContext, mDataModel))
                         .setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION)
                         .build()));
     }
@@ -380,12 +394,11 @@
         if (!mActive) {
             return;
         }
-        Context context = mApp.getContext();
-        AppPredictionManager apm = context.getSystemService(AppPredictionManager.class);
+        AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
         if (apm == null) {
             return;
         }
-        registerHotseatPredictor(apm, context);
+        registerHotseatPredictor(apm, mContext);
     }
 
     private void registerHotseatPredictor(AppPredictionManager apm, Context context) {
@@ -410,7 +423,7 @@
             // No diff, skip
             return;
         }
-        mApp.getModel().enqueueModelUpdateTask(new PredictionUpdateTask(state, targets));
+        mModel.enqueueModelUpdateTask(new PredictionUpdateTask(state, targets));
     }
 
     private void registerWidgetsPredictor(AppPredictor predictor) {
@@ -421,7 +434,7 @@
                         // No diff, skip
                         return;
                     }
-                    mApp.getModel().enqueueModelUpdateTask(
+                    mModel.enqueueModelUpdateTask(
                             new WidgetsPredictionUpdateTask(mWidgetsRecommendationState, targets));
                 });
         mWidgetsRecommendationState.predictor.requestPredictionUpdate();
@@ -459,7 +472,7 @@
     private Bundle getBundleForWidgetsOnWorkspace(Context context, BgDataModel dataModel) {
         Bundle bundle = new Bundle();
         ArrayList<AppTargetEvent> widgetEvents =
-                dataModel.getAllWorkspaceItems().stream()
+                dataModel.itemsIdMap.stream()
                         .filter(PredictionHelper::isTrackedForWidgetPrediction)
                         .map(item -> {
                             AppTarget target = getAppTargetFromItemInfo(context, item);
@@ -478,13 +491,15 @@
         public final int containerId;
         public final PersistedItemArray<ItemInfo> storage;
         public AppPredictor predictor;
+        public CacheLookupFlag lookupFlag;
 
         private List<AppTarget> mLastTargets;
 
-        PredictorState(int containerId, String storageName) {
+        PredictorState(int containerId, String storageName, CacheLookupFlag lookupFlag) {
             this.containerId = containerId;
             storage = new PersistedItemArray<>(storageName);
             mLastTargets = Collections.emptyList();
+            this.lookupFlag = lookupFlag;
         }
 
         public void destroyPredictor() {
@@ -531,24 +546,27 @@
 
     private static class WorkspaceItemFactory implements PersistedItemArray.ItemFactory<ItemInfo> {
 
-        private final LauncherAppState mAppState;
+        private final Context mContext;
         private final UserManagerState mUMS;
         private final PackageManagerHelper mPmHelper;
         private final Map<ShortcutKey, ShortcutInfo> mPinnedShortcuts;
         private final int mMaxCount;
         private final int mContainer;
+        private final CacheLookupFlag mLookupFlag;
 
         private int mReadCount = 0;
 
-        protected WorkspaceItemFactory(LauncherAppState appState, UserManagerState ums,
+        protected WorkspaceItemFactory(
+                Context context, UserManagerState ums,
                 PackageManagerHelper pmHelper, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts,
-                int maxCount, int container) {
-            mAppState = appState;
+                int maxCount, int container, CacheLookupFlag lookupFlag) {
+            mContext = context;
             mUMS = ums;
             mPmHelper = pmHelper;
             mPinnedShortcuts = pinnedShortcuts;
             mMaxCount = maxCount;
             mContainer = container;
+            mLookupFlag = lookupFlag;
         }
 
         @Nullable
@@ -559,7 +577,7 @@
             }
             switch (itemType) {
                 case ITEM_TYPE_APPLICATION: {
-                    LauncherActivityInfo lai = mAppState.getContext()
+                    LauncherActivityInfo lai = mContext
                             .getSystemService(LauncherApps.class)
                             .resolveActivity(intent, user);
                     if (lai == null) {
@@ -567,14 +585,15 @@
                     }
                     AppInfo info = new AppInfo(
                             lai,
-                            UserCache.INSTANCE.get(mAppState.getContext()).getUserInfo(user),
-                            ApiWrapper.INSTANCE.get(mAppState.getContext()),
+                            UserCache.INSTANCE.get(mContext).getUserInfo(user),
+                            ApiWrapper.INSTANCE.get(mContext),
                             mPmHelper,
                             mUMS.isUserQuiet(user));
                     info.container = mContainer;
-                    mAppState.getIconCache().getTitleAndIcon(info, lai, false);
+                    LauncherAppState.getInstance(mContext).getIconCache()
+                            .getTitleAndIcon(info, lai, mLookupFlag);
                     mReadCount++;
-                    return info.makeWorkspaceItem(mAppState.getContext());
+                    return info.makeWorkspaceItem(mContext);
                 }
                 case ITEM_TYPE_DEEP_SHORTCUT: {
                     ShortcutKey key = ShortcutKey.fromIntent(intent, user);
@@ -585,9 +604,9 @@
                     if (si == null) {
                         return null;
                     }
-                    WorkspaceItemInfo wii = new WorkspaceItemInfo(si, mAppState.getContext());
+                    WorkspaceItemInfo wii = new WorkspaceItemInfo(si, mContext);
                     wii.container = mContainer;
-                    mAppState.getIconCache().getShortcutIcon(wii, si);
+                    LauncherAppState.getInstance(mContext).getIconCache().getShortcutIcon(wii, si);
                     mReadCount++;
                     return wii;
                 }
diff --git a/quickstep/src/com/android/launcher3/model/WellbeingModel.java b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
index 0f3aaa6..09433c5 100644
--- a/quickstep/src/com/android/launcher3/model/WellbeingModel.java
+++ b/quickstep/src/com/android/launcher3/model/WellbeingModel.java
@@ -109,9 +109,9 @@
                 ? Executors.UI_HELPER_EXECUTOR.getLooper()
                 : Executors.getPackageExecutor(mWellbeingProviderPkg).getLooper());
         mWellbeingAppChangeReceiver =
-                new SimpleBroadcastReceiver(mWorkerHandler, t -> restartObserver());
+                new SimpleBroadcastReceiver(context, mWorkerHandler, t -> restartObserver());
         mAppAddRemoveReceiver =
-                new SimpleBroadcastReceiver(mWorkerHandler, this::onAppPackageChanged);
+                new SimpleBroadcastReceiver(context, mWorkerHandler, this::onAppPackageChanged);
 
 
         mContentObserver = new ContentObserver(mWorkerHandler) {
@@ -148,8 +148,8 @@
     public void close() {
         if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
             mWorkerHandler.post(() -> {
-                mWellbeingAppChangeReceiver.unregisterReceiverSafely(mContext);
-                mAppAddRemoveReceiver.unregisterReceiverSafely(mContext);
+                mWellbeingAppChangeReceiver.unregisterReceiverSafely();
+                mAppAddRemoveReceiver.unregisterReceiverSafely();
                 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 8c98bab..ada7301 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetPredictionsRequester.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.model;
 
-import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -48,7 +47,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -56,7 +54,7 @@
  * Works with app predictor to fetch and process widget predictions displayed in a standalone
  * widget picker activity for a UI surface.
  */
-public class WidgetPredictionsRequester {
+public class WidgetPredictionsRequester implements AppPredictor.Callback {
     private static final int NUM_OF_RECOMMENDED_WIDGETS_PREDICATION = 20;
     private static final String BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets";
     // container/screenid/[positionx,positiony]/[spanx,spany]
@@ -71,6 +69,9 @@
     @NonNull
     private final String mUiSurface;
     private boolean mPredictionsAvailable;
+    @Nullable
+    private WidgetPredictionsListener mPredictionsListener = null;
+    @Nullable Predicate<WidgetItem> mFilter = null;
     @NonNull
     private final Map<ComponentKey, WidgetItem> mAllWidgets;
 
@@ -81,36 +82,49 @@
         mAllWidgets = Collections.unmodifiableMap(allWidgets);
     }
 
+    // AppPredictor.Callback -> onTargetsAvailable
+    @Override
+    @WorkerThread
+    public void onTargetsAvailable(List<AppTarget> targets) {
+        List<WidgetItem> filteredPredictions = filterPredictions(targets, mAllWidgets, mFilter);
+        List<ItemInfo> mappedPredictions = mapWidgetItemsToItemInfo(filteredPredictions);
+
+        if (!mPredictionsAvailable && mPredictionsListener != null) {
+            mPredictionsAvailable = true;
+            MAIN_EXECUTOR.execute(
+                    () -> mPredictionsListener.onPredictionsAvailable(mappedPredictions));
+        }
+    }
+
     /**
      * Requests one time predictions from the app predictions manager and invokes provided callback
-     * once predictions are available.
+     * once predictions are available. Any previous requests may be cancelled.
      *
      * @param existingWidgets widgets that are currently added to the surface;
-     * @param callback        consumer of prediction results to be called when predictions are
-     *                        available
+     * @param listener        consumer of prediction results to be called when predictions are
+     *                        available; any previous listener will no longer receive updates.
      */
+    @WorkerThread // e.g. MODEL_EXECUTOR
     public void request(List<AppWidgetProviderInfo> existingWidgets,
-            Consumer<List<ItemInfo>> callback) {
+            WidgetPredictionsListener listener) {
+        clear();
+        mPredictionsListener = listener;
+        mFilter = notOnUiSurfaceFilter(existingWidgets);
+
+        AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
+        if (apm == null) {
+            return;
+        }
+
         Bundle bundle = buildBundleForPredictionSession(existingWidgets);
-        Predicate<WidgetItem> filter = notOnUiSurfaceFilter(existingWidgets);
-
-        MODEL_EXECUTOR.execute(() -> {
-            clear();
-            AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
-            if (apm == null) {
-                return;
-            }
-
-            mAppPredictor = apm.createAppPredictionSession(
-                    new AppPredictionContext.Builder(mContext)
-                            .setUiSurface(mUiSurface)
-                            .setExtras(bundle)
-                            .setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION)
-                            .build());
-            mAppPredictor.registerPredictionUpdates(MODEL_EXECUTOR,
-                    targets -> bindPredictions(targets, filter, callback));
-            mAppPredictor.requestPredictionUpdate();
-        });
+        mAppPredictor = apm.createAppPredictionSession(
+                new AppPredictionContext.Builder(mContext)
+                        .setUiSurface(mUiSurface)
+                        .setExtras(bundle)
+                        .setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION)
+                        .build());
+        mAppPredictor.registerPredictionUpdates(MODEL_EXECUTOR, /*callback=*/ this);
+        mAppPredictor.requestPredictionUpdate();
     }
 
     /**
@@ -158,27 +172,14 @@
         return widgetItem -> !existingComponentKeys.contains(widgetItem);
     }
 
-    /** Provides the predictions returned by the predictor to the registered callback. */
-    @WorkerThread
-    private void bindPredictions(List<AppTarget> targets, Predicate<WidgetItem> filter,
-            Consumer<List<ItemInfo>> callback) {
-        if (!mPredictionsAvailable) {
-            mPredictionsAvailable = true;
-            List<WidgetItem> filteredPredictions = filterPredictions(targets, mAllWidgets, filter);
-            List<ItemInfo> mappedPredictions = mapWidgetItemsToItemInfo(filteredPredictions);
-
-            MAIN_EXECUTOR.execute(() -> callback.accept(mappedPredictions));
-            MODEL_EXECUTOR.execute(this::clear);
-        }
-    }
-
     /**
      * Applies the provided filter (e.g. widgets not on workspace) on the predictions returned by
      * the predictor.
      */
     @VisibleForTesting
     static List<WidgetItem> filterPredictions(List<AppTarget> predictions,
-            Map<ComponentKey, WidgetItem> allWidgets, Predicate<WidgetItem> filter) {
+            @NonNull Map<ComponentKey, WidgetItem> allWidgets,
+            @Nullable Predicate<WidgetItem> filter) {
         List<WidgetItem> servicePredictedItems = new ArrayList<>();
 
         for (AppTarget prediction : predictions) {
@@ -187,7 +188,7 @@
                 WidgetItem widgetItem = allWidgets.get(
                         new ComponentKey(new ComponentName(prediction.getPackageName(), className),
                                 prediction.getUser()));
-                if (widgetItem != null && filter.test(widgetItem)) {
+                if (widgetItem != null && (filter == null || filter.test(widgetItem))) {
                     servicePredictedItems.add(widgetItem);
                 }
             }
@@ -200,27 +201,34 @@
      * Converts the list of {@link WidgetItem}s to the list of {@link ItemInfo}s.
      */
     private List<ItemInfo> mapWidgetItemsToItemInfo(List<WidgetItem> widgetItems) {
-        List<ItemInfo> items;
-        if (enableCategorizedWidgetSuggestions()) {
-            WidgetRecommendationCategoryProvider categoryProvider =
-                    WidgetRecommendationCategoryProvider.newInstance(mContext);
-            items = widgetItems.stream()
-                    .map(it -> new PendingAddWidgetInfo(it.widgetInfo, CONTAINER_WIDGETS_PREDICTION,
-                            categoryProvider.getWidgetRecommendationCategory(mContext, it)))
-                    .collect(Collectors.toList());
-        } else {
-            items = widgetItems.stream().map(it -> new PendingAddWidgetInfo(it.widgetInfo,
-                    CONTAINER_WIDGETS_PREDICTION)).collect(Collectors.toList());
-        }
-        return items;
+        WidgetRecommendationCategoryProvider categoryProvider =
+                new WidgetRecommendationCategoryProvider();
+        return widgetItems.stream()
+                .map(it -> new PendingAddWidgetInfo(it.widgetInfo, CONTAINER_WIDGETS_PREDICTION,
+                        categoryProvider.getWidgetRecommendationCategory(mContext, it)))
+                .collect(Collectors.toList());
     }
 
     /** Cleans up any open prediction sessions. */
     public void clear() {
         if (mAppPredictor != null) {
+            mAppPredictor.unregisterPredictionUpdates(this);
             mAppPredictor.destroy();
             mAppPredictor = null;
         }
+        mPredictionsListener = null;
         mPredictionsAvailable = false;
+        mFilter = null;
+    }
+
+    /**
+     * Listener class to listen to updates from the {@link WidgetPredictionsRequester}
+     */
+    public interface WidgetPredictionsListener {
+        /**
+         * Callback method that is called when the predicted widgets are available.
+         * @param predictions list of predicted widgets {@link PendingAddWidgetInfo}
+         */
+        void onPredictionsAvailable(List<ItemInfo> predictions);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
index 9d9054e..0a4b7c8 100644
--- a/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
+++ b/quickstep/src/com/android/launcher3/model/WidgetsPredictionUpdateTask.java
@@ -15,9 +15,9 @@
  */
 package com.android.launcher3.model;
 
-import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
 import static com.android.launcher3.Flags.enableTieredWidgetsByDefaultInPicker;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
 
 import static java.util.stream.Collectors.groupingBy;
 import static java.util.stream.Collectors.toMap;
@@ -44,7 +44,6 @@
 import java.util.Map;
 import java.util.Random;
 import java.util.Set;
-import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 /** Task to update model as a result of predicted widgets update */
@@ -67,22 +66,20 @@
     @Override
     public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
             @NonNull AllAppsList apps) {
-        Predicate<WidgetItem> predictedWidgetsFilter = enableTieredWidgetsByDefaultInPicker()
-                ? dataModel.widgetsModel.getPredictedWidgetsFilter() : null;
-        Set<ComponentKey> widgetsInWorkspace = dataModel.appWidgets.stream().map(
-                widget -> new ComponentKey(widget.providerName, widget.user)).collect(
-                Collectors.toSet());
+        Set<ComponentKey> widgetsInWorkspace = dataModel.itemsIdMap
+                .stream()
+                .filter(WIDGET_FILTER)
+                .map(item -> new ComponentKey(item.getTargetComponent(), item.user))
+                .collect(Collectors.toSet());
 
         // Widgets (excluding shortcuts & already added widgets) that belong to apps eligible for
         // being in predictions.
         Map<ComponentKey, WidgetItem> allEligibleWidgets =
-                dataModel.widgetsModel.getWidgetsByComponentKey()
+                dataModel.widgetsModel.getWidgetsByComponentKeyForPicker()
                         .entrySet()
                         .stream()
                         .filter(entry -> entry.getValue().widgetInfo != null
                                 && !widgetsInWorkspace.contains(entry.getValue())
-                                && (predictedWidgetsFilter == null
-                                || predictedWidgetsFilter.test(entry.getValue()))
                         ).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
 
         Context context = taskController.getApp().getContext();
@@ -131,20 +128,12 @@
             }
         }
 
-        List<ItemInfo> items;
-        if (enableCategorizedWidgetSuggestions()) {
-            WidgetRecommendationCategoryProvider categoryProvider =
-                    WidgetRecommendationCategoryProvider.newInstance(context);
-            items = servicePredictedItems.stream()
-                    .map(it -> new PendingAddWidgetInfo(it.widgetInfo, CONTAINER_WIDGETS_PREDICTION,
-                            categoryProvider.getWidgetRecommendationCategory(context, it)))
-                    .collect(Collectors.toList());
-        } else {
-            items = servicePredictedItems.stream()
-                    .map(it -> new PendingAddWidgetInfo(it.widgetInfo,
-                            CONTAINER_WIDGETS_PREDICTION)).collect(
-                            Collectors.toList());
-        }
+        WidgetRecommendationCategoryProvider categoryProvider =
+                new WidgetRecommendationCategoryProvider();
+        List<ItemInfo> items = servicePredictedItems.stream()
+                .map(it -> new PendingAddWidgetInfo(it.widgetInfo, CONTAINER_WIDGETS_PREDICTION,
+                        categoryProvider.getWidgetRecommendationCategory(context, it)))
+                .collect(Collectors.toList());
         FixedContainerItems fixedContainerItems =
                 new FixedContainerItems(mPredictorState.containerId, items);
 
diff --git a/quickstep/src/com/android/launcher3/model/data/TaskViewItemInfo.kt b/quickstep/src/com/android/launcher3/model/data/TaskViewItemInfo.kt
index ee28d7a..c201ab1 100644
--- a/quickstep/src/com/android/launcher3/model/data/TaskViewItemInfo.kt
+++ b/quickstep/src/com/android/launcher3/model/data/TaskViewItemInfo.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.content.Intent
+import android.os.Process
 import androidx.annotation.VisibleForTesting
 import androidx.annotation.VisibleForTesting.Companion.PRIVATE
 import com.android.launcher3.Flags.privateSpaceRestrictAccessibilityDrag
@@ -26,34 +27,40 @@
 import com.android.launcher3.pm.UserCache
 import com.android.quickstep.TaskUtils
 import com.android.quickstep.views.TaskContainer
+import com.android.quickstep.views.TaskView
 
-class TaskViewItemInfo(taskContainer: TaskContainer) : WorkspaceItemInfo() {
+class TaskViewItemInfo(taskView: TaskView, taskContainer: TaskContainer?) : WorkspaceItemInfo() {
     @VisibleForTesting(otherwise = PRIVATE) val taskViewAtom: LauncherAtom.TaskView
 
     init {
         itemType = LauncherSettings.Favorites.ITEM_TYPE_TASK
         container = LauncherSettings.Favorites.CONTAINER_TASKSWITCHER
-        val componentKey = TaskUtils.getLaunchComponentKeyForTask(taskContainer.task.key)
-        user = componentKey.user
-        intent = Intent().setComponent(componentKey.componentName)
-        title = taskContainer.task.title
-        if (privateSpaceRestrictAccessibilityDrag()) {
-            if (
-                UserCache.getInstance(taskContainer.taskView.context)
-                    .getUserInfo(componentKey.user)
-                    .isPrivate
-            ) {
-                runtimeStatusFlags = runtimeStatusFlags or ItemInfoWithIcon.FLAG_NOT_PINNABLE
+        val componentName: String
+        if (taskContainer != null) {
+            val componentKey = TaskUtils.getLaunchComponentKeyForTask(taskContainer.task.key)
+            user = componentKey.user
+            intent = Intent().setComponent(componentKey.componentName)
+            title = taskContainer.task.title
+            if (privateSpaceRestrictAccessibilityDrag()) {
+                if (
+                    UserCache.getInstance(taskView.context).getUserInfo(componentKey.user).isPrivate
+                ) {
+                    runtimeStatusFlags = runtimeStatusFlags or ItemInfoWithIcon.FLAG_NOT_PINNABLE
+                }
             }
+            componentName = componentKey.componentName.flattenToShortString()
+        } else {
+            user = Process.myUserHandle()
+            intent = Intent()
+            componentName = ""
         }
 
         taskViewAtom =
             createTaskViewAtom(
-                type = taskContainer.taskView.type.ordinal,
-                index =
-                    taskContainer.taskView.recentsView?.indexOfChild(taskContainer.taskView) ?: -1,
-                componentName = componentKey.componentName.flattenToShortString(),
-                cardinality = taskContainer.taskView.taskContainers.size,
+                type = taskView.type.ordinal,
+                index = taskView.recentsView?.indexOfChild(taskView) ?: -1,
+                componentName,
+                cardinality = taskView.taskContainers.size,
             )
     }
 
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
deleted file mode 100644
index 2ff9b18..0000000
--- a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.java
+++ /dev/null
@@ -1,506 +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.statehandlers;
-
-import static android.view.View.VISIBLE;
-import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.content.Context;
-import android.os.Debug;
-import android.util.Log;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.statemanager.BaseState;
-import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.uioverrides.QuickstepLauncher;
-import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.views.ActivityContext;
-import com.android.quickstep.GestureState;
-import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.fallback.RecentsState;
-import com.android.wm.shell.desktopmode.IDesktopTaskListener;
-import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
-
-import java.io.PrintWriter;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Controls the visibility of the workspace and the resumed / paused state when desktop mode
- * is enabled.
- */
-public class DesktopVisibilityController {
-
-    private static final String TAG = "DesktopVisController";
-    private static final boolean DEBUG = false;
-    private final Set<DesktopVisibilityListener> mDesktopVisibilityListeners = new HashSet<>();
-    private final Set<TaskbarDesktopModeListener> mTaskbarDesktopModeListeners = new HashSet<>();
-
-    private int mVisibleDesktopTasksCount;
-    private boolean mInOverviewState;
-    private boolean mBackgroundStateEnabled;
-    private boolean mGestureInProgress;
-
-    @Nullable
-    private DesktopTaskListenerImpl mDesktopTaskListener;
-
-    @Nullable
-    private Context mContext;
-
-    public DesktopVisibilityController(@NonNull Context context) {
-        setContext(context);
-    }
-
-    /** Sets the context and re-registers the System Ui listener */
-    private void setContext(@Nullable Context context) {
-        unregisterSystemUiListener();
-        mContext = context;
-        registerSystemUiListener();
-    }
-
-    /** Register a listener with System UI to receive updates about desktop tasks state */
-    private void registerSystemUiListener() {
-        if (mContext == null) {
-            return;
-        }
-        if (mDesktopTaskListener != null) {
-            return;
-        }
-        mDesktopTaskListener = new DesktopTaskListenerImpl(this, mContext.getDisplayId());
-        SystemUiProxy.INSTANCE.get(mContext).setDesktopTaskListener(mDesktopTaskListener);
-    }
-
-    /**
-     * Clear listener from System UI that was set with {@link #registerSystemUiListener()}
-     */
-    private void unregisterSystemUiListener() {
-        if (mContext == null) {
-            return;
-        }
-        if (mDesktopTaskListener == null) {
-            return;
-        }
-        SystemUiProxy.INSTANCE.get(mContext).setDesktopTaskListener(null);
-        mDesktopTaskListener.release();
-        mDesktopTaskListener = null;
-    }
-
-    /**
-     * Whether desktop tasks are visible in desktop mode.
-     */
-    public boolean areDesktopTasksVisible() {
-        boolean desktopTasksVisible = mVisibleDesktopTasksCount > 0;
-        if (DEBUG) {
-            Log.d(TAG, "areDesktopTasksVisible: desktopVisible=" + desktopTasksVisible);
-        }
-        return desktopTasksVisible;
-    }
-
-    /**
-     * Number of visible desktop windows in desktop mode.
-     */
-    public int getVisibleDesktopTasksCount() {
-        return mVisibleDesktopTasksCount;
-    }
-
-    /** Registers a listener for Desktop Mode visibility updates. */
-    public void registerDesktopVisibilityListener(DesktopVisibilityListener listener) {
-        mDesktopVisibilityListeners.add(listener);
-    }
-
-    /** Removes a previously registered Desktop Mode visibility listener. */
-    public void unregisterDesktopVisibilityListener(DesktopVisibilityListener listener) {
-        mDesktopVisibilityListeners.remove(listener);
-    }
-
-    /** Registers a listener for Taskbar changes in Desktop Mode. */
-    public void registerTaskbarDesktopModeListener(TaskbarDesktopModeListener listener) {
-        mTaskbarDesktopModeListeners.add(listener);
-    }
-
-    /** Removes a previously registered listener for Taskbar changes in Desktop Mode. */
-    public void unregisterTaskbarDesktopModeListener(TaskbarDesktopModeListener listener) {
-        mTaskbarDesktopModeListeners.remove(listener);
-    }
-
-    /**
-     * Sets the number of desktop windows that are visible and updates launcher visibility based on
-     * it.
-     */
-    public void setVisibleDesktopTasksCount(int visibleTasksCount) {
-        if (mContext == null) {
-            return;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "setVisibleDesktopTasksCount: visibleTasksCount=" + visibleTasksCount
-                    + " currentValue=" + mVisibleDesktopTasksCount);
-        }
-
-        if (visibleTasksCount != mVisibleDesktopTasksCount) {
-            final boolean wasVisible = mVisibleDesktopTasksCount > 0;
-            final boolean isVisible = visibleTasksCount > 0;
-            final boolean wereDesktopTasksVisibleBefore = areDesktopTasksVisible();
-            mVisibleDesktopTasksCount = visibleTasksCount;
-            final boolean areDesktopTasksVisibleNow = areDesktopTasksVisible();
-            if (wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow) {
-                notifyDesktopVisibilityListeners(areDesktopTasksVisibleNow);
-            }
-
-            if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
-                    && wasVisible != isVisible) {
-                // TODO: b/333533253 - Remove after flag rollout
-                if (mVisibleDesktopTasksCount > 0) {
-                    setLauncherViewsVisibility(View.INVISIBLE);
-                    if (!mInOverviewState) {
-                        // When desktop tasks are visible & we're not in overview, we want launcher
-                        // to appear paused, this ensures that taskbar displays.
-                        markLauncherPaused();
-                    }
-                } else {
-                    setLauncherViewsVisibility(View.VISIBLE);
-                    // If desktop tasks aren't visible, ensure that launcher appears resumed to
-                    // behave normally.
-                    markLauncherResumed();
-                }
-            }
-        }
-    }
-
-    public void onLauncherStateChanged(LauncherState state) {
-        onLauncherStateChanged(
-                state, state == LauncherState.BACKGROUND_APP, state.isRecentsViewVisible);
-    }
-
-    public void onLauncherStateChanged(RecentsState state) {
-        onLauncherStateChanged(
-                state, state == RecentsState.BACKGROUND_APP, state.isRecentsViewVisible());
-    }
-
-    /**
-     * Process launcher state change and update launcher view visibility based on desktop state
-     */
-    public void onLauncherStateChanged(
-            BaseState<?> state, boolean isBackgroundAppState, boolean isRecentsViewVisible) {
-        if (DEBUG) {
-            Log.d(TAG, "onLauncherStateChanged: newState=" + state);
-        }
-        setBackgroundStateEnabled(isBackgroundAppState);
-        // Desktop visibility tracks overview and background state separately
-        setOverviewStateEnabled(!isBackgroundAppState && isRecentsViewVisible);
-    }
-
-    private void setOverviewStateEnabled(boolean overviewStateEnabled) {
-        if (mContext == null) {
-            return;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled
-                    + " currentValue=" + mInOverviewState);
-        }
-        if (overviewStateEnabled != mInOverviewState) {
-            mInOverviewState = overviewStateEnabled;
-            final boolean areDesktopTasksVisibleNow = areDesktopTasksVisible();
-
-            if (ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) {
-                return;
-            }
-            // TODO: b/333533253 - Clean up after flag rollout
-
-            if (mInOverviewState) {
-                setLauncherViewsVisibility(View.VISIBLE);
-                markLauncherResumed();
-            } else if (areDesktopTasksVisibleNow && !mGestureInProgress) {
-                // Switching out of overview state and gesture finished.
-                // If desktop tasks are still visible, hide launcher again.
-                setLauncherViewsVisibility(View.INVISIBLE);
-                markLauncherPaused();
-            }
-        }
-    }
-
-    private void notifyDesktopVisibilityListeners(boolean areDesktopTasksVisible) {
-        if (mContext == null) {
-            return;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "notifyDesktopVisibilityListeners: visible=" + areDesktopTasksVisible);
-        }
-        for (DesktopVisibilityListener listener : mDesktopVisibilityListeners) {
-            listener.onDesktopVisibilityChanged(areDesktopTasksVisible);
-        }
-        DisplayController.INSTANCE.get(mContext).notifyConfigChange();
-    }
-
-    private void notifyTaskbarDesktopModeListeners(boolean doesAnyTaskRequireTaskbarRounding) {
-        if (DEBUG) {
-            Log.d(TAG, "notifyTaskbarDesktopModeListeners: doesAnyTaskRequireTaskbarRounding="
-                    + doesAnyTaskRequireTaskbarRounding);
-        }
-        for (TaskbarDesktopModeListener listener : mTaskbarDesktopModeListeners) {
-            listener.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding);
-        }
-    }
-
-    /**
-     * TODO: b/333533253 - Remove after flag rollout
-     */
-    private void setBackgroundStateEnabled(boolean backgroundStateEnabled) {
-        if (DEBUG) {
-            Log.d(TAG, "setBackgroundStateEnabled: enabled=" + backgroundStateEnabled
-                    + " currentValue=" + mBackgroundStateEnabled);
-        }
-        if (backgroundStateEnabled != mBackgroundStateEnabled) {
-            mBackgroundStateEnabled = backgroundStateEnabled;
-            if (mBackgroundStateEnabled) {
-                setLauncherViewsVisibility(View.VISIBLE);
-                markLauncherResumed();
-            } else if (areDesktopTasksVisible() && !mGestureInProgress) {
-                // Switching out of background state. If desktop tasks are visible, pause launcher.
-                setLauncherViewsVisibility(View.INVISIBLE);
-                markLauncherPaused();
-            }
-        }
-    }
-
-    /**
-     * Whether recents gesture is currently in progress.
-     *
-     * TODO: b/333533253 - Remove after flag rollout
-     */
-    public boolean isRecentsGestureInProgress() {
-        return mGestureInProgress;
-    }
-
-    /**
-     * Notify controller that recents gesture has started.
-     *
-     * TODO: b/333533253 - Remove after flag rollout
-     */
-    public void setRecentsGestureStart() {
-        if (DEBUG) {
-            Log.d(TAG, "setRecentsGestureStart");
-        }
-        setRecentsGestureInProgress(true);
-    }
-
-    /**
-     * Notify controller that recents gesture finished with the given
-     * {@link com.android.quickstep.GestureState.GestureEndTarget}
-     *
-     * TODO: b/333533253 - Remove after flag rollout
-     */
-    public void setRecentsGestureEnd(@Nullable GestureState.GestureEndTarget endTarget) {
-        if (DEBUG) {
-            Log.d(TAG, "setRecentsGestureEnd: endTarget=" + endTarget);
-        }
-        setRecentsGestureInProgress(false);
-
-        if (endTarget == null) {
-            // Gesture did not result in a new end target. Ensure launchers gets paused again.
-            markLauncherPaused();
-        }
-    }
-
-    /**
-     * TODO: b/333533253 - Remove after flag rollout
-     */
-    private void setRecentsGestureInProgress(boolean gestureInProgress) {
-        if (gestureInProgress != mGestureInProgress) {
-            mGestureInProgress = gestureInProgress;
-        }
-    }
-
-    /**
-     * TODO: b/333533253 - Remove after flag rollout
-     */
-    private void setLauncherViewsVisibility(int visibility) {
-        if (mContext == null) {
-            return;
-        }
-        if (ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) {
-            return;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "setLauncherViewsVisibility: visibility=" + visibility + " "
-                    + Debug.getCaller());
-        }
-        if (!(mContext instanceof ActivityContext activity)) {
-            return;
-        }
-        View dragLayer = activity.getDragLayer();
-        if (dragLayer != null) {
-            dragLayer.setVisibility(visibility);
-        }
-        if (!(activity instanceof Launcher launcher)) {
-            return;
-        }
-        View workspaceView = launcher.getWorkspace();
-        if (workspaceView != null) {
-            workspaceView.setVisibility(visibility);
-        }
-        if (launcher instanceof QuickstepLauncher ql
-                && ql.getTaskbarUIController() != null
-                && mVisibleDesktopTasksCount != 0) {
-            ql.getTaskbarUIController().onLauncherVisibilityChanged(visibility == VISIBLE);
-        }
-    }
-
-    /**
-     * TODO: b/333533253 - Remove after flag rollout
-     */
-    private void markLauncherPaused() {
-        if (mContext == null) {
-            return;
-        }
-        if (ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) {
-            return;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "markLauncherPaused " + Debug.getCaller());
-        }
-        StatefulActivity<LauncherState> activity =
-                QuickstepLauncher.ACTIVITY_TRACKER.getCreatedContext();
-        if (activity != null) {
-            activity.setPaused();
-        }
-    }
-
-    /**
-     * TODO: b/333533253 - Remove after flag rollout
-     */
-    private void markLauncherResumed() {
-        if (mContext == null) {
-            return;
-        }
-        if (ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) {
-            return;
-        }
-        if (DEBUG) {
-            Log.d(TAG, "markLauncherResumed " + Debug.getCaller());
-        }
-        StatefulActivity<LauncherState> activity =
-                QuickstepLauncher.ACTIVITY_TRACKER.getCreatedContext();
-        // Check activity state before calling setResumed(). Launcher may have been actually
-        // paused (eg fullscreen task moved to front).
-        // In this case we should not mark the activity as resumed.
-        if (activity != null && activity.isResumed()) {
-            activity.setResumed();
-        }
-    }
-
-    public void onDestroy() {
-        setContext(null);
-    }
-
-    public void dumpLogs(String prefix, PrintWriter pw) {
-        pw.println(prefix + "DesktopVisibilityController:");
-
-        pw.println(prefix + "\tmDesktopVisibilityListeners=" + mDesktopVisibilityListeners);
-        pw.println(prefix + "\tmVisibleDesktopTasksCount=" + mVisibleDesktopTasksCount);
-        pw.println(prefix + "\tmInOverviewState=" + mInOverviewState);
-        pw.println(prefix + "\tmBackgroundStateEnabled=" + mBackgroundStateEnabled);
-        pw.println(prefix + "\tmGestureInProgress=" + mGestureInProgress);
-        pw.println(prefix + "\tmDesktopTaskListener=" + mDesktopTaskListener);
-        pw.println(prefix + "\tmContext=" + mContext);
-    }
-
-    /** A listener for when the user enters/exits Desktop Mode. */
-    public interface DesktopVisibilityListener {
-        /**
-         * Callback for when the user enters or exits Desktop Mode
-         *
-         * @param visible whether Desktop Mode is now visible
-         */
-        void onDesktopVisibilityChanged(boolean visible);
-    }
-
-    /**
-     * Wrapper for the IDesktopTaskListener stub to prevent lingering references to the launcher
-     * activity via the controller.
-     */
-    private static class DesktopTaskListenerImpl extends IDesktopTaskListener.Stub {
-
-        private DesktopVisibilityController mController;
-        private final int mDisplayId;
-
-        DesktopTaskListenerImpl(@NonNull DesktopVisibilityController controller, int displayId) {
-            mController = controller;
-            mDisplayId = displayId;
-        }
-
-        /**
-         * Clears any references to the controller.
-         */
-        void release() {
-            mController = null;
-        }
-
-        @Override
-        public void onTasksVisibilityChanged(int displayId, int visibleTasksCount) {
-            MAIN_EXECUTOR.execute(() -> {
-                if (mController != null && displayId == mDisplayId) {
-                    if (DEBUG) {
-                        Log.d(TAG, "desktop visible tasks count changed=" + visibleTasksCount);
-                    }
-                    mController.setVisibleDesktopTasksCount(visibleTasksCount);
-                }
-            });
-        }
-
-        @Override
-        public void onStashedChanged(int displayId, boolean stashed) {
-            Log.w(TAG, "DesktopTaskListenerImpl: onStashedChanged is deprecated");
-        }
-
-        @Override
-        public void onTaskbarCornerRoundingUpdate(boolean doesAnyTaskRequireTaskbarRounding) {
-            MAIN_EXECUTOR.execute(() -> {
-                if (mController != null && DesktopModeStatus.useRoundedCorners()) {
-                    Log.d(TAG, "DesktopTaskListenerImpl: doesAnyTaskRequireTaskbarRounding= "
-                            + doesAnyTaskRequireTaskbarRounding);
-                    mController.notifyTaskbarDesktopModeListeners(
-                            doesAnyTaskRequireTaskbarRounding);
-                }
-            });
-        }
-
-        public void onEnterDesktopModeTransitionStarted(int transitionDuration) {
-
-        }
-
-        @Override
-        public void onExitDesktopModeTransitionStarted(int transitionDuration) {
-
-        }
-    }
-
-    /** A listener for Taskbar in Desktop Mode. */
-    public interface TaskbarDesktopModeListener {
-        /**
-         * Callback for when task is resized in desktop mode.
-         *
-         * @param doesAnyTaskRequireTaskbarRounding whether task requires taskbar corner roundness.
-         */
-        void onTaskbarCornerRoundingUpdate(boolean doesAnyTaskRequireTaskbarRounding);
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
new file mode 100644
index 0000000..2402a28
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/statehandlers/DesktopVisibilityController.kt
@@ -0,0 +1,764 @@
+/*
+ * 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.statehandlers
+
+import android.content.Context
+import android.os.Debug
+import android.util.Log
+import android.util.Slog
+import android.util.SparseArray
+import android.view.Display.DEFAULT_DISPLAY
+import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY
+import androidx.core.util.forEach
+import com.android.launcher3.LauncherState
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.statemanager.BaseState
+import com.android.launcher3.statemanager.StatefulActivity
+import com.android.launcher3.uioverrides.QuickstepLauncher
+import com.android.launcher3.util.DaggerSingletonObject
+import com.android.launcher3.util.DaggerSingletonTracker
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.window.WindowManagerProxy.DesktopVisibilityListener
+import com.android.quickstep.GestureState.GestureEndTarget
+import com.android.quickstep.SystemUiProxy
+import com.android.quickstep.fallback.RecentsState
+import com.android.wm.shell.desktopmode.DisplayDeskState
+import com.android.wm.shell.desktopmode.IDesktopTaskListener.Stub
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import java.io.PrintWriter
+import java.lang.ref.WeakReference
+import javax.inject.Inject
+
+/**
+ * Controls the visibility of the workspace and the resumed / paused state when desktop mode is
+ * enabled.
+ */
+@LauncherAppSingleton
+class DesktopVisibilityController
+@Inject
+constructor(
+    @ApplicationContext private val context: Context,
+    systemUiProxy: SystemUiProxy,
+    lifecycleTracker: DaggerSingletonTracker,
+) {
+    /**
+     * Tracks the desks configurations on each display.
+     *
+     * (Used only when multiple desks are enabled).
+     *
+     * @property displayId The ID of the display this object represents.
+     * @property activeDeskId The ID of the active desk on the associated display (if any). It has a
+     *   value of `INACTIVE_DESK_ID` (-1) if there are no active desks. Note that there can only be
+     *   at most one active desk on each display.
+     * @property deskIds a set containing the IDs of the desks on the associated display.
+     */
+    private data class DisplayDeskConfig(
+        val displayId: Int,
+        var activeDeskId: Int = INACTIVE_DESK_ID,
+        val deskIds: MutableSet<Int>,
+    )
+
+    /** True if it is possible to create new desks on current setup. */
+    var canCreateDesks: Boolean = false
+        private set(value) {
+            if (field == value) return
+            field = value
+            desktopVisibilityListeners.forEach { it.onCanCreateDesksChanged(field) }
+        }
+
+    /** Maps each display by its ID to its desks configuration. */
+    private val displaysDesksConfigsMap = SparseArray<DisplayDeskConfig>()
+
+    private val desktopVisibilityListeners: MutableSet<DesktopVisibilityListener> = HashSet()
+    private val taskbarDesktopModeListeners: MutableSet<TaskbarDesktopModeListener> = HashSet()
+
+    // This simply indicates that user is currently in desktop mode or not.
+    var isInDesktopMode = false
+        private set
+
+    // to track if any pending notification to be done.
+    var isNotifyingDesktopVisibilityPending = false
+
+    // to let launcher hold off on notifying desktop visibility listeners.
+    var launcherAnimationRunning = false
+
+    // TODO: b/394387739 - Deprecate this and replace it with something that tracks the count per
+    //  desk.
+    /**
+     * Number of visible desktop windows in desktop mode. This can be > 0 when user goes to overview
+     * from desktop window mode.
+     */
+    var visibleDesktopTasksCount: Int = 0
+        /**
+         * Sets the number of desktop windows that are visible and updates launcher visibility based
+         * on it.
+         */
+        set(visibleTasksCount) {
+            if (DEBUG) {
+                Log.d(
+                    TAG,
+                    ("setVisibleDesktopTasksCount: visibleTasksCount=" +
+                        visibleTasksCount +
+                        " currentValue=" +
+                        field),
+                )
+            }
+
+            if (visibleTasksCount != field) {
+                if (visibleDesktopTasksCount == 0 && visibleTasksCount == 1) {
+                    isInDesktopMode = true
+                }
+                if (visibleDesktopTasksCount == 1 && visibleTasksCount == 0) {
+                    isInDesktopMode = false
+                }
+                val wasVisible = field > 0
+                val isVisible = visibleTasksCount > 0
+                val wereDesktopTasksVisibleBefore = areDesktopTasksVisibleAndNotInOverview()
+                field = visibleTasksCount
+                val areDesktopTasksVisibleNow = areDesktopTasksVisibleAndNotInOverview()
+
+                if (
+                    wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow ||
+                        wasVisible != isVisible
+                ) {
+                    if (!launcherAnimationRunning) {
+                        notifyIsInDesktopModeChanged(DEFAULT_DISPLAY, areDesktopTasksVisibleNow)
+                    } else {
+                        isNotifyingDesktopVisibilityPending = true
+                    }
+                }
+
+                if (
+                    !ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue && wasVisible != isVisible
+                ) {
+                    // TODO: b/333533253 - Remove after flag rollout
+                    if (field > 0) {
+                        if (!inOverviewState) {
+                            // When desktop tasks are visible & we're not in overview, we want
+                            // launcher
+                            // to appear paused, this ensures that taskbar displays.
+                            markLauncherPaused()
+                        }
+                    } else {
+                        // If desktop tasks aren't visible, ensure that launcher appears resumed to
+                        // behave normally.
+                        markLauncherResumed()
+                    }
+                }
+            }
+        }
+
+    private var inOverviewState = false
+    private var backgroundStateEnabled = false
+    private var gestureInProgress = false
+
+    private var desktopTaskListener: DesktopTaskListenerImpl?
+
+    init {
+        desktopTaskListener = DesktopTaskListenerImpl(this, context.displayId)
+        systemUiProxy.setDesktopTaskListener(desktopTaskListener)
+
+        lifecycleTracker.addCloseable {
+            desktopTaskListener = null
+            systemUiProxy.setDesktopTaskListener(null)
+        }
+    }
+
+    /**
+     * Returns the ID of the active desk (if any) on the display whose ID is [displayId], or
+     * [INACTIVE_DESK_ID] if no desk is currently active or the multiple desks feature is disabled.
+     */
+    fun getActiveDeskId(displayId: Int): Int {
+        if (!DesktopModeStatus.enableMultipleDesktops(context)) {
+            // When the multiple desks feature is disabled, callers should not rely on the concept
+            // of a desk ID.
+            return INACTIVE_DESK_ID
+        }
+
+        return getDisplayDeskConfig(displayId)?.activeDeskId ?: INACTIVE_DESK_ID
+    }
+
+    /** Returns whether a desk is currently active on the display with the given [displayId]. */
+    fun isInDesktopMode(displayId: Int): Boolean {
+        if (!DesktopModeStatus.enableMultipleDesktops(context)) {
+            return isInDesktopMode
+        }
+
+        val activeDeskId = getDisplayDeskConfig(displayId)?.activeDeskId ?: INACTIVE_DESK_ID
+        val isInDesktopMode = activeDeskId != INACTIVE_DESK_ID
+        if (DEBUG) {
+            Log.d(TAG, "isInDesktopMode: $isInDesktopMode")
+        }
+        return isInDesktopMode
+    }
+
+    /**
+     * Returns whether a desk is currently active on the display with the given [displayId] and
+     * Overview is not active.
+     */
+    fun isInDesktopModeAndNotInOverview(displayId: Int): Boolean {
+        if (!DesktopModeStatus.enableMultipleDesktops(context)) {
+            return areDesktopTasksVisibleAndNotInOverview()
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "isInDesktopModeAndNotInOverview: overview=$inOverviewState")
+        }
+        return isInDesktopMode(displayId) && !inOverviewState
+    }
+
+    /** Whether desktop tasks are visible in desktop mode. */
+    private fun areDesktopTasksVisibleAndNotInOverview(): Boolean {
+        val desktopTasksVisible: Boolean = visibleDesktopTasksCount > 0
+        if (DEBUG) {
+            Log.d(
+                TAG,
+                ("areDesktopTasksVisible: desktopVisible=" +
+                    desktopTasksVisible +
+                    " overview=" +
+                    inOverviewState),
+            )
+        }
+        return desktopTasksVisible && !inOverviewState
+    }
+
+    /** Registers a listener for Taskbar changes in Desktop Mode. */
+    fun registerTaskbarDesktopModeListener(listener: TaskbarDesktopModeListener) {
+        taskbarDesktopModeListeners.add(listener)
+    }
+
+    /** Removes a previously registered listener for Taskbar changes in Desktop Mode. */
+    fun unregisterTaskbarDesktopModeListener(listener: TaskbarDesktopModeListener) {
+        taskbarDesktopModeListeners.remove(listener)
+    }
+
+    fun onLauncherStateChanged(state: LauncherState) {
+        onLauncherStateChanged(
+            state,
+            state === LauncherState.BACKGROUND_APP,
+            state.isRecentsViewVisible,
+        )
+    }
+
+    /**
+     * Launcher Driven Desktop Mode changes. For example, swipe to home and quick switch from
+     * Desktop Windowing Mode. if there is any pending notification please notify desktop visibility
+     * listeners.
+     */
+    fun onLauncherAnimationFromDesktopEnd() {
+        launcherAnimationRunning = false
+        if (isNotifyingDesktopVisibilityPending) {
+            isNotifyingDesktopVisibilityPending = false
+            notifyIsInDesktopModeChanged(
+                DEFAULT_DISPLAY,
+                isInDesktopModeAndNotInOverview(DEFAULT_DISPLAY),
+            )
+        }
+    }
+
+    fun onLauncherStateChanged(state: RecentsState) {
+        onLauncherStateChanged(
+            state,
+            state === RecentsState.BACKGROUND_APP,
+            state.isRecentsViewVisible,
+        )
+    }
+
+    /** Process launcher state change and update launcher view visibility based on desktop state */
+    fun onLauncherStateChanged(
+        state: BaseState<*>,
+        isBackgroundAppState: Boolean,
+        isRecentsViewVisible: Boolean,
+    ) {
+        if (DEBUG) {
+            Log.d(TAG, "onLauncherStateChanged: newState=$state")
+        }
+        setBackgroundStateEnabled(isBackgroundAppState)
+        // Desktop visibility tracks overview and background state separately
+        setOverviewStateEnabled(!isBackgroundAppState && isRecentsViewVisible)
+    }
+
+    private fun setOverviewStateEnabled(overviewStateEnabled: Boolean) {
+        if (DEBUG) {
+            Log.d(
+                TAG,
+                ("setOverviewStateEnabled: enabled=" +
+                    overviewStateEnabled +
+                    " currentValue=" +
+                    inOverviewState),
+            )
+        }
+        if (overviewStateEnabled != inOverviewState) {
+            val wereDesktopTasksVisibleBefore = areDesktopTasksVisibleAndNotInOverview()
+            inOverviewState = overviewStateEnabled
+            val areDesktopTasksVisibleNow = areDesktopTasksVisibleAndNotInOverview()
+
+            if (!DesktopModeStatus.enableMultipleDesktops(context)) {
+                if (wereDesktopTasksVisibleBefore != areDesktopTasksVisibleNow) {
+                    notifyIsInDesktopModeChanged(DEFAULT_DISPLAY, areDesktopTasksVisibleNow)
+                }
+            } else {
+                // When overview state changes, it changes together on all displays.
+                displaysDesksConfigsMap.forEach { displayId, deskConfig ->
+                    // Overview affects the state of desks only if desktop mode is active on this
+                    // display.
+                    if (isInDesktopMode(displayId)) {
+                        notifyIsInDesktopModeChanged(
+                            displayId,
+                            isInDesktopModeAndNotInOverview(displayId),
+                        )
+                    }
+                }
+            }
+
+            if (ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue) {
+                return
+            }
+
+            // TODO: b/333533253 - Clean up after flag rollout
+            if (inOverviewState) {
+                markLauncherResumed()
+            } else if (areDesktopTasksVisibleNow && !gestureInProgress) {
+                // Switching out of overview state and gesture finished.
+                // If desktop tasks are still visible, hide launcher again.
+                markLauncherPaused()
+            }
+        }
+    }
+
+    /** Registers a listener for Taskbar changes in Desktop Mode. */
+    fun registerDesktopVisibilityListener(listener: DesktopVisibilityListener) {
+        desktopVisibilityListeners.add(listener)
+    }
+
+    /** Removes a previously registered listener for Taskbar changes in Desktop Mode. */
+    fun unregisterDesktopVisibilityListener(listener: DesktopVisibilityListener) {
+        desktopVisibilityListeners.remove(listener)
+    }
+
+    private fun notifyIsInDesktopModeChanged(
+        displayId: Int,
+        isInDesktopModeAndNotInOverview: Boolean,
+    ) {
+        if (DEBUG) {
+            Log.d(
+                TAG,
+                "notifyIsInDesktopModeChanged: displayId=$displayId, isInDesktopModeAndNotInOverview=$isInDesktopModeAndNotInOverview",
+            )
+        }
+
+        for (listener in desktopVisibilityListeners) {
+            listener.onIsInDesktopModeChanged(displayId, isInDesktopModeAndNotInOverview)
+        }
+    }
+
+    private fun notifyTaskbarDesktopModeListeners(doesAnyTaskRequireTaskbarRounding: Boolean) {
+        if (DEBUG) {
+            Log.d(
+                TAG,
+                "notifyTaskbarDesktopModeListeners: doesAnyTaskRequireTaskbarRounding=" +
+                    doesAnyTaskRequireTaskbarRounding,
+            )
+        }
+        for (listener in taskbarDesktopModeListeners) {
+            listener.onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding)
+        }
+    }
+
+    private fun notifyTaskbarDesktopModeListenersForEntry(duration: Int) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyTaskbarDesktopModeListenersForEntry: duration=" + duration)
+        }
+        for (listener in taskbarDesktopModeListeners) {
+            listener.onEnterDesktopMode(duration)
+        }
+        DisplayController.INSTANCE.get(context).notifyConfigChange()
+    }
+
+    private fun notifyTaskbarDesktopModeListenersForExit(duration: Int) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyTaskbarDesktopModeListenersForExit: duration=" + duration)
+        }
+        for (listener in taskbarDesktopModeListeners) {
+            listener.onExitDesktopMode(duration)
+        }
+        DisplayController.INSTANCE.get(context).notifyConfigChange()
+    }
+
+    private fun notifyOnDeskAdded(displayId: Int, deskId: Int) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyOnDeskAdded: displayId=$displayId, deskId=$deskId")
+        }
+
+        for (listener in desktopVisibilityListeners) {
+            listener.onDeskAdded(displayId, deskId)
+        }
+    }
+
+    private fun notifyOnDeskRemoved(displayId: Int, deskId: Int) {
+        if (DEBUG) {
+            Log.d(TAG, "notifyOnDeskRemoved: displayId=$displayId, deskId=$deskId")
+        }
+
+        for (listener in desktopVisibilityListeners) {
+            listener.onDeskRemoved(displayId, deskId)
+        }
+    }
+
+    private fun notifyOnActiveDeskChanged(displayId: Int, newActiveDesk: Int, oldActiveDesk: Int) {
+        if (DEBUG) {
+            Log.d(
+                TAG,
+                "notifyOnActiveDeskChanged: displayId=$displayId, newActiveDesk=$newActiveDesk, oldActiveDesk=$oldActiveDesk",
+            )
+        }
+
+        for (listener in desktopVisibilityListeners) {
+            listener.onActiveDeskChanged(displayId, newActiveDesk, oldActiveDesk)
+        }
+    }
+
+    /** TODO: b/333533253 - Remove after flag rollout */
+    private fun setBackgroundStateEnabled(backgroundStateEnabled: Boolean) {
+        if (DEBUG) {
+            Log.d(
+                TAG,
+                ("setBackgroundStateEnabled: enabled=" +
+                    backgroundStateEnabled +
+                    " currentValue=" +
+                    this.backgroundStateEnabled),
+            )
+        }
+        if (backgroundStateEnabled != this.backgroundStateEnabled) {
+            this.backgroundStateEnabled = backgroundStateEnabled
+            if (this.backgroundStateEnabled) {
+                markLauncherResumed()
+            } else if (areDesktopTasksVisibleAndNotInOverview() && !gestureInProgress) {
+                // Switching out of background state. If desktop tasks are visible, pause launcher.
+                markLauncherPaused()
+            }
+        }
+    }
+
+    var isRecentsGestureInProgress: Boolean
+        /**
+         * Whether recents gesture is currently in progress.
+         *
+         * TODO: b/333533253 - Remove after flag rollout
+         */
+        get() = gestureInProgress
+        /** TODO: b/333533253 - Remove after flag rollout */
+        private set(gestureInProgress) {
+            if (gestureInProgress != this.gestureInProgress) {
+                this.gestureInProgress = gestureInProgress
+            }
+        }
+
+    /**
+     * Notify controller that recents gesture has started.
+     *
+     * TODO: b/333533253 - Remove after flag rollout
+     */
+    fun setRecentsGestureStart() {
+        if (DEBUG) {
+            Log.d(TAG, "setRecentsGestureStart")
+        }
+        isRecentsGestureInProgress = true
+    }
+
+    /**
+     * Notify controller that recents gesture finished with the given
+     * [com.android.quickstep.GestureState.GestureEndTarget]
+     *
+     * TODO: b/333533253 - Remove after flag rollout
+     */
+    fun setRecentsGestureEnd(endTarget: GestureEndTarget?) {
+        if (DEBUG) {
+            Log.d(TAG, "setRecentsGestureEnd: endTarget=$endTarget")
+        }
+        isRecentsGestureInProgress = false
+
+        if (endTarget == null) {
+            // Gesture did not result in a new end target. Ensure launchers gets paused again.
+            markLauncherPaused()
+        }
+    }
+
+    private fun onListenerConnected(
+        displayDeskStates: Array<DisplayDeskState>,
+        canCreateDesks: Boolean,
+    ) {
+        if (!DesktopModeStatus.enableMultipleDesktops(context)) {
+            return
+        }
+
+        displaysDesksConfigsMap.clear()
+
+        displayDeskStates.forEach { displayDeskState ->
+            displaysDesksConfigsMap[displayDeskState.displayId] =
+                DisplayDeskConfig(
+                    displayId = displayDeskState.displayId,
+                    activeDeskId = displayDeskState.activeDeskId,
+                    deskIds = displayDeskState.deskIds.toMutableSet(),
+                )
+        }
+
+        this.canCreateDesks = canCreateDesks
+    }
+
+    private fun getDisplayDeskConfig(displayId: Int) =
+        displaysDesksConfigsMap[displayId]
+            ?: null.also { Slog.e(TAG, "Expected non-null desk config for display: $displayId") }
+
+    private fun onCanCreateDesksChanged(canCreateDesks: Boolean) {
+        if (!DesktopModeStatus.enableMultipleDesktops(context)) {
+            return
+        }
+
+        this.canCreateDesks = canCreateDesks
+    }
+
+    private fun onDeskAdded(displayId: Int, deskId: Int) {
+        if (!DesktopModeStatus.enableMultipleDesktops(context)) {
+            return
+        }
+
+        getDisplayDeskConfig(displayId)?.also {
+            check(it.deskIds.add(deskId)) {
+                "Found a duplicate desk Id: $deskId on display: $displayId"
+            }
+        }
+
+        notifyOnDeskAdded(displayId, deskId)
+    }
+
+    private fun onDeskRemoved(displayId: Int, deskId: Int) {
+        if (!DesktopModeStatus.enableMultipleDesktops(context)) {
+            return
+        }
+
+        getDisplayDeskConfig(displayId)?.also {
+            check(it.deskIds.remove(deskId)) {
+                "Removing non-existing desk Id: $deskId on display: $displayId"
+            }
+            if (it.activeDeskId == deskId) {
+                it.activeDeskId = INACTIVE_DESK_ID
+            }
+        }
+
+        notifyOnDeskRemoved(displayId, deskId)
+    }
+
+    private fun onActiveDeskChanged(displayId: Int, newActiveDesk: Int, oldActiveDesk: Int) {
+        if (!DesktopModeStatus.enableMultipleDesktops(context)) {
+            return
+        }
+
+        val wasInDesktopMode = isInDesktopModeAndNotInOverview(displayId)
+
+        getDisplayDeskConfig(displayId)?.also {
+            check(oldActiveDesk == it.activeDeskId) {
+                "Mismatch between the Shell's oldActiveDesk: $oldActiveDesk, and Launcher's: ${it.activeDeskId}"
+            }
+            check(newActiveDesk == INACTIVE_DESK_ID || it.deskIds.contains(newActiveDesk)) {
+                "newActiveDesk: $newActiveDesk was never added to display: $displayId"
+            }
+            it.activeDeskId = newActiveDesk
+        }
+
+        if (newActiveDesk != oldActiveDesk) {
+            notifyOnActiveDeskChanged(displayId, newActiveDesk, oldActiveDesk)
+        }
+
+        if (wasInDesktopMode != isInDesktopModeAndNotInOverview(displayId)) {
+            notifyIsInDesktopModeChanged(displayId, !wasInDesktopMode)
+        }
+    }
+
+    /** TODO: b/333533253 - Remove after flag rollout */
+    private fun markLauncherPaused() {
+        if (ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue) {
+            return
+        }
+        if (DEBUG) {
+            Log.d(TAG, "markLauncherPaused " + Debug.getCaller())
+        }
+        val activity: StatefulActivity<LauncherState>? =
+            QuickstepLauncher.ACTIVITY_TRACKER.getCreatedContext()
+        activity?.setPaused()
+    }
+
+    /** TODO: b/333533253 - Remove after flag rollout */
+    private fun markLauncherResumed() {
+        if (ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue) {
+            return
+        }
+        if (DEBUG) {
+            Log.d(TAG, "markLauncherResumed " + Debug.getCaller())
+        }
+        val activity: StatefulActivity<LauncherState>? =
+            QuickstepLauncher.ACTIVITY_TRACKER.getCreatedContext()
+        // Check activity state before calling setResumed(). Launcher may have been actually
+        // paused (eg fullscreen task moved to front).
+        // In this case we should not mark the activity as resumed.
+        if (activity != null && activity.isResumed) {
+            activity.setResumed()
+        }
+    }
+
+    fun dumpLogs(prefix: String, pw: PrintWriter) {
+        pw.println(prefix + "DesktopVisibilityController:")
+
+        pw.println("$prefix\tdesktopVisibilityListeners=$desktopVisibilityListeners")
+        pw.println("$prefix\tvisibleDesktopTasksCount=$visibleDesktopTasksCount")
+        pw.println("$prefix\tinOverviewState=$inOverviewState")
+        pw.println("$prefix\tbackgroundStateEnabled=$backgroundStateEnabled")
+        pw.println("$prefix\tgestureInProgress=$gestureInProgress")
+        pw.println("$prefix\tdesktopTaskListener=$desktopTaskListener")
+        pw.println("$prefix\tcontext=$context")
+    }
+
+    /**
+     * Wrapper for the IDesktopTaskListener stub to prevent lingering references to the launcher
+     * activity via the controller.
+     */
+    private class DesktopTaskListenerImpl(
+        controller: DesktopVisibilityController,
+        private val displayId: Int,
+    ) : Stub() {
+        private val controller = WeakReference(controller)
+
+        override fun onListenerConnected(
+            displayDeskStates: Array<DisplayDeskState>,
+            canCreateDesks: Boolean,
+        ) {
+            MAIN_EXECUTOR.execute {
+                controller.get()?.onListenerConnected(displayDeskStates, canCreateDesks)
+            }
+        }
+
+        override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
+            if (displayId != this.displayId) return
+            MAIN_EXECUTOR.execute {
+                controller.get()?.apply {
+                    if (DEBUG) {
+                        Log.d(TAG, "desktop visible tasks count changed=$visibleTasksCount")
+                    }
+                    visibleDesktopTasksCount = visibleTasksCount
+                }
+            }
+        }
+
+        override fun onStashedChanged(displayId: Int, stashed: Boolean) {
+            Log.w(TAG, "DesktopTaskListenerImpl: onStashedChanged is deprecated")
+        }
+
+        override fun onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding: Boolean) {
+            if (!DesktopModeStatus.useRoundedCorners()) return
+            MAIN_EXECUTOR.execute {
+                controller.get()?.apply {
+                    Log.d(
+                        TAG,
+                        "DesktopTaskListenerImpl: doesAnyTaskRequireTaskbarRounding= " +
+                            doesAnyTaskRequireTaskbarRounding,
+                    )
+                    notifyTaskbarDesktopModeListeners(doesAnyTaskRequireTaskbarRounding)
+                }
+            }
+        }
+
+        override fun onEnterDesktopModeTransitionStarted(transitionDuration: Int) {
+            MAIN_EXECUTOR.execute {
+                Log.d(
+                    TAG,
+                    ("DesktopTaskListenerImpl: onEnterDesktopModeTransitionStarted with " +
+                        "duration= " +
+                        transitionDuration),
+                )
+                controller.get()?.isInDesktopMode = true
+                controller.get()?.notifyTaskbarDesktopModeListenersForEntry(transitionDuration)
+            }
+        }
+
+        override fun onExitDesktopModeTransitionStarted(transitionDuration: Int) {
+            MAIN_EXECUTOR.execute {
+                Log.d(
+                    TAG,
+                    ("DesktopTaskListenerImpl: onExitDesktopModeTransitionStarted with " +
+                        "duration= " +
+                        transitionDuration),
+                )
+                controller.get()?.isInDesktopMode = false
+                controller.get()?.notifyTaskbarDesktopModeListenersForExit(transitionDuration)
+            }
+        }
+
+        override fun onCanCreateDesksChanged(canCreateDesks: Boolean) {
+            MAIN_EXECUTOR.execute { controller.get()?.onCanCreateDesksChanged(canCreateDesks) }
+        }
+
+        override fun onDeskAdded(displayId: Int, deskId: Int) {
+            MAIN_EXECUTOR.execute { controller.get()?.onDeskAdded(displayId, deskId) }
+        }
+
+        override fun onDeskRemoved(displayId: Int, deskId: Int) {
+            MAIN_EXECUTOR.execute { controller.get()?.onDeskRemoved(displayId, deskId) }
+        }
+
+        override fun onActiveDeskChanged(displayId: Int, newActiveDesk: Int, oldActiveDesk: Int) {
+            MAIN_EXECUTOR.execute {
+                controller.get()?.onActiveDeskChanged(displayId, newActiveDesk, oldActiveDesk)
+            }
+        }
+    }
+
+    /** A listener for Taskbar in Desktop Mode. */
+    interface TaskbarDesktopModeListener {
+        /**
+         * Callback for when task is resized in desktop mode.
+         *
+         * @param doesAnyTaskRequireTaskbarRounding whether task requires taskbar corner roundness.
+         */
+        fun onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding: Boolean) {}
+
+        /**
+         * Callback for when user is exiting desktop mode.
+         *
+         * @param duration for exit transition
+         */
+        fun onExitDesktopMode(duration: Int) {}
+
+        /**
+         * Callback for when user is entering desktop mode.
+         *
+         * @param duration for enter transition
+         */
+        fun onEnterDesktopMode(duration: Int) {}
+    }
+
+    companion object {
+        @JvmField
+        val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getDesktopVisibilityController)
+
+        private const val TAG = "DesktopVisController"
+        private const val DEBUG = false
+
+        const val INACTIVE_DESK_ID = -1
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java b/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
index a833ccf..09a8670 100644
--- a/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
@@ -18,25 +18,20 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
-import android.view.ContextThemeWrapper;
+import android.os.UserHandle;
 import android.view.LayoutInflater;
 
-import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.util.BaseContext;
 import com.android.launcher3.util.Themes;
-import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.SystemUiProxy;
 
-import java.util.ArrayList;
-import java.util.List;
-
 // TODO(b/218912746): Share more behavior to avoid all apps context depending directly on taskbar.
 /** Base for common behavior between taskbar window contexts. */
-public abstract class BaseTaskbarContext extends ContextThemeWrapper implements ActivityContext,
-        SystemShortcut.BubbleActivityStarter {
+public abstract class BaseTaskbarContext extends BaseContext
+        implements SystemShortcut.BubbleActivityStarter {
 
     protected final LayoutInflater mLayoutInflater;
-    private final List<OnDeviceProfileChangeListener> mDPChangeListeners = new ArrayList<>();
 
     public BaseTaskbarContext(Context windowContext) {
         super(windowContext, Themes.getActivityThemeRes(windowContext));
@@ -49,20 +44,15 @@
     }
 
     @Override
-    public final List<OnDeviceProfileChangeListener> getOnDeviceProfileChangeListeners() {
-        return mDPChangeListeners;
-    }
-
-    @Override
     public void showShortcutBubble(ShortcutInfo info) {
         if (info == null) return;
         SystemUiProxy.INSTANCE.get(this).showShortcutBubble(info);
     }
 
     @Override
-    public void showAppBubble(Intent intent) {
+    public void showAppBubble(Intent intent, UserHandle user) {
         if (intent == null || intent.getPackage() == null) return;
-        SystemUiProxy.INSTANCE.get(this).showAppBubble(intent);
+        SystemUiProxy.INSTANCE.get(this).showAppBubble(intent, user);
     }
 
     /** Callback invoked when a drag is initiated within this context. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
index de42669..1698050 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchController.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.Flags.enableAltTabKqsOnConnectedDisplays;
+
+import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.view.MotionEvent;
@@ -28,10 +31,12 @@
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
 import com.android.launcher3.util.TouchController;
+import com.android.quickstep.RecentsFilterState;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.util.SingleTask;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -153,16 +158,19 @@
                 // Skip the task reload if the list is not changed.
                 if (!mModel.isTaskListValid(mTaskListChangeId) || !taskIdsToExclude.equals(
                         mExcludedTaskIds)) {
+                    final boolean shouldShowDesktopTasks = mControllers.taskbarDesktopModeController
+                            .shouldShowDesktopTasksInTaskbar();
                     mExcludedTaskIds = taskIdsToExclude;
                     mTaskListChangeId = mModel.getTasks((tasks) -> {
                         processLoadedTasks(tasks, taskIdsToExclude);
                         mQuickSwitchViewController.updateQuickSwitchView(
                                 mTasks,
-                                mNumHiddenTasks,
+                                wasOpenedFromTaskbar ? 0 : mNumHiddenTasks,
                                 currentFocusIndexOverride,
                                 mHasDesktopTask,
                                 mWasDesktopTaskFilteredOut);
-                    });
+                    }, shouldShowDesktopTasks ? RecentsFilterState.EMPTY_FILTER
+                            : RecentsFilterState.getDesktopTaskFilter());
                 }
 
                 mQuickSwitchViewController.updateLayoutForSurface(wasOpenedFromTaskbar,
@@ -188,8 +196,8 @@
         mQuickSwitchViewController = new KeyboardQuickSwitchViewController(
                 mControllers, mOverlayContext, keyboardQuickSwitchView, mControllerCallbacks);
 
-        final boolean onDesktop =
-                mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible();
+        final boolean shouldShowDesktopTasks = mControllers.taskbarDesktopModeController
+                .shouldShowDesktopTasksInTaskbar();
 
         if (mModel.isTaskListValid(mTaskListChangeId)
                 && taskIdsToExclude.equals(mExcludedTaskIds)) {
@@ -197,11 +205,11 @@
             // running. If not, focus that first task.
             mQuickSwitchViewController.openQuickSwitchView(
                     mTasks,
-                    mNumHiddenTasks,
+                    wasOpenedFromTaskbar ? 0 : mNumHiddenTasks,
                     /* updateTasks= */ false,
                     currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
                             ? 0 : currentFocusedIndex,
-                    onDesktop,
+                    shouldShowDesktopTasks,
                     mHasDesktopTask,
                     mWasDesktopTaskFilteredOut,
                     wasOpenedFromTaskbar);
@@ -215,25 +223,27 @@
             // the correct index.
             mQuickSwitchViewController.openQuickSwitchView(
                     mTasks,
-                    mNumHiddenTasks,
+                    wasOpenedFromTaskbar ? 0 : mNumHiddenTasks,
                     /* updateTasks= */ true,
                     currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
                             ? 0 : currentFocusedIndex,
-                    onDesktop,
+                    shouldShowDesktopTasks,
                     mHasDesktopTask,
                     mWasDesktopTaskFilteredOut,
                     wasOpenedFromTaskbar);
-        });
+        }, shouldShowDesktopTasks ? RecentsFilterState.EMPTY_FILTER
+                : RecentsFilterState.getDesktopTaskFilter());
     }
 
     private boolean shouldExcludeTask(GroupTask task, Set<Integer> taskIdsToExclude) {
-        return Flags.taskbarOverflow() && taskIdsToExclude.contains(task.task1.key.id);
+        return Flags.taskbarOverflow() && task.getTasks().stream().anyMatch(
+                t -> taskIdsToExclude.contains(t.key.id));
     }
 
     private void processLoadedTasks(List<GroupTask> tasks, Set<Integer> taskIdsToExclude) {
         mHasDesktopTask = false;
         mWasDesktopTaskFilteredOut = false;
-        if (mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible()) {
+        if (mControllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()) {
             processLoadedTasksOnDesktop(tasks, taskIdsToExclude);
         } else {
             processLoadedTasksOutsideDesktop(tasks, taskIdsToExclude);
@@ -265,12 +275,27 @@
     }
 
     private void processLoadedTasksOnDesktop(List<GroupTask> tasks, Set<Integer> taskIdsToExclude) {
-        // Find the single desktop task that contains a grouping of desktop tasks
-        DesktopTask desktopTask = findDesktopTask(tasks);
+        // Find all desktop tasks.
+        List<DesktopTask> desktopTasks = tasks.stream()
+                .filter(t -> t instanceof DesktopTask)
+                .map(t -> (DesktopTask) t)
+                .toList();
 
-        if (desktopTask != null) {
-            mTasks = desktopTask.tasks.stream()
-                    .map(GroupTask::new)
+        // Apps on the connected displays seem to be in different Desktop tasks even with the
+        // multiple desktops flag disabled. So, until multiple desktops is implemented the following
+        // should help with team-fooding Alt+tab on connected displays. Post multiple desktop,
+        // further changes maybe required to support launching selected desktops.
+        if (enableAltTabKqsOnConnectedDisplays()) {
+            mTasks = desktopTasks.stream()
+                    .flatMap(t -> t.getTasks().stream())
+                    .map(SingleTask::new)
+                    .filter(task -> !shouldExcludeTask(task, taskIdsToExclude))
+                    .collect(Collectors.toList());
+
+            mNumHiddenTasks = Math.max(0, tasks.size() - desktopTasks.size());
+        } else if (!desktopTasks.isEmpty()) {
+            mTasks = desktopTasks.get(0).getTasks().stream()
+                    .map(SingleTask::new)
                     .filter(task -> !shouldExcludeTask(task, taskIdsToExclude))
                     .collect(Collectors.toList());
             // All other tasks, apart from the grouped desktop task, are hidden
@@ -282,14 +307,6 @@
         }
     }
 
-    @Nullable
-    private DesktopTask findDesktopTask(List<GroupTask> tasks) {
-        return (DesktopTask) tasks.stream()
-                .filter(t -> t instanceof DesktopTask)
-                .findFirst()
-                .orElse(null);
-    }
-
     void closeQuickSwitchView() {
         closeQuickSwitchView(true);
     }
@@ -338,6 +355,27 @@
         }
     }
 
+    @VisibleForTesting
+    boolean isShownFromTaskbar() {
+        return isShown() && mQuickSwitchViewController.wasOpenedFromTaskbar();
+    }
+
+    @VisibleForTesting
+    boolean isShown() {
+        return mQuickSwitchViewController != null
+                && !mQuickSwitchViewController.isCloseAnimationRunning();
+    }
+
+    @VisibleForTesting
+    List<Integer> shownTaskIds() {
+        if (!isShown()) {
+            return Collections.emptyList();
+        }
+
+        return mTasks.stream().flatMap(
+                groupTask -> groupTask.getTasks().stream().map(task -> task.key.id)).toList();
+    }
+
     @Override
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "KeyboardQuickSwitchController:");
@@ -349,15 +387,12 @@
         pw.println(prefix + "\tmWasDesktopTaskFilteredOut=" + mWasDesktopTaskFilteredOut);
         pw.println(prefix + "\tmTasks=[");
         for (GroupTask task : mTasks) {
-            Task task1 = task.task1;
-            Task task2 = task.task2;
-            ComponentName cn1 = task1.getTopComponent();
-            ComponentName cn2 = task2 != null ? task2.getTopComponent() : null;
-            pw.println(prefix + "\t\tt1: (id=" + task1.key.id
-                    + "; package=" + (cn1 != null ? cn1.getPackageName() + ")" : "no package)")
-                    + " t2: (id=" + (task2 != null ? task2.key.id : "-1")
-                    + "; package=" + (cn2 != null ? cn2.getPackageName() + ")"
-                    : "no package)"));
+            int count = 0;
+            for (Task t : task.getTasks()) {
+                ComponentName cn = t.getTopComponent();
+                pw.println(prefix + "\t\tt" + (++count) + ": (id=" + t.key.id
+                        + "; package=" + (cn != null ? cn.getPackageName() + ")" : "no package)"));
+            }
         }
         pw.println(prefix + "\t]");
 
@@ -410,11 +445,14 @@
             if (task == null) {
                 return false;
             }
-            int runningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().taskId;
-            Task task2 = task.task2;
+            ActivityManager.RunningTaskInfo runningTaskInfo =
+                    ActivityManagerWrapper.getInstance().getRunningTask();
+            if (runningTaskInfo == null) {
+                return false;
+            }
 
-            return runningTaskId == task.task1.key.id
-                    || (task2 != null && runningTaskId == task2.key.id);
+            int runningTaskId = runningTaskInfo.taskId;
+            return task.containsTask(runningTaskId);
         }
 
         boolean isFirstTaskRunning() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
index ce96556..15be03a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchTaskView.java
@@ -40,6 +40,8 @@
 import com.android.quickstep.util.BorderAnimator;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.wm.shell.shared.TypefaceUtils;
+import com.android.wm.shell.shared.TypefaceUtils.FontFamily;
 
 import kotlin.Unit;
 
@@ -64,6 +66,11 @@
     @Nullable private ImageView mIcon2;
     @Nullable private View mContent;
 
+    // Describe the task position in the parent container. Used to add information about the task's
+    // position in a task list to the task view's content description.
+    private int mIndexInParent = -1;
+    private int mTotalTasksInParent = -1;
+
     public KeyboardQuickSwitchTaskView(@NonNull Context context) {
         this(context, null);
     }
@@ -104,9 +111,18 @@
         mIcon2 = findViewById(R.id.icon_2);
         mContent = findViewById(R.id.content);
 
-        Resources resources = mContext.getResources();
-
         Preconditions.assertNotNull(mContent);
+
+        TypefaceUtils.setTypeface(
+                mContent.findViewById(R.id.large_text),
+                FontFamily.GSF_HEADLINE_LARGE_EMPHASIZED
+        );
+        TypefaceUtils.setTypeface(
+                mContent.findViewById(R.id.small_text),
+                FontFamily.GSF_LABEL_LARGE
+        );
+
+        Resources resources = mContext.getResources();
         mBorderAnimator = BorderAnimator.createScalingBorderAnimator(
                 /* borderRadiusPx= */ mBorderRadius != INVALID_BORDER_RADIUS
                         ? mBorderRadius
@@ -144,36 +160,51 @@
         applyThumbnail(mThumbnailView1, task1, thumbnailUpdateFunction);
         applyThumbnail(mThumbnailView2, task2, thumbnailUpdateFunction);
 
+        // Update content description, even in cases task icons, and content descriptions need to be
+        // loaded asynchronously to ensure that the task has non empty description (assuming task
+        // position information was set), as KeyboardQuickSwitch view may request accessibility
+        // focus to be moved to the task when the quick switch UI gets shown. The description will
+        // be updated once the task metadata has been loaded - the delay should be very short, and
+        // the content description when task titles are not available still gives some useful
+        // information to the user (the task's position in the list).
+        updateContentDesctiptionForTasks(task1, task2);
+
         if (iconUpdateFunction == null) {
             applyIcon(mIcon1, task1);
             applyIcon(mIcon2, task2);
-            setContentDescription(task2 == null
-                    ? task1.titleDescription
-                    : getContext().getString(
-                            R.string.quick_switch_split_task,
-                            task1.titleDescription,
-                            task2.titleDescription));
             return;
         }
+
         iconUpdateFunction.updateIconInBackground(task1, t -> {
             applyIcon(mIcon1, task1);
             if (task2 != null) {
                 return;
             }
-            setContentDescription(task1.titleDescription);
+            updateContentDesctiptionForTasks(task1, null);
         });
+
         if (task2 == null) {
             return;
         }
         iconUpdateFunction.updateIconInBackground(task2, t -> {
             applyIcon(mIcon2, task2);
-            setContentDescription(getContext().getString(
-                    R.string.quick_switch_split_task,
-                    task1.titleDescription,
-                    task2.titleDescription));
+            updateContentDesctiptionForTasks(task1, task2);
         });
     }
 
+    /**
+     * Initializes information about the task's position within the parent container context - used
+     * to add position information to the view's content description.
+     * Should be called before associating the view with tasks.
+     *
+     * @param index The view's 0-based index within the parent task container.
+     * @param totalTasks The total number of tasks in the parent task container.
+     */
+    protected void setPositionInformation(int index, int totalTasks) {
+        mIndexInParent = index;
+        mTotalTasksInParent = totalTasks;
+    }
+
     protected void setThumbnailsForSplitTasks(
             @NonNull Task task1,
             @Nullable Task task2,
@@ -188,8 +219,7 @@
 
 
         final boolean isLeftRightSplit = !splitBounds.appsStackedVertically;
-        final float leftOrTopTaskPercent = isLeftRightSplit
-                ? splitBounds.leftTaskPercent : splitBounds.topTaskPercent;
+        final float leftOrTopTaskPercent = splitBounds.getLeftTopTaskPercent();
 
         ConstraintLayout.LayoutParams leftTopParams = (ConstraintLayout.LayoutParams)
                 mThumbnailView1.getLayoutParams();
@@ -273,6 +303,28 @@
                 constantState.newDrawable(getResources(), getContext().getTheme()));
     }
 
+    /**
+     * Updates the task view's content description to reflect tasks represented by the view.
+     */
+    private void updateContentDesctiptionForTasks(@NonNull Task task1, @Nullable Task task2) {
+        String tasksDescription = task1.titleDescription == null || task2 == null
+                ? task1.titleDescription
+                : getContext().getString(
+                        R.string.quick_switch_split_task,
+                        task1.titleDescription,
+                        task2.titleDescription);
+        if (mIndexInParent < 0) {
+            setContentDescription(tasksDescription);
+            return;
+        }
+
+        setContentDescription(
+                getContext().getString(R.string.quick_switch_task_with_position_in_parent,
+                        tasksDescription != null ? tasksDescription : "",
+                        mIndexInParent + 1,
+                        mTotalTasksInParent));
+    }
+
     protected interface ThumbnailUpdateFunction {
 
         void updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback);
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index 306443e..ab147bb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -30,10 +30,12 @@
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
 import android.view.ViewTreeObserver;
 import android.view.animation.Interpolator;
 import android.widget.HorizontalScrollView;
+import android.widget.ImageButton;
 import android.widget.TextView;
 import android.window.OnBackInvokedDispatcher;
 import android.window.WindowOnBackInvokedDispatcher;
@@ -45,13 +47,19 @@
 
 import com.android.app.animation.Interpolators;
 import com.android.internal.jank.Cuj;
+import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
+import com.android.quickstep.util.SplitTask;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
+import com.android.wm.shell.shared.TypefaceUtils;
+import com.android.wm.shell.shared.TypefaceUtils.FontFamily;
 
 import java.util.HashMap;
 import java.util.List;
@@ -99,8 +107,12 @@
     private HorizontalScrollView mScrollView;
     private ConstraintLayout mContent;
 
-    private int mTaskViewWidth;
-    private int mTaskViewHeight;
+    private boolean mSupportsScrollArrows = false;
+    private ImageButton mStartScrollArrow;
+    private ImageButton mEndScrollArrow;
+
+    private int mTaskViewBorderWidth;
+    private int mTaskViewRadius;
     private int mSpacing;
     private int mSmallSpacing;
     private int mOutlineRadius;
@@ -109,11 +121,13 @@
     private int mOverviewTaskIndex = -1;
     private int mDesktopTaskIndex = -1;
 
-    @Nullable private AnimatorSet mOpenAnimation;
+    @Nullable
+    private AnimatorSet mOpenAnimation;
 
     private boolean mIsBackCallbackRegistered = false;
 
-    @Nullable private KeyboardQuickSwitchViewController.ViewCallbacks mViewCallbacks;
+    @Nullable
+    private KeyboardQuickSwitchViewController.ViewCallbacks mViewCallbacks;
 
     public KeyboardQuickSwitchView(@NonNull Context context) {
         this(context, null);
@@ -149,17 +163,37 @@
         mNoRecentItemsPane = findViewById(R.id.no_recent_items_pane);
         mScrollView = findViewById(R.id.scroll_view);
         mContent = findViewById(R.id.content);
+        mStartScrollArrow = findViewById(R.id.scroll_button_start);
+        mEndScrollArrow = findViewById(R.id.scroll_button_end);
+
+        setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
 
         Resources resources = getResources();
-        mTaskViewWidth = resources.getDimensionPixelSize(
-                R.dimen.keyboard_quick_switch_taskview_width);
-        mTaskViewHeight = resources.getDimensionPixelSize(
-                R.dimen.keyboard_quick_switch_taskview_height);
         mSpacing = resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_view_spacing);
         mSmallSpacing = resources.getDimensionPixelSize(
                 R.dimen.keyboard_quick_switch_view_small_spacing);
         mOutlineRadius = resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_view_radius);
+        mTaskViewBorderWidth = resources.getDimensionPixelSize(
+                R.dimen.keyboard_quick_switch_border_width);
+        mTaskViewRadius = resources.getDimensionPixelSize(
+                R.dimen.keyboard_quick_switch_task_view_radius);
+
         mIsRtl = Utilities.isRtl(resources);
+
+        if (Flags.taskbarOverflow()) {
+            initializeScrollArrows();
+
+            if (mIsRtl) {
+                mStartScrollArrow.setContentDescription(
+                        resources.getString(R.string.quick_switch_scroll_arrow_right));
+                mEndScrollArrow.setContentDescription(
+                        resources.getString(R.string.quick_switch_scroll_arrow_left));
+            }
+        }
+
+        TypefaceUtils.setTypeface(
+                mNoRecentItemsPane.findViewById(R.id.no_recent_items_text),
+                FontFamily.GSF_LABEL_LARGE);
     }
 
     private void registerOnBackInvokedCallback() {
@@ -255,17 +289,25 @@
                     layoutInflater,
                     previousTaskView);
 
-            final boolean firstTaskIsLeftTopTask =
-                    groupTask.mSplitBounds == null
-                            || groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id
-                            || groupTask.task2 == null;
+            Task task1;
+            Task task2;
+            if (groupTask instanceof SplitTask splitTask) {
+                task1 = splitTask.getTopLeftTask();
+                task2 = splitTask.getBottomRightTask();
+            } else if (groupTask instanceof SingleTask singleTask) {
+                task1 = singleTask.getTask();
+                task2 = null;
+            } else {
+                continue;
+            }
 
+            currentTaskView.setPositionInformation(i, tasksToDisplay);
             currentTaskView.setThumbnailsForSplitTasks(
-                    firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2,
-                    firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1,
+                    task1,
+                    task2,
                     updateTasks ? mViewCallbacks::updateThumbnailInBackground : null,
                     updateTasks ? mViewCallbacks::updateIconInBackground : null,
-                    groupTask.mSplitBounds);
+                    groupTask instanceof SplitTask splitTask ? splitTask.getSplitBounds() : null);
 
             previousTaskView = currentTaskView;
         }
@@ -300,7 +342,7 @@
                     layoutInflater,
                     previousTaskView);
 
-            desktopButton.<TextView>findViewById(R.id.text).setText(
+            desktopButton.<TextView>findViewById(R.id.small_text).setText(
                     resources.getString(R.string.quick_switch_desktop));
         }
         mDisplayingRecentTasks = !groupTasks.isEmpty() || useDesktopTaskView;
@@ -317,6 +359,78 @@
                 });
     }
 
+    private void initializeScrollArrows() {
+        mSupportsScrollArrows = true;
+
+        mStartScrollArrow.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mIsRtl) {
+                    runScrollCommand(false, () -> {
+                        mScrollView.smoothScrollBy(mScrollView.getWidth(), 0);
+                    });
+                } else {
+                    runScrollCommand(false, () -> {
+                        mScrollView.smoothScrollBy(-mScrollView.getWidth(), 0);
+                    });
+                }
+            }
+        });
+
+        mEndScrollArrow.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (mIsRtl) {
+                    runScrollCommand(false, () -> {
+                        mScrollView.smoothScrollBy(-mScrollView.getWidth(), 0);
+                    });
+                } else {
+                    runScrollCommand(false, () -> {
+                        mScrollView.smoothScrollBy(mScrollView.getWidth(), 0);
+                    });
+                }
+            }
+        });
+
+        // Add listeners to disable arrow buttons when the scroll view cannot be further scrolled in
+        // the associated direction.
+        mScrollView.setOnScrollChangeListener(new OnScrollChangeListener() {
+            @Override
+            public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX,
+                    int oldScrollY) {
+                updateArrowButtonsEnabledState();
+            }
+        });
+
+        // Update scroll view outline to clip its contents with rounded corners.
+        mScrollView.setClipToOutline(true);
+        mScrollView.setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                int spacingWithoutBorder = mSpacing - mTaskViewBorderWidth;
+                outline.setRoundRect(spacingWithoutBorder,
+                        spacingWithoutBorder, view.getWidth() - spacingWithoutBorder,
+                        view.getHeight() - spacingWithoutBorder,
+                        mTaskViewRadius);
+            }
+        });
+    }
+
+    private void updateArrowButtonsEnabledState() {
+        if (!mDisplayingRecentTasks) {
+            return;
+        }
+
+        int scrollX = mScrollView.getScrollX();
+        if (mIsRtl) {
+            mEndScrollArrow.setEnabled(scrollX > 0);
+            mStartScrollArrow.setEnabled(scrollX < mContent.getWidth() - mScrollView.getWidth());
+        } else {
+            mStartScrollArrow.setEnabled(scrollX > 0);
+            mEndScrollArrow.setEnabled(scrollX < mContent.getWidth() - mScrollView.getWidth());
+        }
+    }
+
     int getOverviewTaskIndex() {
         return mOverviewTaskIndex;
     }
@@ -332,6 +446,21 @@
         mViewCallbacks = null;
     }
 
+    private void animateDisplayedContentForClose(View view, AnimatorSet animator) {
+        Animator translationYAnimation = ObjectAnimator.ofFloat(
+                view,
+                TRANSLATION_Y,
+                0, -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP));
+        translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
+        translationYAnimation.setInterpolator(CLOSE_TRANSLATION_Y_INTERPOLATOR);
+        animator.play(translationYAnimation);
+
+        Animator contentAlphaAnimation = ObjectAnimator.ofFloat(view, ALPHA, 1f, 0f);
+        contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
+        animator.play(contentAlphaAnimation);
+
+    }
+
     protected Animator getCloseAnimation() {
         AnimatorSet closeAnimation = new AnimatorSet();
 
@@ -346,17 +475,11 @@
         closeAnimation.play(alphaAnimation);
 
         View displayedContent = mDisplayingRecentTasks ? mScrollView : mNoRecentItemsPane;
-        Animator translationYAnimation = ObjectAnimator.ofFloat(
-                displayedContent,
-                TRANSLATION_Y,
-                0, -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP));
-        translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
-        translationYAnimation.setInterpolator(CLOSE_TRANSLATION_Y_INTERPOLATOR);
-        closeAnimation.play(translationYAnimation);
-
-        Animator contentAlphaAnimation = ObjectAnimator.ofFloat(displayedContent, ALPHA, 1f, 0f);
-        contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
-        closeAnimation.play(contentAlphaAnimation);
+        animateDisplayedContentForClose(displayedContent, closeAnimation);
+        if (mSupportsScrollArrows) {
+            animateDisplayedContentForClose(mStartScrollArrow, closeAnimation);
+            animateDisplayedContentForClose(mEndScrollArrow, closeAnimation);
+        }
 
         closeAnimation.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -371,6 +494,31 @@
         return closeAnimation;
     }
 
+    private void animateDisplayedContentForOpen(View view, AnimatorSet animator) {
+        Animator translationXAnimation = ObjectAnimator.ofFloat(
+                view,
+                TRANSLATION_X,
+                -Utilities.dpToPx(CONTENT_START_TRANSLATION_X_DP), 0);
+        translationXAnimation.setDuration(CONTENT_TRANSLATION_X_ANIMATION_DURATION_MS);
+        translationXAnimation.setInterpolator(OPEN_TRANSLATION_X_INTERPOLATOR);
+        animator.play(translationXAnimation);
+
+        Animator translationYAnimation = ObjectAnimator.ofFloat(
+                view,
+                TRANSLATION_Y,
+                -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP), 0);
+        translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
+        translationYAnimation.setInterpolator(OPEN_TRANSLATION_Y_INTERPOLATOR);
+        animator.play(translationYAnimation);
+
+        view.setAlpha(0.0f);
+        Animator contentAlphaAnimation = ObjectAnimator.ofFloat(view, ALPHA, 0f,
+                1f);
+        contentAlphaAnimation.setStartDelay(CONTENT_ALPHA_ANIMATION_START_DELAY_MS);
+        contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
+        animator.play(contentAlphaAnimation);
+    }
+
     protected void animateOpen(int currentFocusIndexOverride) {
         if (mOpenAnimation != null) {
             // Restart animation since currentFocusIndexOverride can change the initial scroll.
@@ -393,28 +541,17 @@
         mOpenAnimation.play(alphaAnimation);
 
         View displayedContent = mDisplayingRecentTasks ? mScrollView : mNoRecentItemsPane;
-        Animator translationXAnimation = ObjectAnimator.ofFloat(
-                displayedContent,
-                TRANSLATION_X,
-                -Utilities.dpToPx(CONTENT_START_TRANSLATION_X_DP), 0);
-        translationXAnimation.setDuration(CONTENT_TRANSLATION_X_ANIMATION_DURATION_MS);
-        translationXAnimation.setInterpolator(OPEN_TRANSLATION_X_INTERPOLATOR);
-        mOpenAnimation.play(translationXAnimation);
+        animateDisplayedContentForOpen(displayedContent, mOpenAnimation);
+        if (mSupportsScrollArrows) {
+            animateDisplayedContentForOpen(mStartScrollArrow, mOpenAnimation);
+            animateDisplayedContentForOpen(mEndScrollArrow, mOpenAnimation);
+        }
 
-        Animator translationYAnimation = ObjectAnimator.ofFloat(
-                displayedContent,
-                TRANSLATION_Y,
-                -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP), 0);
-        translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
-        translationYAnimation.setInterpolator(OPEN_TRANSLATION_Y_INTERPOLATOR);
-        mOpenAnimation.play(translationYAnimation);
-
-        Animator contentAlphaAnimation = ObjectAnimator.ofFloat(displayedContent, ALPHA, 0f, 1f);
-        contentAlphaAnimation.setStartDelay(CONTENT_ALPHA_ANIMATION_START_DELAY_MS);
-        contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
-        mOpenAnimation.play(contentAlphaAnimation);
 
         ViewOutlineProvider outlineProvider = getOutlineProvider();
+        int defaultFocusedTaskIndex = Math.min(
+                getTaskCount() - 1,
+                currentFocusIndexOverride == -1 ? 1 : currentFocusIndexOverride);
         mOpenAnimation.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
@@ -447,9 +584,28 @@
                                         OPEN_OUTLINE_INTERPOLATOR));
                     }
                 });
-                animateFocusMove(-1, Math.min(
-                        getTaskCount() - 1,
-                        currentFocusIndexOverride == -1 ? 1 : currentFocusIndexOverride));
+
+                if (mSupportsScrollArrows) {
+                    mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(
+                            new ViewTreeObserver.OnGlobalLayoutListener() {
+                                @Override
+                                public void onGlobalLayout() {
+                                    if (mScrollView.getWidth() == 0) {
+                                        return;
+                                    }
+
+                                    if (mContent.getWidth() > mScrollView.getWidth()) {
+                                        mStartScrollArrow.setVisibility(VISIBLE);
+                                        mEndScrollArrow.setVisibility(VISIBLE);
+                                        updateArrowButtonsEnabledState();
+                                    }
+                                    mScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(
+                                            this);
+                                }
+                            });
+                }
+
+                animateFocusMove(-1, defaultFocusedTaskIndex);
                 displayedContent.setVisibility(VISIBLE);
                 setVisibility(VISIBLE);
                 requestFocus();
@@ -469,6 +625,11 @@
                 invalidateOutline();
                 mOpenAnimation = null;
                 InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN);
+
+                View focusedTask = getTaskAt(defaultFocusedTaskIndex);
+                if (focusedTask != null) {
+                    focusedTask.requestAccessibilityFocus();
+                }
             }
         });
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index cb811d6..5f7a026 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -38,18 +38,19 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.desktop.DesktopAppLaunchTransition;
-import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
 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.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
+import com.android.wm.shell.shared.desktopmode.DesktopTaskToFrontReason;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -281,18 +282,25 @@
             return -1;
         }
         RemoteTransition remoteTransition = slideInTransition;
-        if (mOnDesktop
-                && mControllers.taskbarActivityContext.canUnminimizeDesktopTask(task.task1.key.id)
-        ) {
+        boolean canUnminimizeDesktopTask = task instanceof SingleTask singleTask
+                && mControllers.taskbarActivityContext.canUnminimizeDesktopTask(
+                        singleTask.getTask().key.id);
+        if (mOnDesktop && canUnminimizeDesktopTask) {
             // This app is being unminimized - use our own transition runner.
             remoteTransition = new RemoteTransition(
-                    new DesktopAppLaunchTransition(context, MAIN_EXECUTOR, UNMINIMIZE),
+                    new DesktopAppLaunchTransition(
+                            context,
+                            UNMINIMIZE,
+                            Cuj.CUJ_DESKTOP_MODE_KEYBOARD_QUICK_SWITCH_APP_LAUNCH,
+                            MAIN_EXECUTOR
+                    ),
                     "DesktopKeyboardQuickSwitchUnminimize");
         }
         mControllers.taskbarActivityContext.handleGroupTaskLaunch(
                 task,
                 remoteTransition,
                 mOnDesktop,
+                DesktopTaskToFrontReason.ALT_TAB,
                 onStartCallback,
                 onFinishCallback);
         return -1;
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 0d09b48..62f546b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -41,6 +41,7 @@
 import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.taskbar.bubbles.BubbleBarController;
+import com.android.launcher3.taskbar.bubbles.BubbleControllers;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiPropertyFactory;
@@ -48,7 +49,7 @@
 import com.android.quickstep.HomeVisibilityState;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.SystemUiProxy;
-import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SplitTask;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
@@ -86,7 +87,7 @@
     private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
             dp -> {
                 onStashedInAppChanged(dp);
-                adjustHotseatForBubbleBar();
+                postAdjustHotseatForBubbleBar();
                 if (mControllers != null && mControllers.taskbarViewController != null) {
                     mControllers.taskbarViewController.onRotationChanged(dp);
                 }
@@ -134,11 +135,11 @@
     @Override
     protected void onDestroy() {
         onLauncherVisibilityChanged(false /* isVisible */, true /* fromInitOrDestroy */);
+        mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
         super.onDestroy();
         mTaskbarLauncherStateController.onDestroy();
 
         mLauncher.setTaskbarUIController(null);
-        mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
         mHomeState.removeListener(mVisibilityChangeListener);
     }
 
@@ -216,8 +217,10 @@
 
     private int getTaskbarAnimationDuration(boolean isVisible) {
         // fast animation duration since we will not be playing workspace reveal animation.
-        boolean shouldOverrideToFastAnimation =
-                !isHotseatIconOnTopWhenAligned() || mLauncher.getPredictiveBackToHomeInProgress();
+        boolean shouldOverrideToFastAnimation = !isHotseatIconOnTopWhenAligned();
+        if (!Flags.predictiveBackToHomePolish()) {
+            shouldOverrideToFastAnimation |= mLauncher.getPredictiveBackToHomeInProgress();
+        }
         boolean isPinnedTaskbar = DisplayController.isPinnedTaskbar(mLauncher);
         if (isVisible || isPinnedTaskbar) {
             return getTaskbarToHomeDuration(shouldOverrideToFastAnimation, isPinnedTaskbar);
@@ -242,7 +245,8 @@
         }
 
         if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
-                && mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible()) {
+                && mControllers.taskbarDesktopModeController
+                    .isInDesktopModeAndNotInOverview(mLauncher.getDisplayId())) {
             // TODO: b/333533253 - Remove after flag rollout
             isVisible = false;
         }
@@ -272,13 +276,19 @@
         }
     }
 
-    private void adjustHotseatForBubbleBar() {
+    private void postAdjustHotseatForBubbleBar() {
         Hotseat hotseat = mLauncher.getHotseat();
-        if (mControllers.bubbleControllers.isEmpty() || hotseat == null) return;
-        boolean hiddenForBubbles =
-                mControllers.bubbleControllers.get().bubbleBarViewController.isHiddenForNoBubbles();
-        if (hiddenForBubbles) return;
-        hotseat.post(() -> adjustHotseatForBubbleBar(/* isBubbleBarVisible= */ true));
+        if (hotseat == null || !isBubbleBarVisible()) return;
+        hotseat.post(() -> {
+            if (mControllers == null) return;
+            adjustHotseatForBubbleBar(isBubbleBarVisible());
+        });
+    }
+
+    private boolean isBubbleBarVisible() {
+        BubbleControllers bubbleControllers = mControllers.bubbleControllers.orElse(null);
+        return bubbleControllers != null
+                && bubbleControllers.bubbleBarViewController.isBubbleBarVisible();
     }
 
     /**
@@ -460,10 +470,15 @@
     }
 
     @Override
-    protected boolean canToggleHomeAllApps() {
-        return mLauncher.isResumed()
+    protected void toggleAllApps(boolean focusSearch) {
+        boolean canToggleHomeAllApps = mLauncher.isResumed()
                 && !mTaskbarLauncherStateController.isInOverviewUi()
                 && !mLauncher.areDesktopTasksVisible();
+        if (canToggleHomeAllApps) {
+            mLauncher.toggleAllApps(focusSearch);
+            return;
+        }
+        super.toggleAllApps(focusSearch);
     }
 
     @Override
@@ -473,8 +488,8 @@
 
     @Override
     public void launchSplitTasks(
-            @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
-        mLauncher.launchSplitTasks(groupTask, remoteTransition);
+            @NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) {
+        mLauncher.launchSplitTasks(splitTask, remoteTransition);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt b/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
index 032eb51..bfd93dd 100644
--- a/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/ManageWindowsTaskbarShortcut.kt
@@ -27,7 +27,6 @@
 import com.android.launcher3.popup.SystemShortcut
 import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN
 import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext
-import com.android.launcher3.util.Themes
 import com.android.launcher3.util.TouchController
 import com.android.launcher3.views.ActivityContext
 import com.android.quickstep.RecentsModel
@@ -35,9 +34,8 @@
 import com.android.quickstep.util.DesktopTask
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.wm.shell.shared.desktopmode.DesktopTaskToFrontReason
 import com.android.wm.shell.shared.multiinstance.ManageWindowsViewContainer
-import java.util.Collections
-import java.util.function.Predicate
 
 /**
  * A single menu item shortcut to execute displaying open instances of an app. Default interaction
@@ -72,7 +70,7 @@
             val packageDesktopTasks =
                 (desktopTask?.tasks ?: emptyList()).filter(isTargetPackageTask)
             val nonDesktopPackageTasks =
-                tasks.filter { isTargetPackageTask(it.task1) }.map { it.task1 }
+                tasks.flatMap { it.tasks }.filter { isTargetPackageTask(it) }
 
             // Add tasks from the fetched tasks, deduplicating by task ID
             val packageTasks =
@@ -120,7 +118,12 @@
             ({ taskId: Int? ->
                 taskbarShortcutAllWindowsView.animateClose()
                 if (taskId != null) {
-                    SystemUiProxy.INSTANCE.get(target).showDesktopApp(taskId, null)
+                    SystemUiProxy.INSTANCE.get(target)
+                        .showDesktopApp(
+                            taskId,
+                            /* transition= */ null,
+                            DesktopTaskToFrontReason.TASKBAR_MANAGE_WINDOW,
+                        )
                 }
             })
 
@@ -148,11 +151,19 @@
                         FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN,
                         false,
                     )
+                    controllers.taskbarPopupController.cleanUpMultiInstanceMenuReference()
                 }
             }
         )
     }
 
+    /** Closes the multi-instance menu if it has been initialized. */
+    fun closeMultiInstanceMenu() {
+        if (::taskbarShortcutAllWindowsView.isInitialized) {
+            taskbarShortcutAllWindowsView.animateClose()
+        }
+    }
+
     /**
      * A view container for displaying the window of open instances of an app
      *
@@ -238,6 +249,7 @@
             )
             taskbarOverlayContext.dragLayer?.removeView(menuView.rootView)
             taskbarOverlayContext.dragLayer.removeTouchController(this)
+            controllers.taskbarPopupController.cleanUpMultiInstanceMenuReference()
         }
 
         /** TouchController implementations for closing the carousel when touched outside */
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index cb4e5e2..ee5b8d1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -40,9 +40,10 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISMISS_IME;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
@@ -51,7 +52,6 @@
 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.window.flags.Flags.predictiveBackThreeButtonNav;
-import static com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar;
 
 import android.animation.Animator;
 import android.animation.ArgbEvaluator;
@@ -74,6 +74,7 @@
 import android.graphics.drawable.RotateDrawable;
 import android.inputmethodservice.InputMethodService;
 import android.os.Handler;
+import android.os.SystemProperties;
 import android.util.Property;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
@@ -130,9 +131,18 @@
 
     private final Rect mTempRect = new Rect();
 
-    private static final int FLAG_SWITCHER_SHOWING = 1 << 0;
+    /** Whether the IME Switcher button is visible. */
+    private static final int FLAG_IME_SWITCHER_BUTTON_VISIBLE = 1 << 0;
+    /** Whether the IME is visible. */
     private static final int FLAG_IME_VISIBLE = 1 << 1;
-    private static final int FLAG_ROTATION_BUTTON_VISIBLE = 1 << 2;
+    /**
+     * The back button is visually adjusted to indicate that it will dismiss the IME when pressed.
+     * This only takes effect while the IME is visible. By default, it is set while the IME is
+     * visible, but may be overridden by the
+     * {@link android.inputmethodservice.InputMethodService.BackDispositionMode backDispositionMode}
+     * set by the IME.
+     */
+    private static final int FLAG_BACK_DISMISS_IME = 1 << 2;
     private static final int FLAG_A11Y_VISIBLE = 1 << 3;
     private static final int FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE = 1 << 4;
     private static final int FLAG_KEYGUARD_VISIBLE = 1 << 5;
@@ -182,6 +192,7 @@
     private final int mDarkIconColorOnWorkspace;
     /** Color to use for navbar buttons, if they are on on a Taskbar surface background. */
     private final int mOnBackgroundIconColor;
+    private final boolean mIsExpressiveThemeEnabled;
 
     private @Nullable Animator mNavBarLocationAnimator;
     private @Nullable BubbleBarLocation mBubbleBarTargetLocation;
@@ -263,6 +274,9 @@
         if (mContext.isPhoneMode()) {
             mTaskbarTransitions = new TaskbarTransitions(mContext, mNavButtonsView);
         }
+        String SUWTheme = SystemProperties.get("setupwizard.theme", "");
+        mIsExpressiveThemeEnabled = SUWTheme.equals("glif_expressive")
+                || SUWTheme.equals("glif_expressive_light");
     }
 
     /**
@@ -274,13 +288,14 @@
     }
 
     protected void setupController() {
-        boolean isThreeButtonNav = mContext.isThreeButtonNav();
+        final boolean isThreeButtonNav = mContext.isThreeButtonNav();
+        final boolean isPhoneMode = mContext.isPhoneMode();
         DeviceProfile deviceProfile = mContext.getDeviceProfile();
         Resources resources = mContext.getResources();
 
         int setupSize = mControllers.taskbarActivityContext.getSetupWindowSize();
-        Point p = DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources,
-                mContext.isPhoneMode(), mContext.isGestureNav());
+        Point p = DimensionUtils.getTaskbarPhoneDimensions(deviceProfile, resources, isPhoneMode,
+                mContext.isGestureNav());
         ViewGroup.LayoutParams navButtonsViewLayoutParams = mNavButtonsView.getLayoutParams();
         navButtonsViewLayoutParams.width = p.x;
         if (!mContext.isUserSetupComplete()) {
@@ -301,9 +316,10 @@
             mImeSwitcherButton = addButton(switcherResId, BUTTON_IME_SWITCH,
                     isThreeButtonNav ? mStartContextualContainer : mEndContextualContainer,
                     mControllers.navButtonController, R.id.ime_switcher);
+            // A11y and IME Switcher buttons overlap on phone mode, show only a11y if both visible.
             mPropertyHolders.add(new StatePropertyHolder(mImeSwitcherButton,
-                    flags -> ((flags & FLAG_SWITCHER_SHOWING) != 0)
-                            && ((flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0)));
+                    flags -> (flags & FLAG_IME_SWITCHER_BUTTON_VISIBLE) != 0
+                            && !(isPhoneMode && (flags & FLAG_A11Y_VISIBLE) != 0)));
         }
 
         mPropertyHolders.add(new StatePropertyHolder(
@@ -317,7 +333,7 @@
                         .get(ALPHA_INDEX_SMALL_SCREEN),
                 flags -> (flags & FLAG_SMALL_SCREEN) == 0));
 
-        if (!mContext.isPhoneMode()) {
+        if (!isPhoneMode) {
             mPropertyHolders.add(new StatePropertyHolder(mControllers.taskbarDragLayerController
                     .getKeyguardBgTaskbar(), flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0));
         }
@@ -325,17 +341,17 @@
         // Start at 1 because relevant flags are unset at init.
         mOnBackgroundNavButtonColorOverrideMultiplier.value = 1;
 
-        // Force nav buttons (specifically back button) to be visible during setup wizard.
-        boolean isInSetup = !mContext.isUserSetupComplete();
+        // Potentially force the back button to be visible during setup wizard.
+        boolean shouldShowInSetup = !mContext.isUserSetupComplete() && !mIsExpressiveThemeEnabled;
         boolean isInKidsMode = mContext.isNavBarKidsModeActive();
-        boolean alwaysShowButtons = isThreeButtonNav || isInSetup;
+        boolean alwaysShowButtons = isThreeButtonNav || shouldShowInSetup;
 
         // Make sure to remove nav bar buttons translation when any of the following occur:
         // - Notification shade is expanded
-        // - IME is showing (add separate translation for IME)
+        // - IME is visible (add separate translation for IME)
         // - VoiceInteractionWindow (assistant) is showing
         // - Keyboard shortcuts helper is showing
-        if (!mContext.isPhoneMode()) {
+        if (!isPhoneMode) {
             int flagsToRemoveTranslation = FLAG_NOTIFICATION_SHADE_EXPANDED | FLAG_IME_VISIBLE
                     | FLAG_VOICE_INTERACTION_WINDOW_SHOWING | FLAG_KEYBOARD_SHORTCUT_HELPER_SHOWING;
             mPropertyHolders.add(new StatePropertyHolder(mNavButtonInAppDisplayProgressForSysui,
@@ -363,9 +379,9 @@
             initButtons(mNavButtonContainer, mEndContextualContainer,
                     mControllers.navButtonController);
             updateButtonLayoutSpacing();
-            updateStateForFlag(FLAG_SMALL_SCREEN, mContext.isPhoneMode());
+            updateStateForFlag(FLAG_SMALL_SCREEN, isPhoneMode);
 
-            if (!mContext.isPhoneMode()) {
+            if (!isPhoneMode) {
                 mPropertyHolders.add(new StatePropertyHolder(
                         mControllers.taskbarDragLayerController.getNavbarBackgroundAlpha(),
                         flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0));
@@ -392,7 +408,7 @@
                 R.bool.floating_rotation_button_position_left);
         mControllers.rotationButtonController.setRotationButton(mFloatingRotationButton,
                 mRotationButtonListener);
-        if (mContext.isPhoneMode()) {
+        if (isPhoneMode) {
             mTaskbarTransitions.init();
         }
 
@@ -448,7 +464,7 @@
                     flags -> (flags & FLAG_IME_VISIBLE) == 0));
         }
         mPropertyHolders.add(new StatePropertyHolder(mBackButton,
-                flags -> (flags & FLAG_IME_VISIBLE) != 0,
+                flags -> (flags & FLAG_BACK_DISMISS_IME) != 0,
                 ROTATION_DRAWABLE_PERCENT, 1f, 0f));
         // Translate back button to be at end/start of other buttons for keyguard (only after SUW
         // since it is laid to align with SUW actions while in that state)
@@ -497,8 +513,7 @@
                 endContainer, navButtonController, R.id.accessibility_button,
                 R.layout.taskbar_contextual_button);
         mPropertyHolders.add(new StatePropertyHolder(mA11yButton,
-                flags -> (flags & FLAG_A11Y_VISIBLE) != 0
-                        && (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));
+                flags -> (flags & FLAG_A11Y_VISIBLE) != 0));
 
         mSpace = new Space(mNavButtonsView.getContext());
         mSpace.setOnClickListener(view -> navButtonController.onButtonClick(BUTTON_SPACE, view));
@@ -508,8 +523,10 @@
 
     private void parseSystemUiFlags(@SystemUiStateFlags long sysUiStateFlags) {
         mSysuiStateFlags = sysUiStateFlags;
-        boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
-        boolean isImeSwitcherShowing = (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0;
+        boolean isImeSwitcherButtonVisible =
+                (sysUiStateFlags & SYSUI_STATE_IME_SWITCHER_BUTTON_VISIBLE) != 0;
+        boolean isImeVisible = (sysUiStateFlags & SYSUI_STATE_IME_VISIBLE) != 0;
+        boolean isBackDismissIme = (sysUiStateFlags & SYSUI_STATE_BACK_DISMISS_IME) != 0;
         boolean a11yVisible = (sysUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
         boolean isHomeDisabled = (sysUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
         boolean isRecentsDisabled = (sysUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
@@ -523,9 +540,9 @@
         boolean isKeyboardShortcutHelperShowing =
                 (sysUiStateFlags & SYSUI_STATE_SHORTCUT_HELPER_SHOWING) != 0;
 
-        // TODO(b/202218289) we're getting IME as not visible on lockscreen from system
+        updateStateForFlag(FLAG_IME_SWITCHER_BUTTON_VISIBLE, isImeSwitcherButtonVisible);
         updateStateForFlag(FLAG_IME_VISIBLE, isImeVisible);
-        updateStateForFlag(FLAG_SWITCHER_SHOWING, isImeSwitcherShowing);
+        updateStateForFlag(FLAG_BACK_DISMISS_IME, isBackDismissIme);
         updateStateForFlag(FLAG_A11Y_VISIBLE, a11yVisible);
         updateStateForFlag(FLAG_DISABLE_HOME, isHomeDisabled);
         updateStateForFlag(FLAG_DISABLE_RECENTS, isRecentsDisabled);
@@ -866,13 +883,19 @@
             TaskbarNavButtonController navButtonController) {
         final RectF rect = new RectF();
         buttonView.setOnTouchListener((v, event) -> {
-            if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            int motionEventAction = event.getAction();
+            if (motionEventAction == MotionEvent.ACTION_DOWN) {
                 rect.set(0, 0, v.getWidth(), v.getHeight());
             }
-            boolean isCancelled = event.getAction() == MotionEvent.ACTION_CANCEL
-                    || !rect.contains(event.getX(), event.getY());
-            if (event.getAction() == MotionEvent.ACTION_MOVE && !isCancelled) return false;
-            int motionEventAction = event.getAction();
+            boolean isCancelled = motionEventAction == MotionEvent.ACTION_CANCEL
+                    || (!rect.contains(event.getX(), event.getY())
+                    && (motionEventAction == MotionEvent.ACTION_MOVE
+                    || motionEventAction == MotionEvent.ACTION_UP));
+            if (motionEventAction != MotionEvent.ACTION_DOWN
+                    && motionEventAction != MotionEvent.ACTION_UP && !isCancelled) {
+                // return early. we don't care about any other cases than DOWN, UP and CANCEL
+                return false;
+            }
             int keyEventAction = motionEventAction == MotionEvent.ACTION_DOWN
                     ? KeyEvent.ACTION_DOWN : ACTION_UP;
             navButtonController.sendBackKeyEvent(keyEventAction, isCancelled);
@@ -912,6 +935,10 @@
     }
 
     private void handleSetupUi() {
+        // Setup wizard handles the UI when the expressive theme is enabled.
+        if (mIsExpressiveThemeEnabled) {
+            return;
+        }
         // Since setup wizard only has back button enabled, it looks strange to be
         // end-aligned, so start-align instead.
         FrameLayout.LayoutParams navButtonsLayoutParams = (FrameLayout.LayoutParams)
@@ -1221,9 +1248,10 @@
 
     private static String getStateString(int flags) {
         StringJoiner str = new StringJoiner("|");
-        appendFlag(str, flags, FLAG_SWITCHER_SHOWING, "FLAG_SWITCHER_SHOWING");
+        appendFlag(str, flags, FLAG_IME_SWITCHER_BUTTON_VISIBLE,
+                "FLAG_IME_SWITCHER_BUTTON_VISIBLE");
         appendFlag(str, flags, FLAG_IME_VISIBLE, "FLAG_IME_VISIBLE");
-        appendFlag(str, flags, FLAG_ROTATION_BUTTON_VISIBLE, "FLAG_ROTATION_BUTTON_VISIBLE");
+        appendFlag(str, flags, FLAG_BACK_DISMISS_IME, "FLAG_BACK_DISMISS_IME");
         appendFlag(str, flags, FLAG_A11Y_VISIBLE, "FLAG_A11Y_VISIBLE");
         appendFlag(str, flags, FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE,
                 "FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE");
@@ -1342,8 +1370,7 @@
                 || mNavButtonsView.getWidth() == 0) {
             return;
         }
-        if (enableBubbleBarInPersistentTaskBar()
-                && mControllers.bubbleControllers.isPresent()) {
+        if (mControllers.bubbleControllers.isPresent()) {
             if (mBubbleBarTargetLocation == null) {
                 // only set bubble bar location if it was not set before
                 mBubbleBarTargetLocation = mControllers.bubbleControllers.get()
diff --git a/quickstep/src/com/android/launcher3/taskbar/PinToTaskbarShortcut.kt b/quickstep/src/com/android/launcher3/taskbar/PinToTaskbarShortcut.kt
new file mode 100644
index 0000000..da6932f
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/PinToTaskbarShortcut.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar
+
+import android.content.Context
+import android.util.SparseArray
+import android.view.View
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
+import com.android.launcher3.R
+import com.android.launcher3.model.BgDataModel
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.popup.SystemShortcut
+import com.android.launcher3.views.ActivityContext
+
+/**
+ * A single menu item shortcut to allow users to pin an item to the taskbar and unpin an item from
+ * the taskbar.
+ */
+class PinToTaskbarShortcut<T>(
+    target: T,
+    itemInfo: ItemInfo?,
+    originalView: View,
+    private val mIsPin: Boolean,
+    private val mPinnedInfoList: SparseArray<ItemInfo?>,
+) :
+    SystemShortcut<T>(
+        if (mIsPin) R.drawable.ic_pin else R.drawable.ic_unpin,
+        if (mIsPin) R.string.pin_to_taskbar else R.string.unpin_from_taskbar,
+        target,
+        itemInfo,
+        originalView,
+    ) where T : Context?, T : ActivityContext? {
+
+    override fun onClick(v: View?) {
+        dismissTaskMenuView()
+        // Create a placeholder callbacks for the writer to notify other launcher model callbacks
+        // after update.
+        val callbacks: BgDataModel.Callbacks = object : BgDataModel.Callbacks {}
+
+        val writer =
+            LauncherAppState.getInstance(mOriginalView.context)
+                .model
+                .getWriter(true, mTarget!!.cellPosMapper, callbacks)
+
+        if (!mIsPin) {
+            writer.deleteItemFromDatabase(mItemInfo, "item unpinned through long-press menu")
+            return
+        }
+
+        val newInfo =
+            if (mItemInfo is com.android.launcher3.model.data.AppInfo) {
+                mItemInfo.makeWorkspaceItem(mOriginalView.context)
+            } else if (mItemInfo is WorkspaceItemInfo) {
+                mItemInfo.clone()
+            } else {
+                return
+            }
+
+        val dp: DeviceProfile = mTarget.deviceProfile
+        var targetIdx = -1
+
+        for (i in 0 until dp.numShownHotseatIcons) {
+            if (mPinnedInfoList[i] == null) {
+                targetIdx = i
+                break
+            }
+        }
+
+        val cellX = if (dp.isVerticalBarLayout()) 0 else targetIdx
+        val cellY = if (dp.isVerticalBarLayout()) (dp.numShownHotseatIcons - (targetIdx + 1)) else 0
+
+        writer.addItemToDatabase(newInfo, CONTAINER_HOTSEAT, mItemInfo.screenId, cellX, cellY)
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index b6b090c..2e5bebc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -119,7 +119,8 @@
         mControllers = controllers;
         DeviceProfile deviceProfile = mActivity.getDeviceProfile();
         Resources resources = mActivity.getResources();
-        if (mActivity.isPhoneGestureNavMode() || mActivity.isTinyTaskbar()) {
+        if (mActivity.isPhoneGestureNavMode() || mActivity.isTinyTaskbar()
+                || mActivity.isBubbleBarOnPhone()) {
             mTaskbarSize = resources.getDimensionPixelSize(R.dimen.taskbar_phone_size);
             mStashedHandleWidth =
                     resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 8e2246b..6afbebf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -18,17 +18,22 @@
 import static android.os.Trace.TRACE_TAG_APP;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
 
+import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
+
+import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
 import static com.android.launcher3.AbstractFloatingView.TYPE_ON_BOARD_POPUP;
 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
 import static com.android.launcher3.AbstractFloatingView.TYPE_TASKBAR_OVERLAY_PROXY;
 import static com.android.launcher3.Flags.enableCursorHoverStates;
+import static com.android.launcher3.Flags.removeExcludeFromScreenMagnificationFlagUsage;
 import static com.android.launcher3.Utilities.calculateTextHeight;
 import static com.android.launcher3.Utilities.isRunningInTestHarness;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
@@ -43,6 +48,8 @@
 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.wm.shell.Flags.enableBubbleBar;
+import static com.android.wm.shell.Flags.enableBubbleBarOnPhones;
 import static com.android.wm.shell.Flags.enableTinyTaskbar;
 
 import static java.lang.invoke.MethodHandles.Lookup.PROTECTED;
@@ -59,13 +66,11 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
-import android.os.Bundle;
 import android.os.IRemoteCallback;
 import android.os.Process;
 import android.os.Trace;
 import android.provider.Settings;
 import android.util.Log;
-import android.view.Display;
 import android.view.Gravity;
 import android.view.Surface;
 import android.view.View;
@@ -73,7 +78,9 @@
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.Toast;
+import android.window.DesktopExperienceFlags;
 import android.window.DesktopModeFlags;
+import android.window.DesktopModeFlags.DesktopModeFlag;
 import android.window.RemoteTransition;
 
 import androidx.annotation.NonNull;
@@ -82,18 +89,21 @@
 import androidx.core.graphics.Insets;
 import androidx.core.view.WindowInsetsCompat;
 
+import com.android.internal.jank.Cuj;
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.BubbleTextView.RunningAppState;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.R;
+import com.android.launcher3.allapps.ActivityAllAppsContainerView;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.apppairs.AppPairIcon;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.desktop.DesktopAppLaunchTransition;
 import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType;
-import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.logger.LauncherAtom;
@@ -140,19 +150,22 @@
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
+import com.android.launcher3.util.MultiPropertyFactory;
 import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.VibratorWrapper;
-import com.android.launcher3.util.ViewCache;
 import com.android.launcher3.views.ActivityContext;
 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.util.SingleTask;
+import com.android.quickstep.util.SplitTask;
 import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -163,7 +176,7 @@
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
 import com.android.systemui.unfold.updates.RotationChangeProvider;
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
-import com.android.wm.shell.Flags;
+import com.android.wm.shell.shared.desktopmode.DesktopTaskToFrontReason;
 
 import java.io.PrintWriter;
 import java.util.Collections;
@@ -184,16 +197,21 @@
 
     private static final String WINDOW_TITLE = "Taskbar";
 
+    private static final DesktopModeFlag ENABLE_TASKBAR_BEHIND_SHADE = new DesktopModeFlag(
+            Flags::enableTaskbarBehindShade, false);
+
     private final @Nullable Context mNavigationBarPanelContext;
 
     private final TaskbarDragLayer mDragLayer;
     private final TaskbarControllers mControllers;
 
     private final WindowManager mWindowManager;
+    private final boolean mIsPrimaryDisplay;
     private DeviceProfile mDeviceProfile;
     private WindowManager.LayoutParams mWindowLayoutParams;
     private WindowManager.LayoutParams mLastUpdatedLayoutParams;
     private boolean mIsFullscreen;
+    private boolean mIsNotificationShadeExpanded = false;
     // The size we should return to when we call setTaskbarWindowFullscreen(false)
     private int mLastRequestedNonFullscreenSize;
     /**
@@ -205,7 +223,6 @@
 
     private NavigationMode mNavMode;
     private boolean mImeDrawsImeNavBar;
-    private final ViewCache mViewCache = new ViewCache();
 
     private final boolean mIsSafeModeEnabled;
     private final boolean mIsUserSetupComplete;
@@ -215,7 +232,6 @@
     private boolean mIsDestroyed = false;
     // The flag to know if the window is excluded from magnification region computation.
     private boolean mIsExcludeFromMagnificationRegion = false;
-    private boolean mBindingItems = false;
     private boolean mAddedWindow = false;
 
     // The bounds of the taskbar items relative to TaskbarDragLayer
@@ -228,6 +244,7 @@
     private DeviceProfile mPersistentTaskbarDeviceProfile;
 
     private final LauncherPrefs mLauncherPrefs;
+    private final SystemUiProxy mSysUiProxy;
 
     private TaskbarFeatureEvaluator mTaskbarFeatureEvaluator;
 
@@ -235,12 +252,13 @@
 
     public TaskbarActivityContext(Context windowContext,
             @Nullable Context navigationBarPanelContext, DeviceProfile launcherDp,
-            TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
-            unfoldTransitionProgressProvider,
-            @NonNull DesktopVisibilityController desktopVisibilityController) {
+            TaskbarNavButtonController buttonController,
+            ScopedUnfoldTransitionProgressProvider unfoldTransitionProgressProvider,
+            boolean isPrimaryDisplay, SystemUiProxy sysUiProxy) {
         super(windowContext);
-
+        mIsPrimaryDisplay = isPrimaryDisplay;
         mNavigationBarPanelContext = navigationBarPanelContext;
+        mSysUiProxy = sysUiProxy;
         applyDeviceProfile(launcherDp);
         final Resources resources = getResources();
         mTaskbarFeatureEvaluator = TaskbarFeatureEvaluator.getInstance(this);
@@ -264,25 +282,19 @@
         mIsNavBarForceVisible = mIsNavBarKidsMode;
 
         // Get display and corners first, as views might use them in constructor.
-        Display display = windowContext.getDisplay();
         Context c = getApplicationContext();
         mWindowManager = c.getSystemService(WindowManager.class);
 
         // Inflate views.
-        final boolean isTransientTaskbar = DisplayController.isTransientTaskbar(this)
-                && !isPhoneMode();
+        boolean isTransientTaskbar = isTransientTaskbar();
         int taskbarLayout = isTransientTaskbar ? R.layout.transient_taskbar : R.layout.taskbar;
         mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(taskbarLayout, null, false);
         TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view);
         TaskbarScrimView taskbarScrimView = mDragLayer.findViewById(R.id.taskbar_scrim);
         NearestTouchFrame navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
         StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
-        BubbleBarView bubbleBarView = null;
-        FrameLayout bubbleBarContainer = null;
-        if (isTransientTaskbar || Flags.enableBubbleBarInPersistentTaskBar()) {
-            bubbleBarView = mDragLayer.findViewById(R.id.taskbar_bubbles);
-            bubbleBarContainer = mDragLayer.findViewById(R.id.taskbar_bubbles_container);
-        }
+        BubbleBarView bubbleBarView = mDragLayer.findViewById(R.id.taskbar_bubbles);
+        FrameLayout bubbleBarContainer = mDragLayer.findViewById(R.id.taskbar_bubbles_container);
         StashedHandleView bubbleHandleView = mDragLayer.findViewById(R.id.stashed_bubble_handle);
 
         mAccessibilityDelegate = new TaskbarShortcutMenuAccessibilityDelegate(this);
@@ -290,9 +302,10 @@
         // If Bubble bar is present, TaskbarControllers depends on it so build it first.
         Optional<BubbleControllers> bubbleControllersOptional = Optional.empty();
         BubbleBarController.onTaskbarRecreated();
+        final boolean deviceBubbleBarEnabled = enableBubbleBarOnPhones()
+                || (!mDeviceProfile.isPhone && !mDeviceProfile.isVerticalBarLayout());
         if (BubbleBarController.isBubbleBarEnabled()
-                && !mDeviceProfile.isPhone
-                && !mDeviceProfile.isVerticalBarLayout()
+                && deviceBubbleBarEnabled
                 && bubbleBarView != null
         ) {
             Optional<BubbleStashedHandleViewController> bubbleHandleController = Optional.empty();
@@ -314,11 +327,11 @@
                     new BubbleBarViewController(this, bubbleBarView, bubbleBarContainer),
                     bubbleStashController,
                     bubbleHandleController,
-                    new BubbleDragController(this),
+                    new BubbleDragController(this, mDragLayer),
                     new BubbleDismissController(this, mDragLayer),
-                    new BubbleBarPinController(this, mDragLayer,
+                    new BubbleBarPinController(this, bubbleBarContainer,
                             () -> DisplayController.INSTANCE.get(this).getInfo().currentSize),
-                    new BubblePinController(this, mDragLayer,
+                    new BubblePinController(this, bubbleBarContainer,
                             () -> DisplayController.INSTANCE.get(this).getInfo().currentSize),
                     bubbleBarSwipeController,
                     new BubbleCreator(this)
@@ -366,9 +379,18 @@
                 new KeyboardQuickSwitchController(),
                 new TaskbarPinningController(this),
                 bubbleControllersOptional,
-                new TaskbarDesktopModeController(desktopVisibilityController));
+                new TaskbarDesktopModeController(this,
+                        DesktopVisibilityController.INSTANCE.get(this)));
 
         mLauncherPrefs = LauncherPrefs.get(this);
+        onViewCreated();
+    }
+
+    /**
+     * Returns whether this is a primary display.
+     */
+    public boolean isPrimaryDisplay() {
+        return mIsPrimaryDisplay;
     }
 
     /** Updates {@link DeviceProfile} instances for any Taskbar windows. */
@@ -387,6 +409,11 @@
         dispatchDeviceProfileChanged();
     }
 
+    /** Returns whether current taskbar is transient. */
+    public boolean isTransientTaskbar() {
+        return DisplayController.isTransientTaskbar(this) && !isPhoneMode();
+    }
+
     /**
      * Copy the original DeviceProfile, match the number of hotseat icons and qsb width and update
      * the icon size
@@ -420,7 +447,10 @@
                     .setIsTransientTaskbar(true)
                     .build();
         }
-        mNavMode = DisplayController.getNavigationMode(this);
+        mNavMode = (DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()
+                && !mIsPrimaryDisplay) ? NavigationMode.THREE_BUTTONS
+                : DisplayController.getNavigationMode(this);
+
     }
 
     /** Called when the visibility of the bubble bar changed. */
@@ -429,14 +459,25 @@
         mControllers.taskbarViewController.adjustTaskbarForBubbleBar();
     }
 
-    public void init(@NonNull TaskbarSharedState sharedState) {
+    /**
+     * Init of taskbar activity context.
+     * @param duration If duration is greater than 0, it will be used to create an animation
+ *                     for the taskbar create/recreate process.
+     */
+    public void init(@NonNull TaskbarSharedState sharedState, int duration) {
         mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, getResources(), false);
         mLastRequestedNonFullscreenSize = getDefaultTaskbarWindowSize();
         mWindowLayoutParams = createAllWindowParams();
         mLastUpdatedLayoutParams = new WindowManager.LayoutParams();
 
+
+        AnimatorSet recreateAnim = null;
+        if (duration > 0) {
+            recreateAnim = onRecreateAnimation(duration);
+        }
+
         // Initialize controllers after all are constructed.
-        mControllers.init(sharedState);
+        mControllers.init(sharedState, recreateAnim);
         // This may not be necessary and can be reverted once we move towards recreating all
         // controllers without re-creating the window
         mControllers.rotationButtonController.onNavigationModeChanged(mNavMode.resValue);
@@ -464,6 +505,33 @@
         } else {
             notifyUpdateLayoutParams();
         }
+
+
+        if (recreateAnim != null) {
+            recreateAnim.start();
+        }
+    }
+
+    /**
+     * Create AnimatorSet for taskbar create/recreate animation. Further used in init
+     */
+    public AnimatorSet onRecreateAnimation(int duration) {
+        AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.setDuration(duration);
+        return animatorSet;
+    }
+
+    /**
+     * Called when we want destroy current taskbar with animation as part of recreate process.
+     */
+    public AnimatorSet onDestroyAnimation(int duration) {
+        mIsDestroyed = true;
+        AnimatorSet animatorSet = new AnimatorSet();
+        mControllers.taskbarViewController.onDestroyAnimation(animatorSet);
+        mControllers.taskbarDragLayerController.onDestroyAnimation(animatorSet);
+        animatorSet.setInterpolator(LINEAR);
+        animatorSet.setDuration(duration);
+        return animatorSet;
     }
 
     /**
@@ -495,6 +563,10 @@
         return enableTinyTaskbar() && mDeviceProfile.isPhone && mDeviceProfile.isTaskbarPresent;
     }
 
+    public boolean isBubbleBarOnPhone() {
+        return enableBubbleBarOnPhones() && enableBubbleBar() && mDeviceProfile.isPhone;
+    }
+
     /**
      * Returns {@code true} iff bubble bar is enabled (but not necessarily visible /
      * containing bubbles).
@@ -545,16 +617,6 @@
         mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(false);
     }
 
-    /** Toggles Taskbar All Apps overlay. */
-    public void toggleAllApps() {
-        mControllers.taskbarAllAppsController.toggle();
-    }
-
-    /** Toggles Taskbar All Apps overlay with keyboard ready for search. */
-    public void toggleAllAppsSearch() {
-        mControllers.taskbarAllAppsController.toggleSearch();
-    }
-
     @Override
     public DeviceProfile getDeviceProfile() {
         return mDeviceProfile;
@@ -632,7 +694,8 @@
      */
     private WindowManager.LayoutParams createAllWindowParams() {
         final int windowType =
-                ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL;
+                (ENABLE_TASKBAR_NAVBAR_UNIFICATION && mIsPrimaryDisplay) ? TYPE_NAVIGATION_BAR
+                        : TYPE_NAVIGATION_BAR_PANEL;
         WindowManager.LayoutParams windowLayoutParams =
                 createDefaultWindowLayoutParams(windowType, TaskbarActivityContext.WINDOW_TITLE);
 
@@ -742,11 +805,6 @@
     }
 
     @Override
-    public ViewCache getViewCache() {
-        return mViewCache;
-    }
-
-    @Override
     public View.OnClickListener getItemOnClickListener() {
         return this::onTaskbarIconClicked;
     }
@@ -814,32 +872,29 @@
         }
     }
 
-    @Override
-    public DotInfo getDotInfoForItem(ItemInfo info) {
-        return getPopupDataProvider().getDotInfoForItem(info);
-    }
-
     @NonNull
     @Override
     public PopupDataProvider getPopupDataProvider() {
         return mControllers.taskbarPopupController.getPopupDataProvider();
     }
 
+    @NonNull
+    @Override
+    public LauncherBindableItemsContainer getContent() {
+        return mControllers.taskbarViewController.getContent();
+    }
+
+    @Override
+    public ActivityAllAppsContainerView<?> getAppsView() {
+        return mControllers.taskbarAllAppsController.getAppsView();
+    }
+
     @Override
     public View.AccessibilityDelegate getAccessibilityDelegate() {
         return mAccessibilityDelegate;
     }
 
     @Override
-    public boolean isBindingItems() {
-        return mBindingItems;
-    }
-
-    public void setBindingItems(boolean bindingItems) {
-        mBindingItems = bindingItems;
-    }
-
-    @Override
     public void onDragStart() {
         setTaskbarWindowFullscreen(true);
     }
@@ -851,7 +906,7 @@
 
     @Override
     public void onPopupVisibilityChanged(boolean isVisible) {
-        setTaskbarWindowFocusable(isVisible);
+        setTaskbarWindowFocusable(isVisible /* focusable */, false /* imeFocusable */);
     }
 
     @Override
@@ -871,7 +926,7 @@
         options.setSplashScreenStyle(splashScreenStyle);
         options.setPendingIntentBackgroundActivityStartMode(
                 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
-        IRemoteCallback endCallback = completeRunnableListCallback(callbacks);
+        IRemoteCallback endCallback = completeRunnableListCallback(callbacks, this);
         options.setOnAnimationAbortListener(endCallback);
         options.setOnAnimationFinishedListener(endCallback);
 
@@ -883,32 +938,10 @@
         return makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED);
     }
 
-    private ActivityOptionsWrapper getActivityLaunchDesktopOptions(ItemInfo info) {
-        if (!DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS.isTrue()
-                && !DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue()) {
-            return null;
-        }
-        if (!areDesktopTasksVisible()) {
-            return null;
-        }
-        BubbleTextView.RunningAppState appState =
-                mControllers.taskbarRecentAppsController.getDesktopItemState(info);
-        AppLaunchType launchType = null;
-        switch (appState) {
-            case RUNNING:
-                return null;
-            case MINIMIZED:
-                launchType = AppLaunchType.UNMINIMIZE;
-                break;
-            case NOT_RUNNING:
-                launchType = AppLaunchType.LAUNCH;
-                break;
-        }
+    private ActivityOptionsWrapper getActivityLaunchDesktopOptions() {
         ActivityOptions options = ActivityOptions.makeRemoteTransition(
-                new RemoteTransition(
-                        new DesktopAppLaunchTransition(
-                                /* context= */ this, getMainExecutor(), launchType),
-                        "TaskbarDesktopLaunch"));
+                createDesktopAppLaunchRemoteTransition(
+                        AppLaunchType.LAUNCH, Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON));
         return new ActivityOptionsWrapper(options, new RunnableList());
     }
 
@@ -960,6 +993,7 @@
      * Called when this instance of taskbar is no longer needed
      */
     public void onDestroy() {
+        onViewDestroyed();
         mIsDestroyed = true;
         mTaskbarFeatureEvaluator.onDestroy();
         setUIController(TaskbarUIController.DEFAULT);
@@ -1008,6 +1042,8 @@
      * Hides the taskbar icons and background when the notification shade is expanded.
      */
     private void onNotificationShadeExpandChanged(boolean isExpanded, boolean skipAnim) {
+        boolean isExpandedUpdated = isExpanded != mIsNotificationShadeExpanded;
+        mIsNotificationShadeExpanded = isExpanded;
         // Close all floating views within the Taskbar window to make sure nothing is shown over
         // the notification shade.
         if (isExpanded) {
@@ -1021,11 +1057,18 @@
         anim.play(mControllers.taskbarDragLayerController.getNotificationShadeBgTaskbar()
                 .animateToValue(alpha));
 
-        mControllers.bubbleControllers.ifPresent(controllers -> {
-            BubbleBarViewController bubbleBarViewController = controllers.bubbleBarViewController;
-            anim.play(bubbleBarViewController.getBubbleBarAlpha().get(0).animateToValue(alpha));
-        });
-
+        if (isExpandedUpdated) {
+            mControllers.bubbleControllers.ifPresent(controllers -> {
+                BubbleBarViewController bubbleBarViewController =
+                        controllers.bubbleBarViewController;
+                anim.play(bubbleBarViewController.getBubbleBarAlpha().get(0).animateToValue(alpha));
+                MultiPropertyFactory<View>.MultiProperty handleAlpha =
+                        controllers.bubbleStashController.getHandleViewAlpha();
+                if (handleAlpha != null) {
+                    anim.play(handleAlpha.animateToValue(alpha));
+                }
+            });
+        }
         anim.start();
         if (skipAnim) {
             anim.end();
@@ -1223,17 +1266,29 @@
     }
 
     /**
-     * Either adds or removes {@link WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} on the taskbar
-     * window.
+     * Sets whether the taskbar window should be focusable and IME focusable. This won't be IME
+     * focusable unless it is also focusable.
+     *
+     * @param focusable    whether it should be focusable.
+     * @param imeFocusable whether it should be IME focusable.
+     *
+     * @see WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE
+     * @see WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM
      */
-    public void setTaskbarWindowFocusable(boolean focusable) {
+    public void setTaskbarWindowFocusable(boolean focusable, boolean imeFocusable) {
         if (isPhoneMode()) {
             return;
         }
         if (focusable) {
             mWindowLayoutParams.flags &= ~FLAG_NOT_FOCUSABLE;
+            if (imeFocusable) {
+                mWindowLayoutParams.flags &= ~FLAG_ALT_FOCUSABLE_IM;
+            } else {
+                mWindowLayoutParams.flags |= FLAG_ALT_FOCUSABLE_IM;
+            }
         } else {
             mWindowLayoutParams.flags |= FLAG_NOT_FOCUSABLE;
+            mWindowLayoutParams.flags &= ~FLAG_ALT_FOCUSABLE_IM;
         }
         notifyUpdateLayoutParams();
     }
@@ -1254,8 +1309,12 @@
     }
 
     /**
-     * Either adds or removes {@link WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE} on the taskbar
-     * window. If we're now focusable, also move nav buttons to a separate window above IME.
+     * Sets whether the taskbar window should be focusable, as well as IME focusable. If we're now
+     * focusable, also move nav buttons to a separate window above IME.
+     *
+     * @param focusable whether it should be focusable.
+     *
+     * @see WindowManager.LayoutParams#FLAG_NOT_FOCUSABLE
      */
     public void setTaskbarWindowFocusableForIme(boolean focusable) {
         if (focusable) {
@@ -1263,7 +1322,7 @@
         } else {
             mControllers.navbarButtonsViewController.moveNavButtonsBackToTaskbarWindow();
         }
-        setTaskbarWindowFocusable(focusable);
+        setTaskbarWindowFocusable(focusable, true /* imeFocusable */);
     }
 
     /** Adds the given view to WindowManager with the provided LayoutParams (creates new window). */
@@ -1287,7 +1346,7 @@
 
     boolean areDesktopTasksVisible() {
         return mControllers != null
-                && mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible();
+                && mControllers.taskbarDesktopModeController.isInDesktopMode(getDisplayId());
     }
 
     protected void onTaskbarIconClicked(View view) {
@@ -1298,11 +1357,28 @@
 
         mControllers.keyboardQuickSwitchController.closeQuickSwitchView(false);
 
-        if (tag instanceof GroupTask groupTask) {
+        // TODO: b/316004172, b/343289567: Handle `DesktopTask` and `SplitTask`.
+        if (tag instanceof SingleTask singleTask) {
             RemoteTransition remoteTransition =
-                    (areDesktopTasksVisible() && canUnminimizeDesktopTask(groupTask.task1.key.id))
-                            ? createUnminimizeRemoteTransition() : null;
-            handleGroupTaskLaunch(groupTask, remoteTransition, areDesktopTasksVisible());
+                    (areDesktopTasksVisible() && canUnminimizeDesktopTask(
+                            singleTask.getTask().key.id))
+                            ? createDesktopAppLaunchRemoteTransition(AppLaunchType.UNMINIMIZE,
+                            Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
+                            : null;
+            if (areDesktopTasksVisible() && mControllers.uiController.isInOverviewUi()) {
+                RunnableList runnableList = recents.launchRunningDesktopTaskView();
+                // Wrapping it in runnable so we post after DW is ready for the app
+                // launch.
+                if (runnableList != null) {
+                    runnableList.add(() -> UI_HELPER_EXECUTOR.execute(
+                            () -> handleGroupTaskLaunch(singleTask, remoteTransition,
+                                    areDesktopTasksVisible(),
+                                    DesktopTaskToFrontReason.TASKBAR_TAP)));
+                }
+            } else {
+                handleGroupTaskLaunch(singleTask, remoteTransition, areDesktopTasksVisible(),
+                        DesktopTaskToFrontReason.TASKBAR_TAP);
+            }
             mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
         } else if (tag instanceof FolderInfo) {
             // Tapping an expandable folder icon on Taskbar
@@ -1321,26 +1397,30 @@
             }
         } else if (tag instanceof TaskItemInfo info) {
             RemoteTransition remoteTransition = canUnminimizeDesktopTask(info.getTaskId())
-                    ? createUnminimizeRemoteTransition() : null;
+                    ? createDesktopAppLaunchRemoteTransition(
+                            AppLaunchType.UNMINIMIZE, Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
+                    : null;
 
-            if (areDesktopTasksVisible() && recents != null) {
-                TaskView taskView = recents.getTaskViewByTaskId(info.getTaskId());
-                if (taskView == null) return;
-                RunnableList runnableList = taskView.launchWithAnimation();
+
+            if (areDesktopTasksVisible() && mControllers.uiController.isInOverviewUi()) {
+                RunnableList runnableList = recents.launchRunningDesktopTaskView();
                 if (runnableList != null) {
                     runnableList.add(() ->
                             // wrapped it in runnable here since we need the post for DW to be
-                            // ready. if we don't other DW will be gone and only the launched task
-                            // will show.
+                            // ready. if we don't other DW will be gone and only the launched
+                            // task will show.
                             UI_HELPER_EXECUTOR.execute(() ->
                                     SystemUiProxy.INSTANCE.get(this).showDesktopApp(
-                                            info.getTaskId(), remoteTransition)));
+                                            info.getTaskId(), remoteTransition,
+                                            DesktopTaskToFrontReason.TASKBAR_TAP)));
                 }
             } else {
                 UI_HELPER_EXECUTOR.execute(() ->
                         SystemUiProxy.INSTANCE.get(this).showDesktopApp(
-                                info.getTaskId(), remoteTransition));
+                                info.getTaskId(), remoteTransition,
+                                DesktopTaskToFrontReason.TASKBAR_TAP));
             }
+
             mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(
                     /* stash= */ true);
         } else if (tag instanceof WorkspaceItemInfo) {
@@ -1422,6 +1502,7 @@
             Log.e(TAG, "Unknown type clicked: " + tag);
         }
 
+        mControllers.taskbarPopupController.maybeCloseMultiInstanceMenu();
         if (shouldCloseAllOpenViews) {
             AbstractFloatingView.closeAllOpenViews(this);
         }
@@ -1430,8 +1511,9 @@
     public void handleGroupTaskLaunch(
             GroupTask task,
             @Nullable RemoteTransition remoteTransition,
-            boolean onDesktop) {
-        handleGroupTaskLaunch(task, remoteTransition, onDesktop,
+            boolean onDesktop,
+            DesktopTaskToFrontReason toFrontReason) {
+        handleGroupTaskLaunch(task, remoteTransition, onDesktop, toFrontReason,
                 /* onStartCallback= */ null, /* onFinishCallback= */ null);
     }
 
@@ -1450,6 +1532,7 @@
             GroupTask task,
             @Nullable RemoteTransition remoteTransition,
             boolean onDesktop,
+            DesktopTaskToFrontReason toFrontReason,
             @Nullable Runnable onStartCallback,
             @Nullable Runnable onFinishCallback) {
         if (task instanceof DesktopTask) {
@@ -1458,48 +1541,54 @@
                             remoteTransition));
             return;
         }
-        if (onDesktop) {
-            boolean useRemoteTransition = canUnminimizeDesktopTask(task.task1.key.id);
+        if (onDesktop && task instanceof SingleTask singleTask) {
+            boolean useRemoteTransition = canUnminimizeDesktopTask(singleTask.getTask().key.id);
             UI_HELPER_EXECUTOR.execute(() -> {
                 if (onStartCallback != null) {
                     onStartCallback.run();
                 }
-                SystemUiProxy.INSTANCE.get(this).showDesktopApp(
-                        task.task1.key.id, useRemoteTransition ? remoteTransition : null);
+                SystemUiProxy.INSTANCE.get(this).showDesktopApp(singleTask.getTask().key.id,
+                        useRemoteTransition ? remoteTransition : null, toFrontReason);
                 if (onFinishCallback != null) {
                     onFinishCallback.run();
                 }
             });
             return;
         }
-        if (task.task2 == null) {
+        if (task instanceof SingleTask singleTask) {
             UI_HELPER_EXECUTOR.execute(() -> {
                 ActivityOptions activityOptions =
                         makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED).options;
                 activityOptions.setRemoteTransition(remoteTransition);
 
                 ActivityManagerWrapper.getInstance().startActivityFromRecents(
-                        task.task1.key, activityOptions);
+                        singleTask.getTask().key, activityOptions);
             });
             return;
         }
-        mControllers.uiController.launchSplitTasks(task, remoteTransition);
+        assert task instanceof SplitTask;
+        mControllers.uiController.launchSplitTasks((SplitTask) task, remoteTransition);
     }
 
     /** Returns whether the given task is minimized and can be unminimized. */
     public boolean canUnminimizeDesktopTask(int taskId) {
         BubbleTextView.RunningAppState runningAppState =
                 mControllers.taskbarRecentAppsController.getRunningAppState(taskId);
-        return runningAppState == BubbleTextView.RunningAppState.MINIMIZED
-                && (DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS.isTrue()
-                    || DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX.isTrue()
-                    );
+        Log.d(TAG, "Task id=" + taskId + ", Running app state=" + runningAppState);
+        return runningAppState == RunningAppState.MINIMIZED
+                && DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX.isTrue();
     }
 
-    private RemoteTransition createUnminimizeRemoteTransition() {
+    private RemoteTransition createDesktopAppLaunchRemoteTransition(
+            AppLaunchType appLaunchType, @Cuj.CujType int cujType) {
         return new RemoteTransition(
-                new DesktopAppLaunchTransition(this, getMainExecutor(), AppLaunchType.UNMINIMIZE),
-                "TaskbarDesktopUnminimize");
+                new DesktopAppLaunchTransition(
+                        this,
+                        appLaunchType,
+                        cujType,
+                        getMainExecutor()
+                ),
+                "TaskbarDesktopAppLaunch");
     }
 
     /**
@@ -1520,7 +1609,10 @@
      */
     private void launchFromInAppTaskbar(@Nullable RecentsView recents,
             @Nullable View launchingIconView, List<? extends ItemInfo> itemInfos) {
-        if (recents == null) {
+        boolean launchedFromExternalDisplay =
+                DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()
+                        && !mIsPrimaryDisplay;
+        if (recents == null && !launchedFromExternalDisplay) {
             return;
         }
 
@@ -1584,8 +1676,9 @@
                                                 .launchAppPair((AppPairIcon) launchingIconView,
                                                         -1 /*cuj*/)));
                     } else {
-                        if (areDesktopTasksVisible()) {
-                            RunnableList runnableList = recents.launchDesktopTaskView();
+                        if (areDesktopTasksVisible()
+                                && mControllers.uiController.isInOverviewUi()) {
+                            RunnableList runnableList = recents.launchRunningDesktopTaskView();
                             // Wrapping it in runnable so we post after DW is ready for the app
                             // launch.
                             if (runnableList != null) {
@@ -1616,12 +1709,12 @@
                         intent.getComponent(), info.user, intent.getSourceBounds(), null);
                 return;
             }
+            int displayId = getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId();
             // TODO(b/216683257): Use startActivityForResult for search results that require it.
             if (taskInRecents != null) {
                 // Re launch instance from recents
                 ActivityOptionsWrapper opts = getActivityLaunchOptions(null, info);
-                opts.options.setLaunchDisplayId(
-                        getDisplay() == null ? DEFAULT_DISPLAY : getDisplay().getDisplayId());
+                opts.options.setLaunchDisplayId(displayId);
                 if (ActivityManagerWrapper.getInstance()
                         .startActivityFromRecents(taskInRecents.key, opts.options)) {
                     mControllers.uiController.getRecentsView()
@@ -1629,12 +1722,12 @@
                     return;
                 }
             }
-            ActivityOptionsWrapper opts = null;
-            if (areDesktopTasksVisible()) {
-                opts = getActivityLaunchDesktopOptions(info);
+            if (areDesktopTasksVisible()
+                    && DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX.isTrue()) {
+                launchDesktopApp(intent, info, displayId);
+            } else {
+                startActivity(intent, null);
             }
-            Bundle optionsBundle = opts == null ? null : opts.options.toBundle();
-            startActivity(intent, optionsBundle);
         } catch (NullPointerException | ActivityNotFoundException | SecurityException e) {
             Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT)
                     .show();
@@ -1642,6 +1735,31 @@
         }
     }
 
+    private void launchDesktopApp(Intent intent, ItemInfo info, int displayId) {
+        TaskbarRecentAppsController.TaskState taskState =
+                mControllers.taskbarRecentAppsController.getDesktopItemState(info);
+        RunningAppState appState = taskState.getRunningAppState();
+        if (appState == RunningAppState.RUNNING || appState == RunningAppState.MINIMIZED) {
+            // We only need a custom animation (a RemoteTransition) if the task is minimized - if
+            // it's already visible it will just be brought forward.
+            RemoteTransition remoteTransition = (appState == RunningAppState.MINIMIZED)
+                    ? createDesktopAppLaunchRemoteTransition(
+                            AppLaunchType.UNMINIMIZE, Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_ICON)
+                    : null;
+            UI_HELPER_EXECUTOR.execute(() ->
+                    SystemUiProxy.INSTANCE.get(this).showDesktopApp(taskState.getTaskId(),
+                            remoteTransition, DesktopTaskToFrontReason.TASKBAR_TAP));
+            return;
+        }
+        // There is no task associated with this launch - launch a new task through an intent
+        ActivityOptionsWrapper opts = getActivityLaunchDesktopOptions();
+        if (DesktopModeFlags.ENABLE_START_LAUNCH_TRANSITION_FROM_TASKBAR_BUGFIX.isTrue()) {
+            mSysUiProxy.startLaunchIntentTransition(intent, opts.options.toBundle(), displayId);
+        } else {
+            startActivity(intent, opts.options.toBundle());
+        }
+    }
+
     /** Expands a folder icon when it is clicked */
     private void expandFolder(FolderIcon folderIcon) {
         Folder folder = folderIcon.getFolder();
@@ -1667,7 +1785,7 @@
             folder.animateOpen();
             getStatsLogManager().logger().withItemInfo(folder.mInfo).log(LAUNCHER_FOLDER_OPEN);
 
-            folder.iterateOverItems((itemInfo, itemView) -> {
+            folder.mapOverItems((itemInfo, itemView) -> {
                 mControllers.taskbarViewController
                         .setClickAndLongClickListenersForIcon(itemView);
                 // To play haptic when dragging, like other Taskbar items do.
@@ -1848,6 +1966,10 @@
             return;
         }
 
+        if (removeExcludeFromScreenMagnificationFlagUsage()) {
+            return;
+        }
+
         mIsExcludeFromMagnificationRegion = exclude;
         if (exclude) {
             mWindowLayoutParams.privateFlags |=
@@ -1888,6 +2010,10 @@
         return mControllers.taskbarStashController.isInApp();
     }
 
+    public boolean isInOverview() {
+        return mControllers.taskbarStashController.isInOverview();
+    }
+
     public boolean isInStashedLauncherState() {
         return mControllers.taskbarStashController.isInStashedLauncherState();
     }
@@ -1911,8 +2037,6 @@
                 "%s\tmIsUserSetupComplete=%b", prefix, mIsUserSetupComplete));
         pw.println(String.format(
                 "%s\tmWindowLayoutParams.height=%dpx", prefix, mWindowLayoutParams.height));
-        pw.println(String.format(
-                "%s\tmBindInProgress=%b", prefix, mBindingItems));
         mControllers.dumpLogs(prefix + "\t", pw);
         mDeviceProfile.dump(this, prefix, pw);
     }
@@ -1937,15 +2061,12 @@
         mControllers.keyboardQuickSwitchController.closeQuickSwitchView(false);
     }
 
-    boolean canToggleHomeAllApps() {
-        return mControllers.uiController.canToggleHomeAllApps();
-    }
-
     boolean isIconAlignedWithHotseat() {
         return mControllers.uiController.isIconAlignedWithHotseat();
     }
 
-    @VisibleForTesting
+    // TODO(b/395061396): Remove `otherwise` when overview in widow is enabled.
+    @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
     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 ea6d82b..89cc991 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -50,6 +50,8 @@
         }
 
     var isAnimatingPinning = false
+    var isAnimatingPersistentTaskbar = false
+    var isAnimatingTransientTaskbar = false
 
     val paint = Paint()
     private val strokePaint = Paint()
@@ -57,6 +59,7 @@
     var backgroundHeight = context.deviceProfile.taskbarHeight.toFloat()
     var translationYForSwipe = 0f
     var translationYForStash = 0f
+    var translationXForBubbleBar = 0f
 
     private val transientBackgroundBounds = context.transientTaskbarBounds
 
@@ -107,7 +110,7 @@
     fun updateStashedHandleWidth(context: TaskbarActivityContext, res: Resources) {
         stashedHandleWidth =
             res.getDimensionPixelSize(
-                if (context.isPhoneMode || context.isTinyTaskbar) {
+                if (context.isPhoneMode || context.isTinyTaskbar || context.isBubbleBarOnPhone) {
                     R.dimen.taskbar_stashed_small_screen
                 } else {
                     R.dimen.taskbar_stashed_handle_width
@@ -143,7 +146,7 @@
     /** Draws the background with the given paint and height, on the provided canvas. */
     fun draw(canvas: Canvas) {
         if (isInSetup) return
-        val isTransientTaskbar = backgroundProgress == 0f
+        val isTransientTaskbar = DisplayController.isTransientTaskbar(context)
         canvas.save()
         if (!isTransientTaskbar || transientBackgroundBounds.isEmpty || isAnimatingPinning) {
             drawPersistentBackground(canvas)
@@ -157,7 +160,7 @@
     }
 
     private fun drawPersistentBackground(canvas: Canvas) {
-        if (isAnimatingPinning) {
+        if (isAnimatingPinning || isAnimatingPersistentTaskbar) {
             val persistentTaskbarHeight = maxPersistentTaskbarHeight * backgroundProgress
             canvas.translate(0f, canvas.height - persistentTaskbarHeight)
             // Draw the background behind taskbar content.
@@ -180,12 +183,13 @@
     private fun drawTransientBackground(canvas: Canvas) {
         val res = context.resources
         val transientTaskbarHeight = maxTransientTaskbarHeight * (1f - backgroundProgress)
+        val isAnimating = isAnimatingPinning || isAnimatingTransientTaskbar
         val heightProgressWhileAnimating =
-            if (isAnimatingPinning) transientTaskbarHeight else backgroundHeight
+            if (isAnimating) transientTaskbarHeight else backgroundHeight
 
         var progress = heightProgressWhileAnimating / maxTransientTaskbarHeight
         progress = Math.round(progress * 100f) / 100f
-        if (isAnimatingPinning) {
+        if (isAnimating) {
             var scale = transientTaskbarHeight / maxTransientTaskbarHeight
             scale = Math.round(scale * 100f) / 100f
             bottomMargin =
@@ -244,12 +248,12 @@
             setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha)),
         )
         strokePaint.alpha = (paint.alpha * strokeAlpha) / 255
-
+        val currentTranslationX = translationXForBubbleBar * progress
         lastDrawnTransientRect.set(
-            transientBackgroundBounds.left + halfWidthDelta,
+            transientBackgroundBounds.left + halfWidthDelta + currentTranslationX,
             bottom - newBackgroundHeight,
-            transientBackgroundBounds.right - halfWidthDelta,
-            bottom
+            transientBackgroundBounds.right - halfWidthDelta + currentTranslationX,
+            bottom,
         )
         val horizontalInset = fullWidth * widthInsetPercentage
         lastDrawnTransientRect.inset(horizontalInset, 0f)
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 826722d..58606de 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.taskbar;
 
+import android.animation.AnimatorSet;
 import android.content.pm.ActivityInfo.Config;
 
 import androidx.annotation.NonNull;
@@ -149,15 +150,15 @@
      * TaskbarControllers instance, but should be careful to only access things that were created
      * in constructors for now, as some controllers may still be waiting for init().
      */
-    public void init(@NonNull TaskbarSharedState sharedState) {
+    public void init(@NonNull TaskbarSharedState sharedState, AnimatorSet startAnimation) {
         mAreAllControllersInitialized = false;
         mSharedState = sharedState;
 
         taskbarDragController.init(this);
         navbarButtonsViewController.init(this);
         rotationButtonController.init();
-        taskbarDragLayerController.init(this);
-        taskbarViewController.init(this);
+        taskbarDragLayerController.init(this, startAnimation);
+        taskbarViewController.init(this, startAnimation);
         taskbarScrimViewController.init(this);
         taskbarUnfoldAnimationController.init(this);
         taskbarKeyguardController.init(navbarButtonsViewController);
@@ -194,7 +195,11 @@
                 voiceInteractionWindowController
         };
 
-        if (taskbarDesktopModeController.getAreDesktopTasksVisible()) {
+        // TODO(b/401061748): get primary status from
+        //  TaskbarDesktopModeController/DesktopVisibilityController.
+        if (taskbarDesktopModeController.isInDesktopModeAndNotInOverview(
+                taskbarActivityContext.getDisplayId())
+                || !taskbarActivityContext.isPrimaryDisplay()) {
             mCornerRoundness.value = taskbarDesktopModeController.getTaskbarCornerRoundness(
                     mSharedState.showCornerRadiusInDesktopMode);
         } else {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
index 47a35c5..ca8e4ca 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDesktopModeController.kt
@@ -16,19 +16,22 @@
 
 package com.android.launcher3.taskbar
 
+import android.content.Context
 import com.android.launcher3.statehandlers.DesktopVisibilityController
 import com.android.launcher3.statehandlers.DesktopVisibilityController.TaskbarDesktopModeListener
 import com.android.launcher3.taskbar.TaskbarBackgroundRenderer.Companion.MAX_ROUNDNESS
+import com.android.launcher3.util.DisplayController
 
 /** Handles Taskbar in Desktop Windowing mode. */
 class TaskbarDesktopModeController(
-    private val desktopVisibilityController: DesktopVisibilityController
+    private val context: Context,
+    private val desktopVisibilityController: DesktopVisibilityController,
 ) : TaskbarDesktopModeListener {
     private lateinit var taskbarControllers: TaskbarControllers
     private lateinit var taskbarSharedState: TaskbarSharedState
 
-    val areDesktopTasksVisible: Boolean
-        get() = desktopVisibilityController.areDesktopTasksVisible()
+    val isInDesktopMode: Boolean
+        get() = desktopVisibilityController.isInDesktopMode
 
     fun init(controllers: TaskbarControllers, sharedState: TaskbarSharedState) {
         taskbarControllers = controllers
@@ -36,12 +39,25 @@
         desktopVisibilityController.registerTaskbarDesktopModeListener(this)
     }
 
+    fun isInDesktopMode(displayId: Int) = desktopVisibilityController.isInDesktopMode(displayId)
+
+    fun isInDesktopModeAndNotInOverview(displayId: Int) =
+        desktopVisibilityController.isInDesktopModeAndNotInOverview(displayId)
+
     override fun onTaskbarCornerRoundingUpdate(doesAnyTaskRequireTaskbarRounding: Boolean) {
+        if (taskbarControllers.taskbarActivityContext.isDestroyed) return
         taskbarSharedState.showCornerRadiusInDesktopMode = doesAnyTaskRequireTaskbarRounding
         val cornerRadius = getTaskbarCornerRoundness(doesAnyTaskRequireTaskbarRounding)
         taskbarControllers.taskbarCornerRoundness.animateToValue(cornerRadius).start()
     }
 
+    fun shouldShowDesktopTasksInTaskbar(): Boolean {
+        return isInDesktopMode(context.displayId) ||
+            DisplayController.showDesktopTaskbarForFreeformDisplay(context) ||
+            (DisplayController.showLockedTaskbarOnHome(context) &&
+                taskbarControllers.taskbarStashController.isOnHome)
+    }
+
     fun getTaskbarCornerRoundness(doesAnyTaskRequireTaskbarRounding: Boolean): Float {
         return if (doesAnyTaskRequireTaskbarRounding) {
             MAX_ROUNDNESS
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index fc307b2..1b516be 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -34,6 +34,7 @@
 import android.content.ClipDescription;
 import android.content.Intent;
 import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Point;
@@ -51,6 +52,7 @@
 import android.window.SurfaceSyncGroup;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.app.animation.Interpolators;
 import com.android.internal.logging.InstanceId;
@@ -66,6 +68,7 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.folder.Folder;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
 import com.android.launcher3.logging.StatsLogManager;
@@ -74,16 +77,18 @@
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.views.BubbleTextHolder;
-import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LogUtils;
 import com.android.quickstep.util.MultiValueUpdateListener;
+import com.android.quickstep.util.SingleTask;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
 import com.android.wm.shell.shared.draganddrop.DragAndDropConstants;
 
 import java.io.PrintWriter;
@@ -112,6 +117,7 @@
     private int mRegistrationY;
 
     private boolean mIsSystemDragInProgress;
+    private boolean mIsDropHandledByDropTarget;
 
     // Animation for the drag shadow back into position after an unsuccessful drag
     private ValueAnimator mReturnAnimator;
@@ -248,7 +254,8 @@
                 /* originalView = */ btv,
                 dragLayerX + dragOffset.x,
                 dragLayerY + dragOffset.y,
-                (View target, DropTarget.DragObject d, boolean success) -> {} /* DragSource */,
+                (View target, DropTarget.DragObject d, boolean success) ->
+                        mIsDropHandledByDropTarget = success /* DragSource */,
                 btv.getTag() instanceof ItemInfo itemInfo ? itemInfo : null,
                 dragRect,
                 scale * iconScale,
@@ -344,7 +351,8 @@
         // TODO(297921594) clean it up when taskbar to desktop drag is implemented.
         // Pre-drag has ended, start the global system drag.
         if (mDisallowGlobalDrag
-                || mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible()) {
+                || mControllers.taskbarDesktopModeController
+                    .isInDesktopModeAndNotInOverview(mActivity.getDisplayId())) {
             AbstractFloatingView.closeAllOpenViewsExcept(mActivity, TYPE_TASKBAR_ALL_APPS);
             return;
         }
@@ -415,6 +423,10 @@
                                 item.user));
                 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, item.getIntent().getPackage());
                 intent.putExtra(Intent.EXTRA_SHORTCUT_ID, deepShortcutId);
+                ShortcutInfo shortcutInfo = ((WorkspaceItemInfo) item).getDeepShortcutInfo();
+                if (BubbleAnythingFlagHelper.enableCreateAnyBubble() && shortcutInfo != null) {
+                    intent.putExtra(DragAndDropConstants.EXTRA_SHORTCUT_INFO, shortcutInfo);
+                }
             } else if (item.itemType == ITEM_TYPE_SEARCH_ACTION) {
                 // TODO(b/289261756): Buggy behavior when split opposite to an existing search pane.
                 intent.putExtra(
@@ -432,8 +444,8 @@
                                 null, item.user));
             }
             intent.putExtra(Intent.EXTRA_USER, item.user);
-        } else if (tag instanceof GroupTask groupTask && !groupTask.hasMultipleTasks()) {
-            Task task = groupTask.task1;
+        } else if (tag instanceof SingleTask singleTask) {
+            Task task = singleTask.getTask();
             clipDescription = new ClipDescription(task.titleDescription,
                     new String[] {
                             ClipDescription.MIMETYPE_APPLICATION_TASK
@@ -510,6 +522,7 @@
         return mIsSystemDragInProgress;
     }
 
+    @VisibleForTesting
     private void maybeOnDragEnd() {
         if (!isDragging()) {
             ((BubbleTextView) mDragObject.originalView).setIconDisabled(false);
@@ -517,20 +530,41 @@
                     TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, false);
             mActivity.onDragEnd();
             if (mReturnAnimator == null) {
+                // If an item is dropped on the bubble bar, the bubble bar handles the drop,
+                // so it should not collapse along with the taskbar.
+                boolean droppedOnBubbleBar = notifyBubbleBarItemDropped();
                 // Upon successful drag, immediately stash taskbar.
                 // Note, this must be done last to ensure no AutohideSuspendFlags are active, as
                 // that will prevent us from stashing until the timeout.
-                mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
-
+                mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(
+                        /* stash = */ true,
+                        /* shouldBubblesFollow = */ !droppedOnBubbleBar
+                );
                 mActivity.getStatsLogManager().logger().withItemInfo(mDragObject.dragInfo)
                         .log(LAUNCHER_APP_LAUNCH_DRAGDROP);
             }
         }
     }
 
+    /**
+     * Exits the Bubble Bar drop target mode if applicable.
+     *
+     * @return {@code true} if drop target mode was active.
+     */
+    private boolean notifyBubbleBarItemDropped() {
+        return mControllers.bubbleControllers.map(bc -> {
+            BubbleBarViewController bubbleBarViewController = bc.bubbleBarViewController;
+            boolean showingDropTarget = bubbleBarViewController.isShowingDropTarget();
+            if (showingDropTarget) {
+                bubbleBarViewController.onItemDroppedInBubbleBarDragZone();
+            }
+            return showingDropTarget;
+        }).orElse(false);
+    }
+
     @Override
     protected void endDrag() {
-        if (mDisallowGlobalDrag) {
+        if (mDisallowGlobalDrag && !mIsDropHandledByDropTarget) {
             // We need to explicitly set deferDragViewCleanupPostAnimation to true here so the
             // super call doesn't remove it from the drag layer before the animation completes.
             // This variable gets set in to false in super.dispatchDropComplete() because it
@@ -734,8 +768,11 @@
 
     @Override
     public void addDropTarget(DropTarget target) {
-        // No-op as Taskbar currently doesn't support any drop targets internally.
-        // Note: if we do add internal DropTargets, we'll still need to ignore Folder.
+        if (target instanceof Folder) {
+            // we need to ignore Folder.
+            return;
+        }
+        super.addDropTarget(target);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 8b52112..4dbad8c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -186,6 +186,7 @@
 
     @Override
     protected void dispatchDraw(Canvas canvas) {
+        if (mContainer.isDestroyed()) return;
         float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
                 * (1f - mTaskbarBackgroundOffset);
         mBackgroundRenderer.setBackgroundHeight(backgroundHeight);
@@ -254,6 +255,11 @@
         invalidate();
     }
 
+    protected void setBackgroundTranslationXForBubbleBar(float translationX) {
+        mBackgroundRenderer.setTranslationXForBubbleBar(translationX);
+        invalidate();
+    }
+
     /** Returns the bounds in DragLayer coordinates of where the transient background was drawn. */
     protected RectF getLastDrawnTransientRect() {
         return mBackgroundRenderer.getLastDrawnTransientRect();
@@ -281,6 +287,21 @@
     }
 
     /**
+     * Sets animation boolean when only animating persistent taskbar.
+     */
+    public void setIsAnimatingPersistentTaskbarBackground(boolean animatingPersistentTaskbarBg) {
+        mBackgroundRenderer.setAnimatingPersistentTaskbar(animatingPersistentTaskbarBg);
+    }
+
+    /**
+     * Sets animation boolean when only animating transient taskbar.
+     */
+    public void setIsAnimatingTransientTaskbarBackground(boolean animatingTransientTaskbarBg) {
+        mBackgroundRenderer.setAnimatingTransientTaskbar(animatingTransientTaskbarBg);
+    }
+
+
+    /**
      * Sets the width percentage to inset the transient taskbar's background from the left and from
      * the right.
      */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
index 925e10b..55ecc37 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayerController.java
@@ -15,9 +15,12 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT;
 import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT;
 
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Point;
@@ -29,6 +32,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.util.DimensionUtils;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
@@ -58,6 +62,8 @@
     private final AnimatedFloat mImeBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
     private final AnimatedFloat mAssistantBgTaskbar = new AnimatedFloat(
             this::updateBackgroundAlpha);
+    private final AnimatedFloat mBgTaskbarRecreate = new AnimatedFloat(
+            this::updateBackgroundAlpha);
     // Used to hide our background color when someone else (e.g. ScrimView) is handling it.
     private final AnimatedFloat mBgOverride = new AnimatedFloat(this::updateBackgroundAlpha);
 
@@ -88,7 +94,10 @@
         mFolderMargin = resources.getDimensionPixelSize(R.dimen.taskbar_folder_margin);
     }
 
-    public void init(TaskbarControllers controllers) {
+    /**
+     * Init of taskbar drag layer controller
+     */
+    public void init(TaskbarControllers controllers, AnimatorSet startAnimation) {
         mControllers = controllers;
         mTaskbarStashViaTouchController = new TaskbarStashViaTouchController(mControllers);
         mTaskbarDragLayer.init(new TaskbarDragLayerCallbacks());
@@ -96,15 +105,45 @@
         mOnBackgroundNavButtonColorIntensity = mControllers.navbarButtonsViewController
                 .getOnTaskbarBackgroundNavButtonColorOverride();
 
-        mTaskbarBackgroundProgress.updateValue(DisplayController.isTransientTaskbar(mActivity)
-                ? PINNING_TRANSIENT
-                : PINNING_PERSISTENT);
+
+        if (startAnimation != null) {
+            // set taskbar background render animation boolean
+            if (DisplayController.isTransientTaskbar(mActivity)) {
+                mTaskbarDragLayer.setIsAnimatingTransientTaskbarBackground(true);
+            } else {
+                mTaskbarDragLayer.setIsAnimatingPersistentTaskbarBackground(true);
+            }
+
+            float desiredValue = DisplayController.isTransientTaskbar(mActivity)
+                    ? PINNING_TRANSIENT
+                    : PINNING_PERSISTENT;
+
+            float nonDesiredvalue = !DisplayController.isTransientTaskbar(mActivity)
+                    ? PINNING_TRANSIENT
+                    : PINNING_PERSISTENT;
+
+            ObjectAnimator objectAnimator = mTaskbarBackgroundProgress.animateToValue(
+                    nonDesiredvalue, desiredValue);
+            objectAnimator.setInterpolator(EMPHASIZED);
+            startAnimation.play(objectAnimator);
+            startAnimation.addListener(AnimatorListeners.forEndCallback(()-> {
+                // reset taskbar background render animation boolean
+                mTaskbarDragLayer.setIsAnimatingPersistentTaskbarBackground(false);
+                mTaskbarDragLayer.setIsAnimatingTransientTaskbarBackground(false);
+            }));
+
+        } else {
+            mTaskbarBackgroundProgress.updateValue(DisplayController.isTransientTaskbar(mActivity)
+                    ? PINNING_TRANSIENT
+                    : PINNING_PERSISTENT);
+        }
 
         mBgTaskbar.value = 1;
         mKeyguardBgTaskbar.value = 1;
         mNotificationShadeBgTaskbar.value = 1;
         mImeBgTaskbar.value = 1;
         mAssistantBgTaskbar.value = 1;
+        mBgTaskbarRecreate.value = 1;
         mBgOverride.value = 1;
         updateBackgroundAlpha();
 
@@ -112,6 +151,13 @@
         updateTaskbarAlpha();
     }
 
+    /**
+     * Called when destroying Taskbar with animation.
+     */
+    public void onDestroyAnimation(AnimatorSet animatorSet) {
+        animatorSet.play(mBgTaskbarRecreate.animateToValue(0f));
+    }
+
     public void onDestroy() {
         mTaskbarDragLayer.onDestroy();
     }
@@ -172,14 +218,14 @@
     }
 
     private void updateBackgroundAlpha() {
-        if (mActivity.isPhoneMode()) {
+        if (mActivity.isPhoneMode() || mActivity.isDestroyed()) {
             return;
         }
 
         final float bgNavbar = mBgNavbar.value;
         final float bgTaskbar = mBgTaskbar.value * mKeyguardBgTaskbar.value
                 * mNotificationShadeBgTaskbar.value * mImeBgTaskbar.value
-                * mAssistantBgTaskbar.value;
+                * mAssistantBgTaskbar.value * mBgTaskbarRecreate.value;
         mLastSetBackgroundAlpha = mBgOverride.value * Math.max(bgNavbar, bgTaskbar);
         mBackgroundRendererAlpha.setValue(mLastSetBackgroundAlpha);
 
@@ -198,6 +244,13 @@
     }
 
     /**
+     * Sets the translation of the background for the bubble bar.
+     */
+    public void setTranslationXForBubbleBar(float transX) {
+        mTaskbarDragLayer.setBackgroundTranslationXForBubbleBar(transX);
+    }
+
+    /**
      * Sets the translation of the background during the spring on stash animation.
      */
     public void setTranslationYForStash(float transY) {
@@ -259,6 +312,7 @@
         pw.println(prefix + "\t\tmNotificationShadeBgTaskbar=" + mNotificationShadeBgTaskbar.value);
         pw.println(prefix + "\t\tmImeBgTaskbar=" + mImeBgTaskbar.value);
         pw.println(prefix + "\t\tmAssistantBgTaskbar=" + mAssistantBgTaskbar.value);
+        pw.println(prefix + "\t\tmBgTaskbarRecreate=" + mBgTaskbarRecreate.value);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
index 26a552e..d624413 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarEduTooltipController.kt
@@ -39,6 +39,7 @@
 import com.airbnb.lottie.LottieAnimationView
 import com.android.launcher3.LauncherPrefs
 import com.android.launcher3.R
+import com.android.launcher3.RemoveAnimationSettingsTracker
 import com.android.launcher3.Utilities
 import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
 import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
@@ -51,6 +52,8 @@
 import com.android.launcher3.views.BaseDragLayer
 import com.android.quickstep.util.ContextualSearchInvoker
 import com.android.quickstep.util.LottieAnimationColorUtils
+import com.android.wm.shell.shared.TypefaceUtils
+import com.android.wm.shell.shared.TypefaceUtils.FontFamily
 import java.io.PrintWriter
 
 /** First EDU step for swiping up to show transient Taskbar. */
@@ -128,6 +131,26 @@
         activityContext.dragLayer.post { maybeShowSearchEdu() }
     }
 
+    /**
+     * Turns off auto play of lottie animations if user has opted to remove animation else attaches
+     * click listener to allow user to play or pause animations.
+     */
+    fun handleEduAnimations(animationViews: List<LottieAnimationView>) {
+        for (animationView in animationViews) {
+            if (
+                RemoveAnimationSettingsTracker.INSTANCE.get(animationView.context)
+                    .isRemoveAnimationEnabled()
+            ) {
+                animationView.pauseAnimation()
+            } else {
+                animationView.setOnClickListener {
+                    if (animationView.isAnimating) animationView.pauseAnimation()
+                    else animationView.playAnimation()
+                }
+            }
+        }
+    }
+
     /** Shows swipe EDU tooltip if it is the current [tooltipStep]. */
     fun maybeShowSwipeEdu() {
         if (
@@ -141,7 +164,13 @@
         tooltipStep = TOOLTIP_STEP_FEATURES
         inflateTooltip(R.layout.taskbar_edu_swipe)
         tooltip?.run {
-            requireViewById<LottieAnimationView>(R.id.swipe_animation).supportLightTheme()
+            TypefaceUtils.setTypeface(
+                requireViewById(R.id.taskbar_edu_title),
+                FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED,
+            )
+            val swipeAnimation = requireViewById<LottieAnimationView>(R.id.swipe_animation)
+            swipeAnimation.supportLightTheme()
+            handleEduAnimations(listOf(swipeAnimation))
             show()
         }
     }
@@ -170,6 +199,7 @@
             splitscreenAnim.supportLightTheme()
             suggestionsAnim.supportLightTheme()
             pinningAnim.supportLightTheme()
+            handleEduAnimations(listOf(splitscreenAnim, suggestionsAnim, pinningAnim))
             if (DisplayController.isTransientTaskbar(activityContext)) {
                 splitscreenAnim.setAnimation(R.raw.taskbar_edu_splitscreen_transient)
                 suggestionsAnim.setAnimation(R.raw.taskbar_edu_suggestions_transient)
@@ -180,6 +210,23 @@
                 pinningEdu.visibility = GONE
             }
 
+            TypefaceUtils.setTypeface(
+                requireViewById(R.id.taskbar_edu_title),
+                FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED,
+            )
+            TypefaceUtils.setTypeface(
+                requireViewById(R.id.splitscreen_text),
+                FontFamily.GSF_BODY_MEDIUM,
+            )
+            TypefaceUtils.setTypeface(
+                requireViewById(R.id.suggestions_text),
+                FontFamily.GSF_BODY_MEDIUM,
+            )
+            TypefaceUtils.setTypeface(
+                requireViewById(R.id.pinning_text),
+                FontFamily.GSF_BODY_MEDIUM,
+            )
+
             // Set up layout parameters.
             content.updateLayoutParams { width = MATCH_PARENT }
             updateLayoutParams<MarginLayoutParams> {
@@ -228,9 +275,19 @@
 
         tooltip?.run {
             allowTouchDismissal = true
-            requireViewById<LottieAnimationView>(R.id.standalone_pinning_animation)
-                .supportLightTheme()
+            TypefaceUtils.setTypeface(
+                requireViewById(R.id.taskbar_edu_title),
+                FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED,
+            )
+            TypefaceUtils.setTypeface(
+                requireViewById(R.id.pinning_text),
+                FontFamily.GSF_BODY_MEDIUM,
+            )
 
+            val pinningAnim =
+                requireViewById<LottieAnimationView>(R.id.standalone_pinning_animation)
+            pinningAnim.supportLightTheme()
+            handleEduAnimations(listOf(pinningAnim))
             updateLayoutParams<BaseDragLayer.LayoutParams> {
                 if (DisplayController.isTransientTaskbar(activityContext)) {
                     bottomMargin += activityContext.deviceProfile.taskbarHeight
@@ -274,8 +331,17 @@
         inflateTooltip(R.layout.taskbar_edu_search)
         tooltip?.run {
             allowTouchDismissal = true
-            requireViewById<LottieAnimationView>(R.id.search_edu_animation).supportLightTheme()
+            val searchEdu = requireViewById<LottieAnimationView>(R.id.search_edu_animation)
+            searchEdu.supportLightTheme()
+            handleEduAnimations(listOf(searchEdu))
             val eduSubtitle: TextView = requireViewById(R.id.search_edu_text)
+
+            TypefaceUtils.setTypeface(
+                requireViewById(R.id.taskbar_edu_title),
+                FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED,
+            )
+            TypefaceUtils.setTypeface(eduSubtitle, FontFamily.GSF_BODY_SMALL)
+
             showDisclosureText(eduSubtitle)
             updateLayoutParams<BaseDragLayer.LayoutParams> {
                 if (DisplayController.isTransientTaskbar(activityContext)) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
index 3bff31f..b7000db 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHoverToolTipController.java
@@ -108,7 +108,7 @@
             revealHoverToolTip();
             mActivity.setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, true);
         }
-        return true;
+        return false;
     }
 
     private void revealHoverToolTip() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 250e33a..b510e7e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -58,7 +58,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.AnimatorListeners;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -192,6 +191,8 @@
 
     private boolean mIsQsbInline;
 
+    private RecentsAnimationCallbacks mRecentsAnimationCallbacks;
+
     private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
             new DeviceProfile.OnDeviceProfileChangeListener() {
                 @Override
@@ -224,9 +225,13 @@
                     updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true);
                     if (!mShouldDelayLauncherStateAnim) {
                         if (toState == LauncherState.NORMAL) {
-                            applyState(QuickstepTransitionManager.getTaskbarToHomeDuration(
+                            boolean isPinnedTaskbarAndNotInDesktopMode =
                                     DisplayController.isPinnedTaskbar(
-                                            mControllers.taskbarActivityContext)));
+                                            mControllers.taskbarActivityContext)
+                                            && !DisplayController.isInDesktopMode(
+                                            mControllers.taskbarActivityContext);
+                            applyState(QuickstepTransitionManager.getTaskbarToHomeDuration(
+                                    isPinnedTaskbarAndNotInDesktopMode));
                         } else {
                             applyState();
                         }
@@ -292,6 +297,11 @@
         mIsDestroyed = true;
         mCanSyncViews = false;
 
+        if (mRecentsAnimationCallbacks != null) {
+            mRecentsAnimationCallbacks.removeListener(mTaskBarRecentsAnimationListener);
+            mRecentsAnimationCallbacks = null;
+        }
+
         mIconAlignment.finishAnimation();
 
         mLauncher.getHotseat().setIconsAlpha(1f, ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
@@ -312,6 +322,7 @@
         // If going to overview, stash the task bar
         // If going home, align the icons to hotseat
         AnimatorSet animatorSet = new AnimatorSet();
+        mRecentsAnimationCallbacks = callbacks;
 
         // Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly.
         TaskbarStashController stashController = mControllers.taskbarStashController;
@@ -393,10 +404,7 @@
      * @param launcherState The current state launcher is in
      */
     private void updateOverviewDragState(LauncherState launcherState) {
-        boolean disallowLongClick =
-                FeatureFlags.enableSplitContextually()
-                        ? mLauncher.isSplitSelectionActive() || mIsAnimatingToLauncher
-                        : launcherState == LauncherState.OVERVIEW_SPLIT_SELECT;
+        boolean disallowLongClick = mLauncher.isSplitSelectionActive() || mIsAnimatingToLauncher;
         com.android.launcher3.taskbar.Utilities.setOverviewDragState(
                 mControllers, launcherState.disallowTaskbarGlobalDrag(),
                 disallowLongClick, launcherState.allowTaskbarInitialSplitSelection());
@@ -635,7 +643,8 @@
 
         float cornerRoundness = isInLauncher ? 0 : 1;
 
-        if (mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible()
+        if (mControllers.taskbarDesktopModeController.isInDesktopModeAndNotInOverview(
+                mControllers.taskbarActivityContext.getDisplayId())
                 && mControllers.getSharedState() != null) {
             cornerRoundness =
                     mControllers.taskbarDesktopModeController.getTaskbarCornerRoundness(
@@ -683,8 +692,11 @@
         } else if (mIconAlignment.isAnimatingToValue(toAlignment)
                 || mIconAlignment.isSettledOnValue(toAlignment)) {
             // Already at desired value, but make sure we run the callback at the end.
-            animatorSet.addListener(AnimatorListeners.forEndCallback(
-                    this::onIconAlignmentRatioChanged));
+            animatorSet.addListener(AnimatorListeners.forEndCallback(() -> {
+                if (!mIconAlignment.isAnimating()) {
+                    onIconAlignmentRatioChanged();
+                }
+            }));
         } else {
             mIconAlignment.cancelAnimation();
             ObjectAnimator iconAlignAnim = mIconAlignment
@@ -717,6 +729,10 @@
 
     private static boolean shouldShowTaskbar(Context context, boolean isInLauncher,
             boolean isInOverview) {
+        if (DisplayController.showDesktopTaskbarForFreeformDisplay(context)) {
+            return true;
+        }
+
         if (DisplayController.showLockedTaskbarOnHome(context) && isInLauncher) {
             return true;
         }
@@ -772,9 +788,14 @@
      * This refers to the intended state - a transition to this state might be in progress.
      */
     public boolean isTaskbarAlignedWithHotseat() {
+        if (DisplayController.showDesktopTaskbarForFreeformDisplay(mLauncher)) {
+            return false;
+        }
+
         if (DisplayController.showLockedTaskbarOnHome(mLauncher) && isInLauncher()) {
             return false;
         }
+
         return mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 9407e73..bee0997 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -15,24 +15,28 @@
  */
 package com.android.launcher3.taskbar;
 
+import static android.content.Context.RECEIVER_EXPORTED;
 import static android.content.Context.RECEIVER_NOT_EXPORTED;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 
 import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
+import static com.android.launcher3.Flags.enableGrowthNudge;
 import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
 import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
+import static com.android.launcher3.taskbar.growth.GrowthConstants.BROADCAST_SHOW_NUDGE;
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
 import static com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE;
 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
+import static com.android.launcher3.util.DisplayController.CHANGE_SHOW_LOCKED_TASKBAR;
 import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
-import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
 import static com.android.quickstep.util.SystemActionConstants.ACTION_SHOW_TASKBAR;
 import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR;
 
+import android.animation.AnimatorSet;
 import android.annotation.SuppressLint;
 import android.app.PendingIntent;
 import android.content.ComponentCallbacks;
@@ -45,22 +49,26 @@
 import android.os.Handler;
 import android.os.Trace;
 import android.provider.Settings;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.view.Display;
 import android.view.MotionEvent;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
+import android.window.DesktopExperienceFlags;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
 import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
@@ -72,16 +80,24 @@
 import com.android.quickstep.AllAppsActionManager;
 import com.android.quickstep.RecentsActivity;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
+import com.android.quickstep.fallback.window.RecentsWindowFlags;
 import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.util.ContextualSearchInvoker;
+import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.views.RecentsViewContainer;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.statusbar.phone.BarTransitions;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
+import com.android.systemui.shared.system.TaskStackChangeListener;
+import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
 
 import java.io.PrintWriter;
+import java.util.Set;
 import java.util.StringJoiner;
 
 /**
@@ -90,6 +106,10 @@
 public class TaskbarManager {
     private static final String TAG = "TaskbarManager";
     private static final boolean DEBUG = false;
+    private static final int TASKBAR_DESTROY_DURATION = 100;
+
+    // TODO: b/397738606  - Remove all logs with this tag after the growth framework is integrated.
+    public static final String GROWTH_FRAMEWORK_TAG = "Growth Framework";
 
     /**
      * All the configurations which do not initiate taskbar recreation.
@@ -113,15 +133,15 @@
     private static final Uri NAV_BAR_KIDS_MODE = Settings.Secure.getUriFor(
             Settings.Secure.NAV_BAR_KIDS_MODE);
 
-    private final Context mWindowContext;
-    private final @Nullable Context mNavigationBarPanelContext;
-    private WindowManager mWindowManager;
-    private boolean mAddedWindow;
-    private final TaskbarNavButtonController mDefaultNavButtonController;
-    private final ComponentCallbacks mDefaultComponentCallbacks;
+    private final Context mBaseContext;
+    private final TaskbarNavButtonCallbacks mNavCallbacks;
+    // TODO: Remove this during the connected displays lifecycle refactor.
+    private final Context mPrimaryWindowContext;
+    private final WindowManager mPrimaryWindowManager;
+    private TaskbarNavButtonController mPrimaryNavButtonController;
+    private ComponentCallbacks mPrimaryComponentCallbacks;
 
-    private final SimpleBroadcastReceiver mShutdownReceiver =
-            new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, i -> destroyAllTaskbars());
+    private final SimpleBroadcastReceiver mShutdownReceiver;
 
     // The source for this provider is set when Launcher is available
     // We use 'non-destroyable' version here so the original provider won't be destroyed
@@ -131,8 +151,19 @@
             new NonDestroyableScopedUnfoldTransitionProgressProvider();
     /** DisplayId - {@link TaskbarActivityContext} map for Connected Display. */
     private final SparseArray<TaskbarActivityContext> mTaskbars = new SparseArray<>();
+    /** DisplayId - {@link Context} map for Connected Display. */
+    private final SparseArray<Context> mWindowContexts = new SparseArray<>();
     /** DisplayId - {@link FrameLayout} map for Connected Display. */
     private final SparseArray<FrameLayout> mRootLayouts = new SparseArray<>();
+    /** DisplayId - {@link Boolean} map indicating if RootLayout was added to window. */
+    private final SparseBooleanArray mAddedRootLayouts = new SparseBooleanArray();
+    /** DisplayId - {@link TaskbarNavButtonController} map for Connected Display. */
+    private final SparseArray<TaskbarNavButtonController> mNavButtonControllers =
+            new SparseArray<>();
+    /** DisplayId - {@link ComponentCallbacks} map for Connected Display. */
+    private final SparseArray<ComponentCallbacks> mComponentCallbacks = new SparseArray<>();
+    /** DisplayId - {@link DeviceProfile} map for Connected Display. */
+    private final SparseArray<DeviceProfile> mExternalDeviceProfiles = new SparseArray<>();
     private StatefulActivity mActivity;
     private RecentsViewContainer mRecentsViewContainer;
 
@@ -148,45 +179,196 @@
      */
     private final RecreationListener mRecreationListener = new RecreationListener();
 
+    // Currently, there is a duplicative call to recreate taskbars when user enter/exit Desktop
+    // Mode upon getting transition callback from shell side. So, we make sure that if taskbar is
+    // already in recreate process due to transition callback, don't recreate for
+    // DisplayInfoChangeListener.
+    private boolean mShouldIgnoreNextDesktopModeChangeFromDisplayController = false;
+
     private class RecreationListener implements DisplayController.DisplayInfoChangeListener {
         @Override
         public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+            if ((flags & CHANGE_DENSITY) != 0) {
+                debugTaskbarManager("onDisplayInfoChanged: Display density changed",
+                        context.getDisplayId());
+            }
+            if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
+                debugTaskbarManager("onDisplayInfoChanged: Navigation mode changed",
+                        context.getDisplayId());
+            }
+            if ((flags & CHANGE_DESKTOP_MODE) != 0) {
+                debugTaskbarManager("onDisplayInfoChanged: Desktop mode changed",
+                        context.getDisplayId());
+            }
+            if ((flags & CHANGE_TASKBAR_PINNING) != 0) {
+                debugTaskbarManager("onDisplayInfoChanged: Taskbar pinning changed",
+                        context.getDisplayId());
+            }
+
             if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE | CHANGE_DESKTOP_MODE
-                    | CHANGE_TASKBAR_PINNING)) != 0) {
-                recreateTaskbar();
+                    | CHANGE_TASKBAR_PINNING | CHANGE_SHOW_LOCKED_TASKBAR)) != 0) {
+                debugTaskbarManager("onDisplayInfoChanged: Recreating Taskbar!",
+                        context.getDisplayId());
+                TaskbarActivityContext taskbarActivityContext = getCurrentActivityContext();
+                if ((flags & CHANGE_SHOW_LOCKED_TASKBAR) != 0) {
+                    recreateTaskbars();
+                } else if ((flags & CHANGE_DESKTOP_MODE) != 0) {
+                    if (mShouldIgnoreNextDesktopModeChangeFromDisplayController) {
+                        mShouldIgnoreNextDesktopModeChangeFromDisplayController = false;
+                        return;
+                    }
+                    // Only Handles Special Exit Cases for Desktop Mode Taskbar Recreation.
+                    if (taskbarActivityContext != null
+                            && !DisplayController.showLockedTaskbarOnHome(context)) {
+                        recreateTaskbars();
+                    }
+                } else {
+                    recreateTaskbars();
+                }
             }
         }
     }
-    private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> recreateTaskbar();
+
+    private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> {
+        debugPrimaryTaskbar("Settings changed! Recreating Taskbar!");
+        recreateTaskbars();
+    };
+
+    private final PerceptibleTaskListener mTaskStackListener;
+
+    private class PerceptibleTaskListener implements TaskStackChangeListener {
+        private ArraySet<Integer> mPerceptibleTasks = new ArraySet<Integer>();
+
+        @Override
+        public void onTaskMovedToFront(int taskId) {
+            // This listens to any Task, so we filter them by the ones shown in the launcher.
+            // For Tasks restored after startup, they will by default not be Perceptible, and no
+            // need to until user interacts with it by bringing it to the foreground.
+            for (int i = 0; i < mTaskbars.size(); i++) {
+                // get pinned tasks - we care about all tasks, not just the one moved to the front
+                Set<Integer> taskbarPinnedTasks =
+                        mTaskbars.valueAt(i).getControllers().taskbarViewController
+                                .getTaskIdsForPinnedApps();
+
+                // filter out tasks already marked as perceptible
+                taskbarPinnedTasks.removeAll(mPerceptibleTasks);
+
+                // add the filtered tasks as perceptible
+                for (int pinnedTaskId : taskbarPinnedTasks) {
+                    ActivityManagerWrapper.getInstance()
+                            .setTaskIsPerceptible(pinnedTaskId, true);
+                    mPerceptibleTasks.add(pinnedTaskId);
+                }
+            }
+        }
+
+        /**
+         * Launcher also can display recently launched tasks that are not pinned. Also add
+         * these as perceptible
+         */
+        @Override
+        public void onRecentTaskListUpdated() {
+            for (int i = 0; i < mTaskbars.size(); i++) {
+                for (GroupTask gTask : mTaskbars.valueAt(i).getControllers()
+                        .taskbarRecentAppsController.getShownTasks()) {
+                    for (Task task : gTask.getTasks()) {
+                        int taskId = task.key.id;
+
+                        if (!mPerceptibleTasks.contains(taskId)) {
+                            ActivityManagerWrapper.getInstance()
+                                    .setTaskIsPerceptible(taskId, true);
+                            mPerceptibleTasks.add(taskId);
+                        }
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onTaskRemoved(int taskId) {
+            mPerceptibleTasks.remove(taskId);
+        }
+    }
+
+    private final DesktopVisibilityController.TaskbarDesktopModeListener
+            mTaskbarDesktopModeListener =
+            new DesktopVisibilityController.TaskbarDesktopModeListener() {
+                @Override
+                public void onExitDesktopMode(int duration) {
+                    for (int taskbarIndex = 0; taskbarIndex < mTaskbars.size(); taskbarIndex++) {
+                        int displayId = mTaskbars.keyAt(taskbarIndex);
+                        TaskbarActivityContext taskbarActivityContext = getTaskbarForDisplay(
+                                displayId);
+                        if (taskbarActivityContext != null
+                                && !taskbarActivityContext.isInOverview()) {
+                            mShouldIgnoreNextDesktopModeChangeFromDisplayController = true;
+                            AnimatorSet animatorSet = taskbarActivityContext.onDestroyAnimation(
+                                    TASKBAR_DESTROY_DURATION);
+                            animatorSet.addListener(AnimatorListeners.forEndCallback(
+                                    () -> recreateTaskbarForDisplay(getDefaultDisplayId(),
+                                            duration)));
+                            animatorSet.start();
+                        }
+                    }
+                }
+
+                @Override
+                public void onEnterDesktopMode(int duration) {
+                    for (int taskbarIndex = 0; taskbarIndex < mTaskbars.size(); taskbarIndex++) {
+                        int displayId = mTaskbars.keyAt(taskbarIndex);
+                        TaskbarActivityContext taskbarActivityContext = getTaskbarForDisplay(
+                                displayId);
+                        if (taskbarActivityContext != null) {
+                            mShouldIgnoreNextDesktopModeChangeFromDisplayController = true;
+                            AnimatorSet animatorSet = taskbarActivityContext.onDestroyAnimation(
+                                    TASKBAR_DESTROY_DURATION);
+                            animatorSet.addListener(AnimatorListeners.forEndCallback(
+                                    () -> recreateTaskbarForDisplay(getDefaultDisplayId(),
+                                            duration)));
+                            animatorSet.start();
+                        }
+                    }
+                }
+
+                @Override
+                public void onTaskbarCornerRoundingUpdate(
+                        boolean doesAnyTaskRequireTaskbarRounding) {
+                    //NO-OP
+                }
+            };
+
 
     private boolean mUserUnlocked = false;
 
-    private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver =
-            new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, this::showTaskbarFromBroadcast);
+    private final SimpleBroadcastReceiver mTaskbarBroadcastReceiver;
+
+    private final SimpleBroadcastReceiver mGrowthBroadcastReceiver;
 
     private final AllAppsActionManager mAllAppsActionManager;
+    private final RecentsDisplayModel mRecentsDisplayModel;
 
     private final Runnable mActivityOnDestroyCallback = new Runnable() {
         @Override
         public void run() {
             int displayId = getDefaultDisplayId();
+            debugTaskbarManager("onActivityDestroyed:", displayId);
             if (mActivity != null) {
                 displayId = mActivity.getDisplayId();
                 mActivity.removeOnDeviceProfileChangeListener(
                         mDebugActivityDeviceProfileChanged);
-                Log.d(TASKBAR_NOT_DESTROYED_TAG,
-                        "unregistering activity lifecycle callbacks from "
-                                + "onActivityDestroyed.");
+                debugTaskbarManager("onActivityDestroyed: unregistering callbacks", displayId);
                 mActivity.removeEventCallback(EVENT_DESTROYED, this);
             }
             if (mActivity == mRecentsViewContainer) {
                 mRecentsViewContainer = null;
             }
             mActivity = null;
-            debugWhyTaskbarNotDestroyed("clearActivity");
             TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
             if (taskbar != null) {
+                debugTaskbarManager("onActivityDestroyed: setting taskbarUIController", displayId);
                 taskbar.setUIController(TaskbarUIController.DEFAULT);
+            } else {
+                debugTaskbarManager("onActivityDestroyed: taskbar is null!", displayId);
             }
             mUnfoldProgressProvider.setSourceProvider(null);
         }
@@ -196,169 +378,120 @@
             new UnfoldTransitionProgressProvider.TransitionProgressListener() {
                 @Override
                 public void onTransitionStarted() {
-                    Log.d(TASKBAR_NOT_DESTROYED_TAG,
-                            "fold/unfold transition started getting called.");
+                    debugPrimaryTaskbar("fold/unfold transition started getting called.");
                 }
 
                 @Override
                 public void onTransitionProgress(float progress) {
-                    Log.d(TASKBAR_NOT_DESTROYED_TAG,
-                            "fold/unfold transition progress : " + progress);
+                    debugPrimaryTaskbar(
+                            "fold/unfold transition progress getting called. | progress="
+                                    + progress);
                 }
 
                 @Override
                 public void onTransitionFinishing() {
-                    Log.d(TASKBAR_NOT_DESTROYED_TAG,
+                    debugPrimaryTaskbar(
                             "fold/unfold transition finishing getting called.");
 
                 }
 
                 @Override
                 public void onTransitionFinished() {
-                    Log.d(TASKBAR_NOT_DESTROYED_TAG,
+                    debugPrimaryTaskbar(
                             "fold/unfold transition finished getting called.");
-
                 }
             };
 
-    @NonNull private final DesktopVisibilityController mDesktopVisibilityController;
-
     @SuppressLint("WrongConstant")
     public TaskbarManager(
             Context context,
             AllAppsActionManager allAppsActionManager,
             TaskbarNavButtonCallbacks navCallbacks,
-            @NonNull DesktopVisibilityController desktopVisibilityController) {
-        Display display =
-                context.getSystemService(DisplayManager.class).getDisplay(context.getDisplayId());
-        mWindowContext = context.createWindowContext(display,
-                ENABLE_TASKBAR_NAVBAR_UNIFICATION ? TYPE_NAVIGATION_BAR : TYPE_NAVIGATION_BAR_PANEL,
-                null);
+            RecentsDisplayModel recentsDisplayModel) {
+        mBaseContext = context;
         mAllAppsActionManager = allAppsActionManager;
-        mNavigationBarPanelContext = ENABLE_TASKBAR_NAVBAR_UNIFICATION
-                ? context.createWindowContext(display, TYPE_NAVIGATION_BAR_PANEL, null)
-                : null;
-        mDesktopVisibilityController = desktopVisibilityController;
-        if (enableTaskbarNoRecreate()) {
-            mWindowManager = mWindowContext.getSystemService(WindowManager.class);
-            createTaskbarRootLayout(getDefaultDisplayId());
-        }
-        mDefaultNavButtonController = createDefaultNavButtonController(context, navCallbacks);
-        mDefaultComponentCallbacks = createDefaultComponentCallbacks();
-        SettingsCache.INSTANCE.get(mWindowContext)
+        mNavCallbacks = navCallbacks;
+        mRecentsDisplayModel = recentsDisplayModel;
+
+        // Set up primary display.
+        int primaryDisplayId = getDefaultDisplayId();
+        debugPrimaryTaskbar("TaskbarManager constructor");
+        mPrimaryWindowContext = createWindowContext(primaryDisplayId);
+        mPrimaryWindowManager = mPrimaryWindowContext.getSystemService(WindowManager.class);
+        DesktopVisibilityController.INSTANCE.get(
+                mPrimaryWindowContext).registerTaskbarDesktopModeListener(
+                mTaskbarDesktopModeListener);
+        createTaskbarRootLayout(primaryDisplayId);
+        createNavButtonController(primaryDisplayId);
+        createAndRegisterComponentCallbacks(primaryDisplayId);
+
+        SettingsCache.INSTANCE.get(mPrimaryWindowContext)
                 .register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
-        SettingsCache.INSTANCE.get(mWindowContext)
+        SettingsCache.INSTANCE.get(mPrimaryWindowContext)
                 .register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
-        Log.d(TASKBAR_NOT_DESTROYED_TAG, "registering component callbacks from constructor.");
-        mWindowContext.registerComponentCallbacks(mDefaultComponentCallbacks);
-        mShutdownReceiver.register(mWindowContext, Intent.ACTION_SHUTDOWN);
+        mShutdownReceiver =
+                new SimpleBroadcastReceiver(
+                        mPrimaryWindowContext, UI_HELPER_EXECUTOR, i -> destroyAllTaskbars());
+        mTaskbarBroadcastReceiver =
+                new SimpleBroadcastReceiver(mPrimaryWindowContext,
+                        UI_HELPER_EXECUTOR, this::showTaskbarFromBroadcast);
+
+        mShutdownReceiver.register(Intent.ACTION_SHUTDOWN);
+        if (enableGrowthNudge()) {
+            // TODO: b/397739323 - Add permission to limit access to Growth Framework.
+            mGrowthBroadcastReceiver =
+                    new SimpleBroadcastReceiver(
+                            mPrimaryWindowContext, UI_HELPER_EXECUTOR, this::showGrowthNudge);
+            mGrowthBroadcastReceiver.register(RECEIVER_EXPORTED,
+                    BROADCAST_SHOW_NUDGE);
+        } else {
+            mGrowthBroadcastReceiver = null;
+        }
         UI_HELPER_EXECUTOR.execute(() -> {
             mSharedState.taskbarSystemActionPendingIntent = PendingIntent.getBroadcast(
-                    mWindowContext,
+                    mPrimaryWindowContext,
                     SYSTEM_ACTION_ID_TASKBAR,
-                    new Intent(ACTION_SHOW_TASKBAR).setPackage(mWindowContext.getPackageName()),
+                    new Intent(ACTION_SHOW_TASKBAR).setPackage(
+                            mPrimaryWindowContext.getPackageName()),
                     PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-            mTaskbarBroadcastReceiver.register(
-                    mWindowContext, RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
+            mTaskbarBroadcastReceiver.register(RECEIVER_NOT_EXPORTED, ACTION_SHOW_TASKBAR);
         });
 
-        debugWhyTaskbarNotDestroyed("TaskbarManager created");
-        recreateTaskbar();
-    }
-
-    @NonNull
-    private TaskbarNavButtonController createDefaultNavButtonController(Context context,
-            TaskbarNavButtonCallbacks navCallbacks) {
-        return new TaskbarNavButtonController(
-                context,
-                navCallbacks,
-                SystemUiProxy.INSTANCE.get(mWindowContext),
-                ContextualEduStatsManager.INSTANCE.get(mWindowContext),
-                new Handler(),
-                new ContextualSearchInvoker(mWindowContext));
-    }
-
-    private ComponentCallbacks createDefaultComponentCallbacks() {
-        return new ComponentCallbacks() {
-            private Configuration mOldConfig = mWindowContext.getResources().getConfiguration();
-
-            @Override
-            public void onConfigurationChanged(Configuration newConfig) {
-                Trace.instantForTrack(Trace.TRACE_TAG_APP, "TaskbarManager",
-                        "onConfigurationChanged: " + newConfig);
-                debugWhyTaskbarNotDestroyed(
-                        "TaskbarManager#mComponentCallbacks.onConfigurationChanged: " + newConfig);
-                // TODO: adapt this logic to be specific to different displays.
-                DeviceProfile dp = mUserUnlocked
-                        ? LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext)
-                        : null;
-                int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
-
-                if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
-                    // Only recreate for theme changes, not other UI mode changes such as docking.
-                    int oldUiNightMode = (mOldConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
-                    int newUiNightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
-                    if (oldUiNightMode == newUiNightMode) {
-                        configDiff &= ~ActivityInfo.CONFIG_UI_MODE;
-                    }
-                }
-
-                debugWhyTaskbarNotDestroyed("ComponentCallbacks#onConfigurationChanged() "
-                        + "configDiff=" + Configuration.configurationDiffToString(configDiff));
-                if (configDiff != 0 || getCurrentActivityContext() == null) {
-                    recreateTaskbar();
-                } else {
-                    // Config change might be handled without re-creating the taskbar
-                    if (dp != null && !isTaskbarEnabled(dp)) {
-                        destroyDefaultTaskbar();
-                    } else {
-                        if (dp != null && isTaskbarEnabled(dp)) {
-                            if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
-                                // Re-initialize for screen size change? Should this be done
-                                // by looking at screen-size change flag in configDiff in the
-                                // block above?
-                                recreateTaskbar();
-                            } else {
-                                getCurrentActivityContext().updateDeviceProfile(dp);
-                            }
-                        }
-                        getCurrentActivityContext().onConfigurationChanged(configDiff);
-                    }
-                }
-                mOldConfig = new Configuration(newConfig);
-                // reset taskbar was pinned value, so we don't automatically unstash taskbar upon
-                // user unfolding the device.
-                mSharedState.setTaskbarWasPinned(false);
-            }
-
-            @Override
-            public void onLowMemory() { }
-        };
+        if (ActivityManagerWrapper.usePerceptibleTasks(getPrimaryWindowContext())) {
+            mTaskStackListener = new PerceptibleTaskListener();
+            TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
+        } else {
+            mTaskStackListener = null;
+        }
+        recreateTaskbars();
+        debugPrimaryTaskbar("TaskbarManager created");
     }
 
     private void destroyAllTaskbars() {
+        debugPrimaryTaskbar("destroyAllTaskbars");
         for (int i = 0; i < mTaskbars.size(); i++) {
             int displayId = mTaskbars.keyAt(i);
+            debugTaskbarManager("destroyAllTaskbars: call destroyTaskbarForDisplay", displayId);
             destroyTaskbarForDisplay(displayId);
+
+            debugTaskbarManager("destroyAllTaskbars: call removeTaskbarRootViewFromWindow",
+                    displayId);
             removeTaskbarRootViewFromWindow(displayId);
         }
     }
 
-    private void destroyDefaultTaskbar() {
-        destroyTaskbarForDisplay(getDefaultDisplayId());
-    }
-
     private void destroyTaskbarForDisplay(int displayId) {
+        debugTaskbarManager("destroyTaskbarForDisplay", displayId);
         TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
-        debugWhyTaskbarNotDestroyed(
-                "destroyTaskbarForDisplay: " + taskbar + " displayId=" + displayId);
         if (taskbar != null) {
             taskbar.onDestroy();
             // remove all defaults that we store
             removeTaskbarFromMap(displayId);
+        } else {
+            debugTaskbarManager("destroyTaskbarForDisplay: taskbar is NULL!", displayId);
         }
-        DeviceProfile dp = mUserUnlocked ?
-                LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext) : null;
+
+        DeviceProfile dp = getDeviceProfile(displayId);
         if (dp == null || !isTaskbarEnabled(dp)) {
             removeTaskbarRootViewFromWindow(displayId);
         }
@@ -368,24 +501,35 @@
      * Show Taskbar upon receiving broadcast
      */
     private void showTaskbarFromBroadcast(Intent intent) {
+        debugPrimaryTaskbar("destroyTaskbarForDisplay");
         // TODO: make this code displayId specific
         TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
-        if (ACTION_SHOW_TASKBAR.equals(intent.getAction()) && taskbar != null) {
+        if (ACTION_SHOW_TASKBAR.equals(intent.getAction())) {
             taskbar.showTaskbarFromBroadcast();
         }
     }
 
+    private void showGrowthNudge(Intent intent) {
+        if (!enableGrowthNudge()) {
+            return;
+        }
+        if (BROADCAST_SHOW_NUDGE.equals(intent.getAction())) {
+            // TODO: b/397738606 - extract the details and create a nudge payload.
+            Log.d(GROWTH_FRAMEWORK_TAG, "Intent received");
+        }
+    }
+
     /**
      * Toggles All Apps for Taskbar or Launcher depending on the current state.
      */
-    public void toggleAllApps() {
+    public void toggleAllAppsSearch() {
         TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
-        if (taskbar == null || taskbar.canToggleHomeAllApps()) {
+        if (taskbar == null) {
             // Home All Apps should be toggled from this class, because the controllers are not
             // initialized when Taskbar is disabled (i.e. TaskbarActivityContext is null).
-            if (mActivity instanceof Launcher l) l.toggleAllAppsSearch();
+            if (mActivity instanceof Launcher l) l.toggleAllApps(true);
         } else {
-            taskbar.toggleAllAppsSearch();
+            taskbar.getControllers().uiController.toggleAllApps(true);
         }
     }
 
@@ -404,25 +548,32 @@
      * Called when the user is unlocked
      */
     public void onUserUnlocked() {
+        debugPrimaryTaskbar("onUserUnlocked");
         mUserUnlocked = true;
-        DisplayController.INSTANCE.get(mWindowContext).addChangeListener(mRecreationListener);
-        recreateTaskbar();
-        addTaskbarRootViewToWindow(getDefaultDisplayId());
+        DisplayController.INSTANCE.get(mPrimaryWindowContext).addChangeListener(
+                mRecreationListener);
+        debugPrimaryTaskbar("onUserUnlocked: recreating all taskbars!");
+        recreateTaskbars();
+        for (int i = 0; i < mTaskbars.size(); i++) {
+            int displayId = mTaskbars.keyAt(i);
+            debugTaskbarManager("onUserUnlocked: addTaskbarRootViewToWindow()", displayId);
+            addTaskbarRootViewToWindow(displayId);
+        }
     }
 
     /**
      * Sets a {@link StatefulActivity} to act as taskbar callback
      */
     public void setActivity(@NonNull StatefulActivity activity) {
+        debugPrimaryTaskbar("setActivity: mActivity=" + mActivity);
         if (mActivity == activity) {
+            debugPrimaryTaskbar("setActivity: No need to set activity!");
             return;
         }
         removeActivityCallbacksAndListeners();
         mActivity = activity;
-        debugWhyTaskbarNotDestroyed("Set mActivity=" + mActivity);
         mActivity.addOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
-        Log.d(TASKBAR_NOT_DESTROYED_TAG,
-                "registering activity lifecycle callbacks from setActivity().");
+        debugPrimaryTaskbar("setActivity: registering activity lifecycle callbacks.");
         mActivity.addEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback);
         UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
                 getUnfoldTransitionProgressProviderForActivity(activity);
@@ -440,6 +591,7 @@
      * Sets the current RecentsViewContainer, from which we create a TaskbarUIController.
      */
     public void setRecentsViewContainer(@NonNull RecentsViewContainer recentsViewContainer) {
+        debugPrimaryTaskbar("setRecentsViewContainer");
         if (mRecentsViewContainer == recentsViewContainer) {
             return;
         }
@@ -463,21 +615,36 @@
      */
     private UnfoldTransitionProgressProvider getUnfoldTransitionProgressProviderForActivity(
             StatefulActivity activity) {
+        debugPrimaryTaskbar("getUnfoldTransitionProgressProviderForActivity");
         if (!enableUnfoldStateAnimation()) {
             if (activity instanceof QuickstepLauncher ql) {
                 return ql.getUnfoldTransitionProgressProvider();
             }
         } else {
-            return SystemUiProxy.INSTANCE.get(mWindowContext).getUnfoldTransitionProvider();
+            return SystemUiProxy.INSTANCE.get(mBaseContext).getUnfoldTransitionProvider();
         }
         return null;
     }
 
+    /** Creates a {@link TaskbarUIController} to use with non default displays. */
+    private TaskbarUIController createTaskbarUIControllerForNonDefaultDisplay(int displayId) {
+        debugPrimaryTaskbar("createTaskbarUIControllerForNonDefaultDisplay");
+        if (RecentsWindowFlags.Companion.getEnableOverviewInWindow()) {
+            RecentsViewContainer rvc = mRecentsDisplayModel.getRecentsWindowManager(displayId);
+            if (rvc != null) {
+                return createTaskbarUIControllerForRecentsViewContainer(rvc);
+            }
+        }
+
+        return new TaskbarUIController();
+    }
+
     /**
      * Creates a {@link TaskbarUIController} to use while the given StatefulActivity is active.
      */
     private TaskbarUIController createTaskbarUIControllerForRecentsViewContainer(
             RecentsViewContainer container) {
+        debugPrimaryTaskbar("createTaskbarUIControllerForRecentsViewContainer");
         if (container instanceof QuickstepLauncher quickstepLauncher) {
             return new LauncherTaskbarUIController(quickstepLauncher);
         }
@@ -494,13 +661,24 @@
 
     /**
      * This method is called multiple times (ex. initial init, then when user unlocks) in which case
-     * we fully want to destroy the existing default display's taskbar and create a new one.
+     * we fully want to destroy existing taskbars and create all desired new ones.
      * In other case (folding/unfolding) we don't need to remove and add window.
      */
     @VisibleForTesting
-    public synchronized void recreateTaskbar() {
-        // TODO: make this recreate all taskbars in map.
-        recreateTaskbarForDisplay(getDefaultDisplayId());
+    public synchronized void recreateTaskbars() {
+        debugPrimaryTaskbar("recreateTaskbars");
+        // Handles initial creation case.
+        if (mTaskbars.size() == 0) {
+            debugTaskbarManager("recreateTaskbars: create primary taskbar", getDefaultDisplayId());
+            recreateTaskbarForDisplay(getDefaultDisplayId(), 0);
+            return;
+        }
+
+        for (int i = 0; i < mTaskbars.size(); i++) {
+            int displayId = mTaskbars.keyAt(i);
+            debugTaskbarManager("recreateTaskbars: create external taskbar", displayId);
+            recreateTaskbarForDisplay(displayId, 0);
+        }
     }
 
     /**
@@ -508,74 +686,103 @@
      * we fully want to destroy an existing taskbar for a specified display and create a new one.
      * In other case (folding/unfolding) we don't need to remove and add window.
      */
-    private void recreateTaskbarForDisplay(int displayId) {
-        Trace.beginSection("recreateTaskbar");
+    private void recreateTaskbarForDisplay(int displayId, int duration) {
+        debugTaskbarManager("recreateTaskbarForDisplay: ", displayId);
+        Trace.beginSection("recreateTaskbarForDisplay");
         try {
-            DeviceProfile dp = mUserUnlocked ?
-                    LauncherAppState.getIDP(mWindowContext).getDeviceProfile(mWindowContext) : null;
+            debugTaskbarManager("recreateTaskbarForDisplay: getting device profile", displayId);
+            // TODO (b/381113004): make this display-specific via getWindowContext()
+            DeviceProfile dp = getDeviceProfile(displayId);
 
             // All Apps action is unrelated to navbar unification, so we only need to check DP.
             final boolean isLargeScreenTaskbar = dp != null && dp.isTaskbarPresent;
             mAllAppsActionManager.setTaskbarPresent(isLargeScreenTaskbar);
-
+            debugTaskbarManager("recreateTaskbarForDisplay: destroying taskbar", displayId);
             destroyTaskbarForDisplay(displayId);
 
+            boolean displayExists = getDisplay(displayId) != null;
             boolean isTaskbarEnabled = dp != null && isTaskbarEnabled(dp);
-            debugWhyTaskbarNotDestroyed("recreateTaskbar: isTaskbarEnabled=" + isTaskbarEnabled
-                + " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
-                + " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
-                + " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent));
-            if (!isTaskbarEnabled || !isLargeScreenTaskbar) {
-                SystemUiProxy.INSTANCE.get(mWindowContext)
-                    .notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
-                if (!isTaskbarEnabled) {
+            debugTaskbarManager("recreateTaskbarForDisplay: isTaskbarEnabled=" + isTaskbarEnabled
+                    + " [dp != null (i.e. mUserUnlocked)]=" + (dp != null)
+                    + " FLAG_HIDE_NAVBAR_WINDOW=" + ENABLE_TASKBAR_NAVBAR_UNIFICATION
+                    + " dp.isTaskbarPresent=" + (dp == null ? "null" : dp.isTaskbarPresent)
+                    + " displayExists=" + displayExists, displayId);
+            if (!isTaskbarEnabled || !isLargeScreenTaskbar || !displayExists) {
+                SystemUiProxy.INSTANCE.get(mBaseContext)
+                        .notifyTaskbarStatus(/* visible */ false, /* stashed */ false);
+                if (!isTaskbarEnabled || !displayExists) {
+                    debugTaskbarManager(
+                            "recreateTaskbarForDisplay: exiting bc (!isTaskbarEnabled || "
+                                    + "!displayExists)",
+                            displayId);
                     return;
                 }
             }
 
             TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
             if (enableTaskbarNoRecreate() || taskbar == null) {
+                debugTaskbarManager("recreateTaskbarForDisplay: creating taskbar", displayId);
                 taskbar = createTaskbarActivityContext(dp, displayId);
+                if (taskbar == null) {
+                    debugTaskbarManager(
+                            "recreateTaskbarForDisplay: new taskbar instance is null!", displayId);
+                    return;
+                }
             } else {
+                debugTaskbarManager("recreateTaskbarForDisplay: updating taskbar device profile",
+                        displayId);
                 taskbar.updateDeviceProfile(dp);
             }
             mSharedState.startTaskbarVariantIsTransient =
                     DisplayController.isTransientTaskbar(taskbar);
             mSharedState.allAppsVisible = mSharedState.allAppsVisible && isLargeScreenTaskbar;
-            taskbar.init(mSharedState);
+            taskbar.init(mSharedState, duration);
 
-            if (mRecentsViewContainer != null) {
+            // Non default displays should not use LauncherTaskbarUIController as they shouldn't
+            // have access to the Launcher activity.
+            if (!isDefaultDisplay(displayId)
+                    && DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()) {
+                taskbar.setUIController(createTaskbarUIControllerForNonDefaultDisplay(displayId));
+            } else if (mRecentsViewContainer != null) {
                 taskbar.setUIController(
                         createTaskbarUIControllerForRecentsViewContainer(mRecentsViewContainer));
             }
 
             if (enableTaskbarNoRecreate()) {
+                debugTaskbarManager("recreateTaskbarForDisplay: adding rootView", displayId);
                 addTaskbarRootViewToWindow(displayId);
                 FrameLayout taskbarRootLayout = getTaskbarRootLayoutForDisplay(displayId);
-                taskbarRootLayout.removeAllViews();
-                taskbarRootLayout.addView(taskbar.getDragLayer());
-                taskbar.notifyUpdateLayoutParams();
+                if (taskbarRootLayout != null) {
+                    debugTaskbarManager("recreateTaskbarForDisplay: adding root layout", displayId);
+                    taskbarRootLayout.removeAllViews();
+                    taskbarRootLayout.addView(taskbar.getDragLayer());
+                    taskbar.notifyUpdateLayoutParams();
+                } else {
+                    debugTaskbarManager("recreateTaskbarForDisplay: taskbarRootLayout is null!",
+                            displayId);
+                }
             }
         } finally {
             Trace.endSection();
         }
     }
 
-    public void onSystemUiFlagsChanged(@SystemUiStateFlags long systemUiStateFlags) {
+    /** Called when the SysUI flags for a given display change. */
+    public void onSystemUiFlagsChanged(@SystemUiStateFlags long systemUiStateFlags, int displayId) {
         if (DEBUG) {
             Log.d(TAG, "SysUI flags changed: " + formatFlagChange(systemUiStateFlags,
                     mSharedState.sysuiStateFlags, QuickStepContract::getSystemUiStateString));
         }
         mSharedState.sysuiStateFlags = systemUiStateFlags;
-        TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+        TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
         if (taskbar != null) {
             taskbar.updateSysuiStateFlags(systemUiStateFlags, false /* fromInit */);
         }
     }
 
     public void onLongPressHomeEnabled(boolean assistantLongPressEnabled) {
-        if (mDefaultNavButtonController != null) {
-            mDefaultNavButtonController.setAssistantLongPressEnabled(assistantLongPressEnabled);
+        if (mPrimaryNavButtonController != null) {
+            mPrimaryNavButtonController.setAssistantLongPressEnabled(assistantLongPressEnabled);
         }
     }
 
@@ -584,15 +791,19 @@
      */
     public void setSetupUIVisible(boolean isVisible) {
         mSharedState.setupUIVisible = isVisible;
+        mAllAppsActionManager.setSetupUiVisible(isVisible);
         TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
         if (taskbar != null) {
             taskbar.setSetupUIVisible(isVisible);
         }
     }
 
-    public void setWallpaperVisible(boolean isVisible) {
+    /**
+     * Sets wallpaper visibility for specific display.
+     */
+    public void setWallpaperVisible(int displayId, boolean isVisible) {
         mSharedState.wallpaperVisible = isVisible;
-        TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+        TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
         if (taskbar != null) {
             taskbar.setWallpaperVisible(isVisible);
         }
@@ -620,7 +831,7 @@
     }
 
     public void transitionTo(int displayId, @BarTransitions.TransitionMode int barMode,
-            boolean animate) {
+                             boolean animate) {
         TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
         if (taskbar != null) {
             taskbar.transitionTo(barMode, animate);
@@ -689,12 +900,122 @@
         }
     }
 
+    /**
+     * Signal from SysUI indicating that a non-mirroring display was just connected to the
+     * primary device or a previously mirroring display is switched to extended mode.
+     */
+    public void onDisplayAddSystemDecorations(int displayId) {
+        debugTaskbarManager("onDisplayAddSystemDecorations: ", displayId);
+        Display display = getDisplay(displayId);
+        if (display == null) {
+            debugTaskbarManager("onDisplayAddSystemDecorations: can't find display!", displayId);
+            return;
+        }
+
+        if (!DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue() || isDefaultDisplay(
+                displayId)) {
+            debugTaskbarManager(
+                    "onDisplayAddSystemDecorations: not an external display! | "
+                            + "ENABLE_TASKBAR_CONNECTED_DISPLAYS="
+                            + DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()
+                            + " isDefaultDisplay=" + isDefaultDisplay(displayId), displayId);
+            return;
+        }
+        debugTaskbarManager("onDisplayAddSystemDecorations: creating new windowContext!",
+                displayId);
+        Context newWindowContext = createWindowContext(displayId);
+        if (newWindowContext != null) {
+            debugTaskbarManager("onDisplayAddSystemDecorations: add new windowContext to map!",
+                    displayId);
+            addWindowContextToMap(displayId, newWindowContext);
+            WindowManager wm = getWindowManager(displayId);
+            if (wm == null || !wm.shouldShowSystemDecors(displayId)) {
+                String wmStatus = wm == null ? "WindowManager is null!" : "WindowManager exists";
+                boolean showDecor = wm != null && wm.shouldShowSystemDecors(displayId);
+                debugTaskbarManager(
+                        "onDisplayAddSystemDecorations:\n\t" + wmStatus + "\n\tshowSystemDecors="
+                                + showDecor, displayId);
+                return;
+            }
+            debugTaskbarManager("onDisplayAddSystemDecorations: creating RootLayout!", displayId);
+
+            createExternalDeviceProfile(displayId);
+
+            debugTaskbarManager("onDisplayAddSystemDecorations: creating RootLayout!", displayId);
+            createTaskbarRootLayout(displayId);
+
+            debugTaskbarManager("onDisplayAddSystemDecorations: creating NavButtonController!",
+                    displayId);
+            createNavButtonController(displayId);
+
+            debugTaskbarManager(
+                    "onDisplayAddSystemDecorations: createAndRegisterComponentCallbacks!",
+                    displayId);
+            createAndRegisterComponentCallbacks(displayId);
+            debugTaskbarManager("onDisplayAddSystemDecorations: recreateTaskbarForDisplay!",
+                    displayId);
+            recreateTaskbarForDisplay(displayId, 0);
+        } else {
+            debugTaskbarManager("onDisplayAddSystemDecorations: newWindowContext is NULL!",
+                    displayId);
+        }
+
+        debugTaskbarManager("onDisplayAddSystemDecorations: finished!", displayId);
+    }
+
+    /**
+     * Signal from SysUI indicating that a previously connected non-mirroring display was just
+     * removed from the primary device.
+     */
+    public void onDisplayRemoved(int displayId) {
+        debugTaskbarManager("onDisplayRemoved: ", displayId);
+        if (!DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue() || isDefaultDisplay(
+                displayId)) {
+            debugTaskbarManager(
+                    "onDisplayRemoved: not an external display! | "
+                            + "ENABLE_TASKBAR_CONNECTED_DISPLAYS="
+                            + DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()
+                            + " isDefaultDisplay=" + isDefaultDisplay(displayId), displayId);
+            return;
+        }
+
+        Context windowContext = getWindowContext(displayId);
+        if (windowContext != null) {
+            debugTaskbarManager("onDisplayRemoved: removing NavButtonController!", displayId);
+            removeNavButtonController(displayId);
+
+            debugTaskbarManager("onDisplayRemoved: removeAndUnregisterComponentCallbacks!",
+                    displayId);
+            removeAndUnregisterComponentCallbacks(displayId);
+
+            debugTaskbarManager("onDisplayRemoved: destroying Taskbar!", displayId);
+            destroyTaskbarForDisplay(displayId);
+
+            debugTaskbarManager("onDisplayRemoved: removing DeviceProfile from map!", displayId);
+            removeDeviceProfileFromMap(displayId);
+
+            debugTaskbarManager("onDisplayRemoved: removing WindowContext from map!", displayId);
+            removeWindowContextFromMap(displayId);
+
+            debugTaskbarManager("onDisplayRemoved: finished!", displayId);
+        } else {
+            debugTaskbarManager("onDisplayRemoved: removing NavButtonController!", displayId);
+        }
+    }
+
+    /**
+     * Signal from SysUI indicating that system decorations should be removed from the display.
+     */
+    public void onDisplayRemoveSystemDecorations(int displayId) {
+        // The display mirroring starts. The handling logic is the same as when removing a
+        // display.
+        onDisplayRemoved(displayId);
+    }
+
     private void removeActivityCallbacksAndListeners() {
         if (mActivity != null) {
             mActivity.removeOnDeviceProfileChangeListener(mDebugActivityDeviceProfileChanged);
-            Log.d(TASKBAR_NOT_DESTROYED_TAG,
-                    "unregistering activity lifecycle callbacks from "
-                            + "removeActivityCallbackAndListeners().");
+            debugPrimaryTaskbar("unregistering activity lifecycle callbacks");
             mActivity.removeEventCallback(EVENT_DESTROYED, mActivityOnDestroyCallback);
             UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
                     getUnfoldTransitionProgressProviderForActivity(mActivity);
@@ -708,54 +1029,136 @@
      * Called when the manager is no longer needed
      */
     public void destroy() {
+        debugPrimaryTaskbar("TaskbarManager#destroy()");
         mRecentsViewContainer = null;
-        debugWhyTaskbarNotDestroyed("TaskbarManager#destroy()");
+        debugPrimaryTaskbar("destroy: removing activity callbacks");
+        DesktopVisibilityController.INSTANCE.get(
+                mPrimaryWindowContext).unregisterTaskbarDesktopModeListener(
+                mTaskbarDesktopModeListener);
         removeActivityCallbacksAndListeners();
-        mTaskbarBroadcastReceiver.unregisterReceiverSafely(mWindowContext);
-        destroyAllTaskbars();
+        mTaskbarBroadcastReceiver.unregisterReceiverSafely();
+        if (mGrowthBroadcastReceiver != null) {
+            mGrowthBroadcastReceiver.unregisterReceiverSafely();
+        }
+
         if (mUserUnlocked) {
-            DisplayController.INSTANCE.get(mWindowContext).removeChangeListener(
+            DisplayController.INSTANCE.get(mPrimaryWindowContext).removeChangeListener(
                     mRecreationListener);
         }
-        SettingsCache.INSTANCE.get(mWindowContext)
+        SettingsCache.INSTANCE.get(mPrimaryWindowContext)
                 .unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
-        SettingsCache.INSTANCE.get(mWindowContext)
+        SettingsCache.INSTANCE.get(mPrimaryWindowContext)
                 .unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
-        Log.d(TASKBAR_NOT_DESTROYED_TAG, "unregistering component callbacks from destroy().");
-        mWindowContext.unregisterComponentCallbacks(mDefaultComponentCallbacks);
-        mShutdownReceiver.unregisterReceiverSafely(mWindowContext);
+        debugPrimaryTaskbar("destroy: unregistering component callbacks");
+        removeAndUnregisterComponentCallbacks(getDefaultDisplayId());
+        mShutdownReceiver.unregisterReceiverSafely();
+        if (ActivityManagerWrapper.usePerceptibleTasks(getPrimaryWindowContext())) {
+            for (Integer taskId : mTaskStackListener.mPerceptibleTasks) {
+                ActivityManagerWrapper.getInstance().setTaskIsPerceptible(taskId, false);
+            }
+        }
+        TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
+        debugPrimaryTaskbar("destroy: destroying all taskbars!");
+        destroyAllTaskbars();
+        debugPrimaryTaskbar("destroy: finished!");
     }
 
     public @Nullable TaskbarActivityContext getCurrentActivityContext() {
-        return getTaskbarForDisplay(mWindowContext.getDisplayId());
+        return getTaskbarForDisplay(getDefaultDisplayId());
     }
 
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarManager:");
-        TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
-        if (taskbar == null) {
-            pw.println(prefix + "\tTaskbarActivityContext: null");
-        } else {
-            taskbar.dumpLogs(prefix + "\t", pw);
+        // iterate through taskbars and do the dump for each
+        for (int i = 0; i < mTaskbars.size(); i++) {
+            int displayId = mTaskbars.keyAt(i);
+            TaskbarActivityContext taskbar = mTaskbars.get(i);
+            pw.println(prefix + "\tTaskbar at display " + displayId + ":");
+            if (taskbar == null) {
+                pw.println(prefix + "\t\tTaskbarActivityContext: null");
+            } else {
+                taskbar.dumpLogs(prefix + "\t\t", pw);
+            }
         }
     }
 
     private void addTaskbarRootViewToWindow(int displayId) {
+        debugTaskbarManager("addTaskbarRootViewToWindow:", displayId);
         TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
-        if (enableTaskbarNoRecreate() && !mAddedWindow && taskbar != null) {
-            mWindowManager.addView(getTaskbarRootLayoutForDisplay(displayId),
-                    taskbar.getWindowLayoutParams());
-            mAddedWindow = true;
+        if (!enableTaskbarNoRecreate() || taskbar == null) {
+            debugTaskbarManager("addTaskbarRootViewToWindow: taskbar null", displayId);
+            return;
+        }
+
+        if (getDisplay(displayId) == null) {
+            debugTaskbarManager("addTaskbarRootViewToWindow: display null", displayId);
+            return;
+        }
+
+        if (!isTaskbarRootLayoutAddedForDisplay(displayId)) {
+            FrameLayout rootLayout = getTaskbarRootLayoutForDisplay(displayId);
+            WindowManager windowManager = getWindowManager(displayId);
+            if (rootLayout != null && windowManager != null) {
+                windowManager.addView(rootLayout, taskbar.getWindowLayoutParams());
+                mAddedRootLayouts.put(displayId, true);
+            } else {
+                String rootLayoutStatus =
+                        (rootLayout == null) ? "rootLayout is NULL!" : "rootLayout exists!";
+                String wmStatus = (windowManager == null) ? "windowManager is NULL!"
+                        : "windowManager exists!";
+                debugTaskbarManager(
+                        "addTaskbarRootViewToWindow: \n\t" + rootLayoutStatus + "\n\t" + wmStatus,
+                        displayId);
+            }
+        } else {
+            debugTaskbarManager("addTaskbarRootViewToWindow: rootLayout already added!", displayId);
         }
     }
 
     private void removeTaskbarRootViewFromWindow(int displayId) {
+        debugTaskbarManager("removeTaskbarRootViewFromWindow", displayId);
         FrameLayout rootLayout = getTaskbarRootLayoutForDisplay(displayId);
-        if (enableTaskbarNoRecreate() && mAddedWindow && rootLayout != null) {
-            mWindowManager.removeViewImmediate(rootLayout);
-            mAddedWindow = false;
-            removeTaskbarRootLayoutFromMap(displayId);
+        if (!enableTaskbarNoRecreate() || rootLayout == null) {
+            return;
         }
+
+        WindowManager windowManager = getWindowManager(displayId);
+        if (isTaskbarRootLayoutAddedForDisplay(displayId) && windowManager != null) {
+            windowManager.removeViewImmediate(rootLayout);
+            mAddedRootLayouts.put(displayId, false);
+            removeTaskbarRootLayoutFromMap(displayId);
+        } else {
+            debugTaskbarManager("removeTaskbarRootViewFromWindow: WindowManager is null",
+                    displayId);
+        }
+    }
+
+    /**
+     * Returns the {@link TaskbarUIController} associated with the given display ID.
+     * TODO(b/395061396): Remove this method when overview in widow is enabled.
+     *
+     * @param displayId The ID of the display to retrieve the taskbar for.
+     * @return The {@link TaskbarUIController} for the specified display, or
+     * {@code null} if no taskbar is associated with that display.
+     */
+    @Nullable
+    public TaskbarUIController getUIControllerForDisplay(int displayId) {
+        if (!mTaskbars.contains(displayId)) {
+            return null;
+        }
+
+        return getTaskbarForDisplay(displayId).getControllers().uiController;
+    }
+
+    /**
+     * Retrieves whether RootLayout was added to window for specific display, or false if no
+     * such mapping has been made.
+     *
+     * @param displayId The ID of the display for which to retrieve the taskbar root layout.
+     * @return if RootLayout was added to window {@link Boolean} for a display or {@code false}.
+     */
+    private boolean isTaskbarRootLayoutAddedForDisplay(int displayId) {
+        return mAddedRootLayouts.get(displayId);
     }
 
     /**
@@ -763,7 +1166,7 @@
      *
      * @param displayId The ID of the display to retrieve the taskbar for.
      * @return The {@link TaskbarActivityContext} for the specified display, or
-     *         {@code null} if no taskbar is associated with that display.
+     * {@code null} if no taskbar is associated with that display.
      */
     private TaskbarActivityContext getTaskbarForDisplay(int displayId) {
         return mTaskbars.get(displayId);
@@ -772,21 +1175,236 @@
 
     /**
      * Creates a {@link TaskbarActivityContext} for the given display and adds it to the map.
+     *
+     * @param dp        The {@link DeviceProfile} for the display.
+     * @param displayId The ID of the display.
      */
-    private TaskbarActivityContext createTaskbarActivityContext(DeviceProfile dp, int displayId) {
-        TaskbarActivityContext newTaskbar = new TaskbarActivityContext(mWindowContext,
-                mNavigationBarPanelContext, dp, mDefaultNavButtonController,
-                mUnfoldProgressProvider, mDesktopVisibilityController);
+    private @Nullable TaskbarActivityContext createTaskbarActivityContext(DeviceProfile dp,
+            int displayId) {
+        Display display = getDisplay(displayId);
+        if (display == null) {
+            debugTaskbarManager("createTaskbarActivityContext: display null", displayId);
+            return null;
+        }
+
+        Context navigationBarPanelContext = null;
+        if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
+            navigationBarPanelContext = mBaseContext.createWindowContext(display,
+                    TYPE_NAVIGATION_BAR_PANEL, null);
+        }
+
+        boolean isPrimaryDisplay = isDefaultDisplay(displayId)
+                || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue();
+
+        TaskbarActivityContext newTaskbar = new TaskbarActivityContext(getWindowContext(displayId),
+                navigationBarPanelContext, dp, getNavButtonController(displayId),
+                mUnfoldProgressProvider, isPrimaryDisplay,
+                SystemUiProxy.INSTANCE.get(mBaseContext));
 
         addTaskbarToMap(displayId, newTaskbar);
         return newTaskbar;
     }
 
     /**
+     * Creates a {@link DeviceProfile} for the given display and adds it to the map.
+     * @param displayId The ID of the display.
+     */
+    private void createExternalDeviceProfile(int displayId) {
+        if (!mUserUnlocked) {
+            return;
+        }
+
+        InvariantDeviceProfile idp = LauncherAppState.getIDP(mPrimaryWindowContext);
+        if (idp == null) {
+            return;
+        }
+
+        Context displayContext = getWindowContext(displayId);
+        if (displayContext == null) {
+            return;
+        }
+
+        DeviceProfile externalDeviceProfile = idp.createDeviceProfileForSecondaryDisplay(
+                displayContext);
+        mExternalDeviceProfiles.put(displayId, externalDeviceProfile);
+    }
+
+    /**
+     * Gets a {@link DeviceProfile} for the given displayId.
+     * @param displayId The ID of the display.
+     */
+    private @Nullable DeviceProfile getDeviceProfile(int displayId) {
+        if (!mUserUnlocked) {
+            return null;
+        }
+
+        InvariantDeviceProfile idp = LauncherAppState.getIDP(mPrimaryWindowContext);
+        if (idp == null) {
+            return null;
+        }
+
+        boolean isPrimary = isDefaultDisplay(displayId)
+                || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue();
+        if (isPrimary) {
+            return idp.getDeviceProfile(mPrimaryWindowContext);
+        }
+
+        return mExternalDeviceProfiles.get(displayId);
+    }
+
+    /**
+     * Removes the {@link DeviceProfile} associated with the given display ID from the map.
+     * @param displayId The ID of the display for which to remove the taskbar.
+     */
+    private void removeDeviceProfileFromMap(int displayId) {
+        mExternalDeviceProfiles.delete(displayId);
+    }
+
+    /**
+     * Create {@link ComponentCallbacks} for the given display and register it to the relevant
+     * WindowContext. For external displays, populate maps.
+     *
+     * @param displayId The ID of the display.
+     */
+    private void createAndRegisterComponentCallbacks(int displayId) {
+        debugTaskbarManager("createAndRegisterComponentCallbacks", displayId);
+        ComponentCallbacks callbacks = new ComponentCallbacks() {
+            private Configuration mOldConfig =
+                    getWindowContext(displayId).getResources().getConfiguration();
+
+            @Override
+            public void onConfigurationChanged(Configuration newConfig) {
+                Trace.instantForTrack(Trace.TRACE_TAG_APP, "TaskbarManager",
+                        "onConfigurationChanged: " + newConfig);
+                debugTaskbarManager("onConfigurationChanged: " + newConfig, displayId);
+
+                DeviceProfile dp = getDeviceProfile(displayId);
+                int configDiff = mOldConfig.diff(newConfig) & ~SKIP_RECREATE_CONFIG_CHANGES;
+
+                if ((configDiff & ActivityInfo.CONFIG_UI_MODE) != 0) {
+                    debugTaskbarManager("onConfigurationChanged: theme changed", displayId);
+                    // Only recreate for theme changes, not other UI mode changes such as docking.
+                    int oldUiNightMode = (mOldConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
+                    int newUiNightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK);
+                    if (oldUiNightMode == newUiNightMode) {
+                        configDiff &= ~ActivityInfo.CONFIG_UI_MODE;
+                    }
+                }
+
+                debugTaskbarManager("onConfigurationChanged: | configDiff="
+                        + Configuration.configurationDiffToString(configDiff), displayId);
+                if (configDiff != 0 || getCurrentActivityContext() == null) {
+                    debugTaskbarManager("onConfigurationChanged: call recreateTaskbars", displayId);
+                    recreateTaskbars();
+                } else if (dp != null) {
+                    // Config change might be handled without re-creating the taskbar
+                    if (!isTaskbarEnabled(dp)) {
+                        debugPrimaryTaskbar(
+                                "onConfigurationChanged: isTaskbarEnabled(dp)=False | "
+                                        + "destroyTaskbarForDisplay");
+                        destroyTaskbarForDisplay(getDefaultDisplayId());
+                    } else {
+                        debugPrimaryTaskbar("onConfigurationChanged: isTaskbarEnabled(dp)=True");
+                        if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
+                            // Re-initialize for screen size change? Should this be done
+                            // by looking at screen-size change flag in configDiff in the
+                            // block above?
+                            debugPrimaryTaskbar("onConfigurationChanged: call recreateTaskbars");
+                            recreateTaskbars();
+                        } else {
+                            debugPrimaryTaskbar(
+                                    "onConfigurationChanged: updateDeviceProfile for current "
+                                            + "taskbar.");
+                            getCurrentActivityContext().updateDeviceProfile(dp);
+                        }
+                    }
+                } else {
+
+                    getCurrentActivityContext().onConfigurationChanged(configDiff);
+                }
+                mOldConfig = new Configuration(newConfig);
+                // reset taskbar was pinned value, so we don't automatically unstash taskbar upon
+                // user unfolding the device.
+                mSharedState.setTaskbarWasPinned(false);
+            }
+
+            @Override
+            public void onLowMemory() {
+            }
+        };
+        if (isDefaultDisplay(displayId)
+                || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()) {
+            mPrimaryComponentCallbacks = callbacks;
+            mPrimaryWindowContext.registerComponentCallbacks(callbacks);
+        } else {
+            mComponentCallbacks.put(displayId, callbacks);
+            getWindowContext(displayId).registerComponentCallbacks(callbacks);
+        }
+    }
+
+    /**
+     * Unregister {@link ComponentCallbacks} for the given display from its WindowContext. For
+     * external displays, remove from the map.
+     *
+     * @param displayId The ID of the display.
+     */
+    private void removeAndUnregisterComponentCallbacks(int displayId) {
+        if (isDefaultDisplay(displayId)
+                || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()) {
+            mPrimaryWindowContext.unregisterComponentCallbacks(mPrimaryComponentCallbacks);
+        } else {
+            ComponentCallbacks callbacks = mComponentCallbacks.get(displayId);
+            getWindowContext(displayId).unregisterComponentCallbacks(callbacks);
+            mComponentCallbacks.delete(displayId);
+        }
+    }
+
+    /**
+     * Creates a {@link TaskbarNavButtonController} for the given display and adds it to the map
+     * if it doesn't already exist.
+     *
+     * @param displayId The ID of the display
+     */
+    private void createNavButtonController(int displayId) {
+        if (isDefaultDisplay(displayId)
+                || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()) {
+            mPrimaryNavButtonController = new TaskbarNavButtonController(
+                    mPrimaryWindowContext,
+                    mNavCallbacks,
+                    SystemUiProxy.INSTANCE.get(mBaseContext),
+                    new Handler(),
+                    new ContextualSearchInvoker(mBaseContext));
+        } else {
+            TaskbarNavButtonController navButtonController = new TaskbarNavButtonController(
+                    getWindowContext(displayId),
+                    mNavCallbacks,
+                    SystemUiProxy.INSTANCE.get(mBaseContext),
+                    new Handler(),
+                    new ContextualSearchInvoker(mBaseContext));
+            mNavButtonControllers.put(displayId, navButtonController);
+        }
+    }
+
+    private TaskbarNavButtonController getNavButtonController(int displayId) {
+        return (isDefaultDisplay(displayId)
+                || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue())
+                ? mPrimaryNavButtonController : mNavButtonControllers.get(displayId);
+    }
+
+    private void removeNavButtonController(int displayId) {
+        if (isDefaultDisplay(displayId)
+                || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()) {
+            mPrimaryNavButtonController = null;
+        } else {
+            mNavButtonControllers.delete(displayId);
+        }
+    }
+
+    /**
      * Adds the {@link TaskbarActivityContext} associated with the given display ID to taskbar
      * map if there is not already a taskbar mapped to that displayId.
      *
-     * @param displayId The ID of the display to retrieve the taskbar for.
+     * @param displayId  The ID of the display to retrieve the taskbar for.
      * @param newTaskbar The new {@link TaskbarActivityContext} to add to the map.
      */
     private void addTaskbarToMap(int displayId, TaskbarActivityContext newTaskbar) {
@@ -806,24 +1424,37 @@
 
     /**
      * Creates {@link FrameLayout} for the taskbar on the specified display and adds it to map.
+     *
      * @param displayId The ID of the display for which to create the taskbar root layout.
      */
     private void createTaskbarRootLayout(int displayId) {
-        FrameLayout newTaskbarRootLayout = new FrameLayout(mWindowContext) {
+        debugTaskbarManager("createTaskbarRootLayout: ", displayId);
+        if (!enableTaskbarNoRecreate()) {
+            return;
+        }
+
+        FrameLayout newTaskbarRootLayout = new FrameLayout(getWindowContext(displayId)) {
             @Override
             public boolean dispatchTouchEvent(MotionEvent ev) {
+                debugTaskbarManager("dispatchTouchEvent: ", displayId);
                 // The motion events can be outside the view bounds of task bar, and hence
                 // manually dispatching them to the drag layer here.
-                TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
+                TaskbarActivityContext taskbar = getTaskbarForDisplay(displayId);
                 if (taskbar != null && taskbar.getDragLayer().isAttachedToWindow()) {
                     return taskbar.getDragLayer().dispatchTouchEvent(ev);
                 }
                 return super.dispatchTouchEvent(ev);
             }
         };
+
+        debugTaskbarManager("createTaskbarRootLayout: adding to map", displayId);
         addTaskbarRootLayoutToMap(displayId, newTaskbarRootLayout);
     }
 
+    private boolean isDefaultDisplay(int displayId) {
+        return displayId == getDefaultDisplayId();
+    }
+
     /**
      * Retrieves the root layout of the taskbar for the specified display.
      *
@@ -831,19 +1462,31 @@
      * @return The taskbar root layout {@link FrameLayout} for a given display or {@code null}.
      */
     private FrameLayout getTaskbarRootLayoutForDisplay(int displayId) {
-        return mRootLayouts.get(displayId);
+        debugTaskbarManager("getTaskbarRootLayoutForDisplay:", displayId);
+        FrameLayout frameLayout = mRootLayouts.get(displayId);
+        if (frameLayout != null) {
+            return frameLayout;
+        } else {
+            debugTaskbarManager("getTaskbarRootLayoutForDisplay: rootLayout is null!", displayId);
+            return null;
+        }
     }
 
     /**
      * Adds the taskbar root layout {@link FrameLayout} to taskbar map, mapped to display ID.
      *
-     * @param displayId The ID of the display to associate with the taskbar root layout.
+     * @param displayId  The ID of the display to associate with the taskbar root layout.
      * @param rootLayout The taskbar root layout {@link FrameLayout} to add to the map.
      */
     private void addTaskbarRootLayoutToMap(int displayId, FrameLayout rootLayout) {
+        debugTaskbarManager("addTaskbarRootLayoutToMap: ", displayId);
         if (!mRootLayouts.contains(displayId) && rootLayout != null) {
             mRootLayouts.put(displayId, rootLayout);
         }
+
+        debugTaskbarManager(
+                "addTaskbarRootLayoutToMap: finished! mRootLayouts.size()=" + mRootLayouts.size(),
+                displayId);
     }
 
     /**
@@ -852,27 +1495,189 @@
      * @param displayId The ID of the display for which to remove the taskbar root layout.
      */
     private void removeTaskbarRootLayoutFromMap(int displayId) {
+        debugTaskbarManager("removeTaskbarRootLayoutFromMap:", displayId);
         if (mRootLayouts.contains(displayId)) {
+            mAddedRootLayouts.delete(displayId);
             mRootLayouts.delete(displayId);
         }
+
+        debugTaskbarManager("removeTaskbarRootLayoutFromMap: finished! mRootLayouts.size="
+                + mRootLayouts.size(), displayId);
+    }
+
+    /**
+     * Creates {@link Context} for the taskbar on the specified display.
+     *
+     * @param displayId The ID of the display for which to create the window context.
+     */
+    private @Nullable Context createWindowContext(int displayId) {
+        debugTaskbarManager("createWindowContext: ", displayId);
+        Display display = getDisplay(displayId);
+        if (display == null) {
+            debugTaskbarManager("createWindowContext: display null!", displayId);
+            return null;
+        }
+
+        int windowType = TYPE_NAVIGATION_BAR_PANEL;
+        if (ENABLE_TASKBAR_NAVBAR_UNIFICATION && isDefaultDisplay(displayId)) {
+            windowType = TYPE_NAVIGATION_BAR;
+        }
+        debugTaskbarManager(
+                "createWindowContext: windowType=" + ((windowType == TYPE_NAVIGATION_BAR)
+                        ? "TYPE_NAVIGATION_BAR" : "TYPE_NAVIGATION_BAR_PANEL"), displayId);
+
+        return mBaseContext.createWindowContext(display, windowType, null);
+    }
+
+    private @Nullable Display getDisplay(int displayId) {
+        DisplayManager displayManager = mBaseContext.getSystemService(DisplayManager.class);
+        if (displayManager == null) {
+            debugTaskbarManager("cannot get DisplayManager", displayId);
+            return null;
+        }
+
+        Display display = displayManager.getDisplay(displayId);
+        if (display == null) {
+            debugTaskbarManager("Cannot get display!", displayId);
+            return null;
+        }
+
+        return displayManager.getDisplay(displayId);
+    }
+
+    /**
+     * Retrieves the window context of the taskbar for the specified display.
+     *
+     * @param displayId The ID of the display for which to retrieve the window context.
+     * @return The Window Context {@link Context} for a given display or {@code null}.
+     */
+    private Context getWindowContext(int displayId) {
+        return (isDefaultDisplay(displayId)
+                || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue())
+                ? mPrimaryWindowContext : mWindowContexts.get(displayId);
+    }
+
+    @VisibleForTesting
+    public Context getPrimaryWindowContext() {
+        return mPrimaryWindowContext;
+    }
+
+    /**
+     * Retrieves the window manager {@link WindowManager} of the taskbar for the specified display.
+     *
+     * @param displayId The ID of the display for which to retrieve the window manager.
+     * @return The window manager {@link WindowManager} for a given display or {@code null}.
+     */
+    private @Nullable WindowManager getWindowManager(int displayId) {
+        if (isDefaultDisplay(displayId)
+                || !DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue()) {
+            debugTaskbarManager("cannot get mPrimaryWindowManager", displayId);
+            return mPrimaryWindowManager;
+        }
+
+        Context externalDisplayContext = getWindowContext(displayId);
+        if (externalDisplayContext == null) {
+            debugTaskbarManager("cannot get externalDisplayContext", displayId);
+            return null;
+        }
+
+        return externalDisplayContext.getSystemService(WindowManager.class);
+    }
+
+    /**
+     * Adds the window context {@link Context} to taskbar map, mapped to display ID.
+     *
+     * @param displayId     The ID of the display to associate with the taskbar root layout.
+     * @param windowContext The window context {@link Context} to add to the map.
+     */
+    private void addWindowContextToMap(int displayId, @NonNull Context windowContext) {
+        if (!mWindowContexts.contains(displayId)) {
+            mWindowContexts.put(displayId, windowContext);
+        }
+    }
+
+    /**
+     * Removes the window context {@link Context} for given display ID from the taskbar map.
+     *
+     * @param displayId The ID of the display for which to remove the taskbar root layout.
+     */
+    private void removeWindowContextFromMap(int displayId) {
+        if (mWindowContexts.contains(displayId)) {
+            mWindowContexts.delete(displayId);
+        }
     }
 
     private int getDefaultDisplayId() {
-        return mWindowContext.getDisplayId();
+        return mBaseContext.getDisplayId();
     }
 
-    /** Temp logs for b/254119092. */
-    public void debugWhyTaskbarNotDestroyed(String debugReason) {
+    /**
+     * Logs debug information about the TaskbarManager for primary display.
+     * @param debugReason A string describing the reason for the debug log.
+     * @param displayId The ID of the display for which to log debug information.
+     */
+    public void debugTaskbarManager(String debugReason, int displayId) {
         StringJoiner log = new StringJoiner("\n");
-        log.add(debugReason);
+        log.add(debugReason + " displayId=" + displayId + " isDefaultDisplay=" + isDefaultDisplay(
+                displayId));
+        Log.d(TAG, log.toString());
+    }
 
+    /**
+     * Logs verbose debug information about the TaskbarManager for primary display.
+     * @param debugReason A string describing the reason for the debug log.
+     * @param displayId The ID of the display for which to log debug information.
+     * @param verbose Indicates whether or not to debug with detail.
+     */
+    public void debugTaskbarManager(String debugReason, int displayId, boolean verbose) {
+        StringJoiner log = new StringJoiner("\n");
+        log.add(debugReason + " displayId=" + displayId + " isDefaultDisplay=" + isDefaultDisplay(
+                displayId));
+        if (verbose) {
+            generateVerboseLogs(log, displayId);
+        }
+        Log.d(TAG, log.toString());
+    }
+
+    /**
+     * Logs debug information about the TaskbarManager for primary display.
+     * @param debugReason A string describing the reason for the debug log.
+     *
+     */
+    public void debugPrimaryTaskbar(String debugReason) {
+        debugTaskbarManager(debugReason, getDefaultDisplayId(), false);
+    }
+
+    /**
+     * Logs debug information about the TaskbarManager for primary display.
+     * @param debugReason A string describing the reason for the debug log.
+     *
+     */
+    public void debugPrimaryTaskbar(String debugReason, boolean verbose) {
+        debugTaskbarManager(debugReason, getDefaultDisplayId(), verbose);
+    }
+
+    /**
+     * Logs verbose debug information about the TaskbarManager for a specific display.
+     */
+    private void generateVerboseLogs(StringJoiner log, int displayId) {
         boolean activityTaskbarPresent = mActivity != null
                 && mActivity.getDeviceProfile().isTaskbarPresent;
-        boolean contextTaskbarPresent = mUserUnlocked && LauncherAppState.getIDP(mWindowContext)
-                .getDeviceProfile(mWindowContext).isTaskbarPresent;
+        // TODO (b/381113004): make this display-specific via getWindowContext()
+        Context windowContext = mPrimaryWindowContext;
+        if (windowContext == null) {
+            log.add("windowContext is null!");
+            return;
+        }
+
+        boolean contextTaskbarPresent = false;
+        if (mUserUnlocked) {
+            DeviceProfile dp = getDeviceProfile(displayId);
+            contextTaskbarPresent = dp != null && dp.isTaskbarPresent;
+        }
         if (activityTaskbarPresent == contextTaskbarPresent) {
             log.add("mActivity and mWindowContext agree taskbarIsPresent=" + contextTaskbarPresent);
-            Log.d(TASKBAR_NOT_DESTROYED_TAG, log.toString());
+            Log.d(TAG, log.toString());
             return;
         }
 
@@ -886,25 +1691,19 @@
             log.add("\t\tmActivity.getDeviceProfile().isTaskbarPresent="
                     + activityTaskbarPresent);
         }
-        log.add("\tmWindowContext logs:");
-        log.add("\t\tmWindowContext=" + mWindowContext);
-        log.add("\t\tmWindowContext.getResources().getConfiguration()="
-                + mWindowContext.getResources().getConfiguration());
+        log.add("\tWindowContext logs:");
+        log.add("\t\tWindowContext=" + windowContext);
+        log.add("\t\tWindowContext.getResources().getConfiguration()="
+                + windowContext.getResources().getConfiguration());
         if (mUserUnlocked) {
-            log.add("\t\tLauncherAppState.getIDP().getDeviceProfile(mWindowContext)"
-                    + ".isTaskbarPresent=" + contextTaskbarPresent);
+            log.add("\t\tgetDeviceProfile(mPrimaryWindowContext).isTaskbarPresent="
+                    + contextTaskbarPresent);
         } else {
             log.add("\t\tCouldn't get DeviceProfile because !mUserUnlocked");
         }
-
-        Log.d(TASKBAR_NOT_DESTROYED_TAG, log.toString());
     }
 
     private final DeviceProfile.OnDeviceProfileChangeListener mDebugActivityDeviceProfileChanged =
-            dp -> debugWhyTaskbarNotDestroyed("mActivity onDeviceProfileChanged");
+            dp -> debugPrimaryTaskbar("mActivity onDeviceProfileChanged", true);
 
-    @VisibleForTesting
-    public Context getWindowContext() {
-        return mWindowContext;
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index f905c5f..15c7a8d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -18,6 +18,7 @@
 import android.util.SparseArray;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -25,7 +26,6 @@
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
@@ -39,9 +39,9 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.function.Predicate;
 
 /**
@@ -62,6 +62,7 @@
     // Used to defer any UI updates during the SUW unstash animation.
     private boolean mDeferUpdatesForSUW;
     private Runnable mDeferredUpdates;
+    private boolean mBindingItems = false;
 
     public TaskbarModelCallbacks(
             TaskbarActivityContext context, TaskbarView container) {
@@ -75,14 +76,14 @@
 
     @Override
     public void startBinding() {
-        mContext.setBindingItems(true);
+        mBindingItems = true;
         mHotseatItems.clear();
         mPredictedItems = Collections.emptyList();
     }
 
     @Override
     public void finishBindingItems(IntSet pagesBoundFirst) {
-        mContext.setBindingItems(false);
+        mBindingItems = false;
         commitItemsToUI();
     }
 
@@ -114,26 +115,21 @@
         return modified;
     }
 
-
     @Override
-    public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
-        updateWorkspaceItems(updated, mContext);
+    public void bindItemsUpdated(Set<ItemInfo> updates) {
+        updateContainerItems(updates, mContext);
     }
 
     @Override
-    public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
-        updateRestoreItems(updates, mContext);
-    }
-
-    @Override
-    public void mapOverItems(ItemOperator op) {
+    public View mapOverItems(@NonNull ItemOperator op) {
         final int itemCount = mContainer.getChildCount();
         for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
             View item = mContainer.getChildAt(itemIdx);
             if (item.getTag() instanceof ItemInfo itemInfo && op.evaluate(itemInfo, item)) {
-                return;
+                return item;
             }
         }
+        return null;
     }
 
     @Override
@@ -174,7 +170,7 @@
     }
 
     private void commitItemsToUI() {
-        if (mContext.isBindingItems()) {
+        if (mBindingItems) {
             return;
         }
 
@@ -210,6 +206,7 @@
             ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
         mContainer.updateItems(hotseatItemInfos, recentTasks);
         mControllers.taskbarViewController.updateIconViewsRunningStates();
+        mControllers.taskbarPopupController.setHotseatInfosList(mHotseatItems);
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
index d1f9be0..1adb2e9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java
@@ -51,7 +51,6 @@
 import androidx.annotation.StringRes;
 
 import com.android.launcher3.R;
-import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
@@ -120,7 +119,6 @@
     private final Context mContext;
     private final TaskbarNavButtonCallbacks mCallbacks;
     private final SystemUiProxy mSystemUiProxy;
-    private final ContextualEduStatsManager mContextualEduStatsManager;
     private final Handler mHandler;
     private final ContextualSearchInvoker mContextualSearchInvoker;
     @Nullable private StatsLogManager mStatsLogManager;
@@ -131,13 +129,11 @@
             Context context,
             TaskbarNavButtonCallbacks callbacks,
             SystemUiProxy systemUiProxy,
-            ContextualEduStatsManager contextualEduStatsManager,
             Handler handler,
             ContextualSearchInvoker contextualSearchInvoker) {
         mContext = context;
         mCallbacks = callbacks;
         mSystemUiProxy = systemUiProxy;
-        mContextualEduStatsManager = contextualEduStatsManager;
         mHandler = handler;
         mContextualSearchInvoker = contextualSearchInvoker;
     }
@@ -159,13 +155,13 @@
                 break;
             case BUTTON_HOME:
                 logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_TAP);
-                mContextualEduStatsManager.updateEduStats(/* isTrackpadGesture= */ false,
+                mSystemUiProxy.updateContextualEduStats(/* isTrackpadGesture= */ false,
                         GestureType.HOME);
                 navigateHome();
                 break;
             case BUTTON_RECENTS:
                 logEvent(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP);
-                mContextualEduStatsManager.updateEduStats(/* isTrackpadGesture= */ false,
+                mSystemUiProxy.updateContextualEduStats(/* isTrackpadGesture= */ false,
                         GestureType.OVERVIEW);
                 navigateToOverview();
                 break;
@@ -364,7 +360,7 @@
     private void executeBack(@Nullable KeyEvent keyEvent) {
         if (keyEvent == null || (keyEvent.getAction() == ACTION_UP && !keyEvent.isCanceled())) {
             logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_TAP);
-            mContextualEduStatsManager.updateEduStats(/* isTrackpadGesture= */ false,
+            mSystemUiProxy.updateContextualEduStats(/* isTrackpadGesture= */ false,
                     GestureType.BACK);
         }
         mSystemUiProxy.onBackEvent(keyEvent);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
index 8775766..d909d19 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarOverflowView.java
@@ -35,10 +35,10 @@
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
 import androidx.core.graphics.ColorUtils;
 
 import com.android.app.animation.Interpolators;
-import com.android.launcher3.R;
 import com.android.launcher3.Reorderable;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.IconNormalizer;
@@ -165,7 +165,6 @@
     private boolean mIsRtlLayout;
     private final List<Task> mItems = new ArrayList<Task>();
     private int mIconSize;
-    private int mPadding;
     private Paint mItemBackgroundPaint;
     private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
     private float mScaleForReorderBounce = 1f;
@@ -214,25 +213,25 @@
         TaskbarOverflowView icon = (TaskbarOverflowView) inflater.inflate(resId, group, false);
 
         icon.mIconSize = iconSize;
-        icon.mPadding = padding;
 
         final float taskbarIconRadius =
-                iconSize * IconNormalizer.ICON_VISIBLE_AREA_FACTOR / 2f - padding;
+                (iconSize - padding * 2f) * IconNormalizer.ICON_VISIBLE_AREA_FACTOR / 2f;
 
         icon.mLeaveBehindSizeDefault = taskbarIconRadius;  // 1/2 of taskbar app icon size
         icon.mLeaveBehindSizeScaledDown =
                 icon.mLeaveBehindSizeDefault * LEAVE_BEHIND_SIZE_SCALE_DOWN_MULTIPLIER;
         icon.mLeaveBehindSize = icon.mLeaveBehindSizeScaledDown;
 
-        icon.mItemIconStrokeWidthDefault = taskbarIconRadius / 5f;  // 1/10 of taskbar app icon size
+        icon.mItemIconStrokeWidthDefault =
+                taskbarIconRadius / 10f;  // 1/20 of taskbar app icon size
         icon.mItemIconStrokeWidth = icon.mItemIconStrokeWidthDefault;
 
-        icon.mItemIconSizeDefault = 2 * (taskbarIconRadius - icon.mItemIconStrokeWidthDefault)
-                * TWO_ITEM_ICONS_BOX_ASPECT_RATIO;
+        icon.mItemIconSizeDefault = 2f * taskbarIconRadius * TWO_ITEM_ICONS_BOX_ASPECT_RATIO;
         icon.mItemIconSizeScaledDown = icon.mLeaveBehindSizeScaledDown;
         icon.mItemIconSize = icon.mItemIconSizeDefault;
 
-        icon.mItemIconCenterOffsetDefault = taskbarIconRadius - icon.mItemIconSizeDefault / 2f
+        icon.mItemIconCenterOffsetDefault = taskbarIconRadius
+                - icon.mItemIconSizeDefault * IconNormalizer.ICON_VISIBLE_AREA_FACTOR / 2f
                 - icon.mItemIconStrokeWidthDefault;
         icon.mItemIconCenterOffset = icon.mItemIconCenterOffsetDefault;
 
@@ -242,7 +241,8 @@
     private void init() {
         mIsRtlLayout = Utilities.isRtl(getResources());
         mItemBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        mItemBackgroundColor = getContext().getColor(R.color.taskbar_background);
+        mItemBackgroundColor = getContext().getColor(
+                com.android.internal.R.color.materialColorInverseOnSurface);
         mLeaveBehindColor = Themes.getAttrColor(getContext(), android.R.attr.textColorTertiary);
 
         setWillNotDraw(false);
@@ -258,8 +258,9 @@
 
     private void drawAppIcons(@NonNull Canvas canvas) {
         mItemBackgroundPaint.setColor(mItemBackgroundColor);
-        float radius = mIconSize / 2f - mPadding;
+        float canvasCenterXY = mIconSize / 2f;
         int adjustedItemIconSize = Math.round(mItemIconSize);
+        float itemIconRadius = adjustedItemIconSize / 2f;
 
         int itemsToShow = Math.min(mItems.size(), MAX_ITEMS_IN_PREVIEW);
         for (int i = itemsToShow - 1; i >= 0; --i) {
@@ -278,12 +279,12 @@
                     BlendMode.SRC_ATOP));
 
             canvas.save();
-            float itemIconRadius = adjustedItemIconSize / 2f;
             canvas.translate(
-                    mPadding + itemCenterX + radius - itemIconRadius,
-                    mPadding + itemCenterY + radius - itemIconRadius);
+                    canvasCenterXY + itemCenterX - itemIconRadius,
+                    canvasCenterXY + itemCenterY - itemIconRadius);
             canvas.drawCircle(itemIconRadius, itemIconRadius,
-                    itemIconRadius + mItemIconStrokeWidth, mItemBackgroundPaint);
+                    itemIconRadius * IconNormalizer.ICON_VISIBLE_AREA_FACTOR + mItemIconStrokeWidth,
+                    mItemBackgroundPaint);
             iconCopy.draw(canvas);
             canvas.restore();
         }
@@ -315,6 +316,11 @@
         invalidate();
     }
 
+    @VisibleForTesting
+    public List<Integer> getItemIds() {
+        return mItems.stream().map(task -> task.key.id).toList();
+    }
+
     /**
      * Called when a task is updated. If the task is contained within the view, it's cached value
      * gets updated. If the task is shown within the icon, invalidates the view, so the task icon
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
index bcfc718..7141bb8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
@@ -57,7 +57,11 @@
                     return
                 }
                 val shouldPinTaskbar =
-                    if (controllers.taskbarDesktopModeController.areDesktopTasksVisible) {
+                    if (
+                        controllers.taskbarDesktopModeController.isInDesktopModeAndNotInOverview(
+                            context.displayId
+                        )
+                    ) {
                         !launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)
                     } else {
                         !launcherPrefs.get(TASKBAR_PINNING)
@@ -137,7 +141,11 @@
     @VisibleForTesting
     fun recreateTaskbarAndUpdatePinningValue() {
         updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(false)
-        if (controllers.taskbarDesktopModeController.areDesktopTasksVisible) {
+        if (
+            controllers.taskbarDesktopModeController.isInDesktopModeAndNotInOverview(
+                context.displayId
+            )
+        ) {
             launcherPrefs.put(
                 TASKBAR_PINNING_IN_DESKTOP_MODE,
                 !launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE),
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index abf35a2..1a6cd60 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.taskbar;
 
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
 import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
 import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
 
@@ -23,10 +24,12 @@
 import android.content.pm.LauncherApps;
 import android.graphics.Point;
 import android.util.Pair;
+import android.util.SparseArray;
 import android.view.MotionEvent;
 import android.view.View;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.internal.logging.InstanceId;
 import com.android.launcher3.AbstractFloatingView;
@@ -34,11 +37,7 @@
 import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
-import com.android.launcher3.dot.FolderDotInfo;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.notification.NotificationListener;
@@ -48,13 +47,13 @@
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.splitscreen.SplitShortcut;
 import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.LauncherBindableItemsContainer;
-import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.ShortcutUtil;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.LogUtils;
+import com.android.quickstep.util.SingleTask;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 
 import java.io.PrintWriter;
@@ -63,7 +62,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Objects;
-import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -86,10 +84,14 @@
     private TaskbarControllers mControllers;
     private boolean mAllowInitialSplitSelection;
     private AppInfo[] mAppInfosList;
+    // Saves the ItemInfos in the hotseat without the predicted items.
+    private SparseArray<ItemInfo> mHotseatInfosList;
+    private ManageWindowsTaskbarShortcut<BaseTaskbarContext> mManageWindowsTaskbarShortcut;
+
 
     public TaskbarPopupController(TaskbarActivityContext context) {
         mContext = context;
-        mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
+        mPopupDataProvider = new PopupDataProvider(mContext);
     }
 
     public void init(TaskbarControllers controllers) {
@@ -111,41 +113,21 @@
         mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
     }
 
-    public void setAllowInitialSplitSelection(boolean allowInitialSplitSelection) {
-        mAllowInitialSplitSelection = allowInitialSplitSelection;
+    /** Closes the multi-instance menu if it is enabled and currently open. */
+    public void maybeCloseMultiInstanceMenu() {
+        if (Flags.enableMultiInstanceMenuTaskbar() && mManageWindowsTaskbarShortcut != null) {
+            mManageWindowsTaskbarShortcut.closeMultiInstanceMenu();
+            cleanUpMultiInstanceMenuReference();
+        }
     }
 
-    private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
-        final PackageUserKey packageUserKey = new PackageUserKey(null, null);
-        Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
-                || updatedDots.test(packageUserKey);
+    /** Releases the reference to the Taskbar multi-instance menu */
+    public void cleanUpMultiInstanceMenuReference() {
+        mManageWindowsTaskbarShortcut = null;
+    }
 
-        LauncherBindableItemsContainer.ItemOperator op = (info, v) -> {
-            if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
-                if (matcher.test(info)) {
-                    ((BubbleTextView) v).applyDotState(info, true /* animate */);
-                }
-            } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
-                FolderInfo fi = (FolderInfo) info;
-                if (fi.anyMatch(matcher)) {
-                    FolderDotInfo folderDotInfo = new FolderDotInfo();
-                    for (ItemInfo si : fi.getContents()) {
-                        folderDotInfo.addDotInfo(mPopupDataProvider.getDotInfoForItem(si));
-                    }
-                    ((FolderIcon) v).setDotInfo(folderDotInfo);
-                }
-            }
-
-            // process all the shortcuts
-            return false;
-        };
-
-        mControllers.taskbarViewController.mapOverItems(op);
-        Folder folder = Folder.getOpen(mContext);
-        if (folder != null) {
-            folder.iterateOverItems(op);
-        }
-        mControllers.taskbarAllAppsController.updateNotificationDots(updatedDots);
+    public void setAllowInitialSplitSelection(boolean allowInitialSplitSelection) {
+        mAllowInitialSplitSelection = allowInitialSplitSelection;
     }
 
     /**
@@ -159,22 +141,35 @@
             icon.clearFocus();
             return null;
         }
-        // TODO(b/344657629) support GroupTask as well, for Taskbar Recent apps
-        if (!(icon.getTag() instanceof ItemInfo item) || !ShortcutUtil.supportsShortcuts(item)) {
+
+        ItemInfo itemInfo;
+        if (icon.getTag() instanceof ItemInfo item && ShortcutUtil.supportsShortcuts(item)) {
+            itemInfo = item;
+        } else if (icon.getTag() instanceof SingleTask task) {
+            itemInfo = SingleTask.Companion.createTaskItemInfo(task);
+        } else {
             return null;
         }
 
         PopupContainerWithArrow<BaseTaskbarContext> container;
-        int deepShortcutCount = mPopupDataProvider.getShortcutCountForItem(item);
+        int deepShortcutCount = mPopupDataProvider.getShortcutCountForItem(itemInfo);
         // TODO(b/198438631): add support for INSTALL shortcut factory
         List<SystemShortcut> systemShortcuts = getSystemShortcuts()
-                .map(s -> s.getShortcut(context, item, icon))
+                .map(s -> s.getShortcut(context, itemInfo, icon))
                 .filter(Objects::nonNull)
                 .collect(Collectors.toList());
 
+        // TODO(b/375648361): Revisit to see if this can be implemented within getSystemShortcuts().
+        if (Flags.enablePinningAppWithContextMenu()) {
+            SystemShortcut shortcut = createPinShortcut(context, itemInfo, icon);
+            if (shortcut != null) {
+                systemShortcuts.add(0, shortcut);
+            }
+        }
+
         container = (PopupContainerWithArrow) context.getLayoutInflater().inflate(
                     R.layout.popup_container, context.getDragLayer(), false);
-        container.populateAndShowRows(icon, deepShortcutCount, systemShortcuts);
+        container.populateAndShowRows(icon, itemInfo, deepShortcutCount, systemShortcuts);
 
         // TODO (b/198438631): configure for taskbar/context
         container.setPopupItemDragHandler(new TaskbarPopupItemDragHandler());
@@ -196,21 +191,43 @@
         // here will reflect in the popup
         ArrayList<SystemShortcut.Factory> shortcuts = new ArrayList<>();
         shortcuts.add(APP_INFO);
-        if (!mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible()) {
+        if (!mControllers.taskbarDesktopModeController
+                .isInDesktopModeAndNotInOverview(mContext.getDisplayId())) {
             shortcuts.addAll(mControllers.uiController.getSplitMenuOptions().toList());
         }
-        if (com.android.wm.shell.Flags.enableBubbleAnything()) {
+        if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
             shortcuts.add(BUBBLE);
         }
 
         if (Flags.enableMultiInstanceMenuTaskbar()
                 && DesktopModeStatus.canEnterDesktopMode(mContext)
                 && !mControllers.taskbarStashController.isInOverview()) {
+            maybeCloseMultiInstanceMenu();
             shortcuts.addAll(getMultiInstanceMenuOptions().toList());
         }
         return shortcuts.stream();
     }
 
+    @Nullable
+    private SystemShortcut createPinShortcut(BaseTaskbarContext target, ItemInfo itemInfo,
+            BubbleTextView originalView) {
+        // Predicted items use {@code HotseatPredictionController.PinPrediction} shortcut to pin.
+        if (itemInfo.isPredictedItem()) {
+            return null;
+        }
+        if (itemInfo.container == CONTAINER_HOTSEAT) {
+            return new PinToTaskbarShortcut<>(target, itemInfo, originalView, false,
+                    mHotseatInfosList);
+        }
+        if (mHotseatInfosList.size()
+                < mContext.getTaskbarSpecsEvaluator().getNumShownHotseatIcons()) {
+            return new PinToTaskbarShortcut<>(target, itemInfo, originalView, true,
+                    mHotseatInfosList);
+        }
+
+        return null;
+    }
+
     @Override
     public void dumpLogs(String prefix, PrintWriter pw) {
         pw.println(prefix + "TaskbarPopupController:");
@@ -294,6 +311,10 @@
         return index < 0 ? null : mAppInfosList[index];
     }
 
+    public void setHotseatInfosList(SparseArray<ItemInfo> info) {
+        mHotseatInfosList = info;
+    }
+
     /**
      * Returns a stream of Multi Instance menu options if an app supports it.
      */
@@ -325,8 +346,9 @@
     public SystemShortcut.Factory<BaseTaskbarContext> createManageWindowsShortcutFactory() {
         return (context, itemInfo, originalView) -> {
             if (shouldShowMultiInstanceOptions(itemInfo)) {
-                return new ManageWindowsTaskbarShortcut<>(context, itemInfo, originalView,
-                        mControllers);
+                mManageWindowsTaskbarShortcut = new ManageWindowsTaskbarShortcut<>(
+                        context, itemInfo, originalView, mControllers);
+                return mManageWindowsTaskbarShortcut;
             }
             return null;
         };
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
index a059b22..417ef7e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarRecentAppsController.kt
@@ -19,15 +19,18 @@
 import android.window.DesktopModeFlags
 import androidx.annotation.VisibleForTesting
 import com.android.launcher3.BubbleTextView.RunningAppState
+import com.android.launcher3.Flags
 import com.android.launcher3.Flags.enableRecentsInTaskbar
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.TaskItemInfo
 import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
 import com.android.launcher3.util.CancellableTask
+import com.android.quickstep.RecentsFilterState
 import com.android.quickstep.RecentsModel
 import com.android.quickstep.util.DesktopTask
 import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.SingleTask
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import java.io.PrintWriter
 
@@ -73,6 +76,17 @@
     var shownTasks: List<GroupTask> = emptyList()
         private set
 
+    val shownTaskIds: List<Int>
+        get() = shownTasks.flatMap { shownTask -> shownTask.tasks }.map { it.key.id }
+
+    /**
+     * The task-state of an app, i.e. whether the app has a task and what state that task is in.
+     *
+     * @property taskId The ID of the task if one exists (i.e. if the state is RUNNING or
+     *   MINIMIZED), null otherwise (NOT_RUNNING).
+     */
+    data class TaskState(val runningAppState: RunningAppState, val taskId: Int? = null)
+
     /**
      * Returns the state of the most active Desktop task represented by the given [ItemInfo].
      *
@@ -80,24 +94,28 @@
      * i.e. we return [DesktopAppState.RUNNING] over [DesktopAppState.MINIMIZED], and
      * [DesktopAppState.MINIMIZED] over [DesktopAppState.NOT_RUNNING].
      */
-    fun getDesktopItemState(itemInfo: ItemInfo?): RunningAppState {
-        val packageName = itemInfo?.getTargetPackage() ?: return RunningAppState.NOT_RUNNING
-        return getDesktopAppState(packageName, itemInfo.user.identifier)
+    fun getDesktopItemState(itemInfo: ItemInfo?): TaskState {
+        val packageName =
+            itemInfo?.getTargetPackage() ?: return TaskState(RunningAppState.NOT_RUNNING)
+        return getDesktopTaskState(packageName, itemInfo.user.identifier)
     }
 
-    private fun getDesktopAppState(packageName: String, userId: Int): RunningAppState {
-        val tasks = desktopTask?.tasks ?: return RunningAppState.NOT_RUNNING
+    private fun getDesktopTaskState(packageName: String, userId: Int): TaskState {
+        val tasks = desktopTask?.tasks ?: return TaskState(RunningAppState.NOT_RUNNING)
         val appTasks =
             tasks.filter { task ->
                 packageName == task.key.packageName && task.key.userId == userId
             }
-        if (appTasks.find { getRunningAppState(it.key.id) == RunningAppState.RUNNING } != null) {
-            return RunningAppState.RUNNING
+        val runningTask = appTasks.find { getRunningAppState(it.key.id) == RunningAppState.RUNNING }
+        if (runningTask != null) {
+            return TaskState(RunningAppState.RUNNING, runningTask.key.id)
         }
-        if (appTasks.find { getRunningAppState(it.key.id) == RunningAppState.MINIMIZED } != null) {
-            return RunningAppState.MINIMIZED
+        val minimizedTask =
+            appTasks.find { getRunningAppState(it.key.id) == RunningAppState.MINIMIZED }
+        if (minimizedTask != null) {
+            return TaskState(RunningAppState.MINIMIZED, taskId = minimizedTask.key.id)
         }
-        return RunningAppState.NOT_RUNNING
+        return TaskState(RunningAppState.NOT_RUNNING)
     }
 
     /** Get the [RunningAppState] for the given task. */
@@ -118,7 +136,7 @@
         get() {
             if (
                 !canShowRunningApps ||
-                    !controllers.taskbarDesktopModeController.areDesktopTasksVisible
+                    !controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()
             ) {
                 return emptySet()
             }
@@ -134,7 +152,7 @@
         get() {
             if (
                 !canShowRunningApps ||
-                    !controllers.taskbarDesktopModeController.areDesktopTasksVisible
+                    !controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()
             ) {
                 return emptySet()
             }
@@ -170,10 +188,10 @@
     /** Called to update hotseatItems, in order to de-dupe them from Recent/Running tasks later. */
     fun updateHotseatItemInfos(hotseatItems: Array<ItemInfo?>): Array<ItemInfo?> {
         // Ignore predicted apps - we show running or recent apps instead.
-        val areDesktopTasksVisible = controllers.taskbarDesktopModeController.areDesktopTasksVisible
+        val showDesktopTasks =
+            controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()
         val removePredictions =
-            (areDesktopTasksVisible && canShowRunningApps) ||
-                (!areDesktopTasksVisible && canShowRecentApps)
+            (showDesktopTasks && canShowRunningApps) || (!showDesktopTasks && canShowRecentApps)
         if (!removePredictions) {
             shownHotseatItems = hotseatItems.filterNotNull()
             onRecentsOrHotseatChanged()
@@ -185,7 +203,7 @@
                 .filter { itemInfo -> !itemInfo.isPredictedItem }
                 .toMutableList()
 
-        if (areDesktopTasksVisible && canShowRunningApps) {
+        if (showDesktopTasks && canShowRunningApps) {
             shownHotseatItems =
                 updateHotseatItemsFromRunningTasks(
                     getOrderedAndWrappedDesktopTasks(),
@@ -198,30 +216,35 @@
         return shownHotseatItems.toTypedArray()
     }
 
-    private fun getOrderedAndWrappedDesktopTasks(): List<GroupTask> {
+    private fun getOrderedAndWrappedDesktopTasks(): List<SingleTask> {
         val tasks = desktopTask?.tasks ?: emptyList()
-        // Kind of hacky, we wrap each single task in the Desktop as a GroupTask.
+        // We wrap each task in the Desktop as a `SingleTask`.
         val orderFromId = orderedRunningTaskIds.withIndex().associate { (index, id) -> id to index }
         val sortedTasks = tasks.sortedWith(compareBy(nullsLast()) { orderFromId[it.key.id] })
-        return sortedTasks.map { GroupTask(it) }
+        return sortedTasks.map { SingleTask(it) }
     }
 
     private fun reloadRecentTasksIfNeeded() {
         if (!recentsModel.isTaskListValid(taskListChangeId)) {
             taskListChangeId =
-                recentsModel.getTasks { tasks ->
-                    allRecentTasks = tasks
-                    val oldRunningTaskdIds = runningTaskIds
-                    val oldMinimizedTaskIds = minimizedTaskIds
-                    desktopTask = allRecentTasks.filterIsInstance<DesktopTask>().firstOrNull()
-                    val runningTasksChanged = oldRunningTaskdIds != runningTaskIds
-                    val minimizedTasksChanged = oldMinimizedTaskIds != minimizedTaskIds
-                    if (
-                        onRecentsOrHotseatChanged() || runningTasksChanged || minimizedTasksChanged
-                    ) {
-                        controllers.taskbarViewController.commitRunningAppsToUI()
-                    }
-                }
+                recentsModel.getTasks(
+                    { tasks ->
+                        allRecentTasks = tasks
+                        val oldRunningTaskdIds = runningTaskIds
+                        val oldMinimizedTaskIds = minimizedTaskIds
+                        desktopTask = allRecentTasks.filterIsInstance<DesktopTask>().firstOrNull()
+                        val runningTasksChanged = oldRunningTaskdIds != runningTaskIds
+                        val minimizedTasksChanged = oldMinimizedTaskIds != minimizedTaskIds
+                        if (
+                            onRecentsOrHotseatChanged() ||
+                                runningTasksChanged ||
+                                minimizedTasksChanged
+                        ) {
+                            controllers.taskbarViewController.commitRunningAppsToUI()
+                        }
+                    },
+                    RecentsFilterState.EMPTY_FILTER,
+                )
         }
     }
 
@@ -234,7 +257,7 @@
         val oldShownTasks = shownTasks
         orderedRunningTaskIds = updateOrderedRunningTaskIds()
         shownTasks =
-            if (controllers.taskbarDesktopModeController.areDesktopTasksVisible) {
+            if (controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()) {
                 computeShownRunningTasks()
             } else {
                 computeShownRecentTasks()
@@ -265,8 +288,8 @@
     }
 
     private fun updateOrderedRunningTaskIds(): MutableList<Int> {
-        val desktopTaskAsList = getOrderedAndWrappedDesktopTasks()
-        val desktopTaskIds = desktopTaskAsList.map { it.task1.key.id }
+        val desktopTasksAsList = getOrderedAndWrappedDesktopTasks().map { it.task }
+        val desktopTaskIds = desktopTasksAsList.map { it.key.id }
         var newOrder =
             orderedRunningTaskIds
                 .filter { it in desktopTaskIds } // Only keep the tasks that are still running
@@ -276,27 +299,61 @@
         return newOrder
     }
 
+    /**
+     * Computes the list of running tasks to be shown in the recent apps section of the taskbar in
+     * desktop mode, taking into account deduplication against hotseat items and existing tasks.
+     */
     private fun computeShownRunningTasks(): List<GroupTask> {
         if (!canShowRunningApps) {
             return emptyList()
         }
-        val desktopTaskAsList = getOrderedAndWrappedDesktopTasks()
-        val desktopTaskIds = desktopTaskAsList.map { it.task1.key.id }
-        val shownTaskIds = shownTasks.map { it.task1.key.id }
-        // TODO(b/315344726 Multi-instance support): only show one icon per package once we support
-        //  taskbar multi-instance menus
-        val shownHotseatItemTaskIds =
-            shownHotseatItems.mapNotNull { it as? TaskItemInfo }.map { it.taskId }
-        // Remove any newly-missing Tasks, and actual group-tasks
+
+        val desktopTasks = getOrderedAndWrappedDesktopTasks()
+
         val newShownTasks =
-            shownTasks
-                .filter { !it.supportsMultipleTasks() }
-                .filter { it.task1.key.id in desktopTaskIds }
-                .toMutableList()
-        // Add any new Tasks, maintaining the order from previous shownTasks.
-        newShownTasks.addAll(desktopTaskAsList.filter { it.task1.key.id !in shownTaskIds })
-        // Remove any tasks already covered by Hotseat icons
-        return newShownTasks.filter { it.task1.key.id !in shownHotseatItemTaskIds }
+            if (Flags.enableMultiInstanceMenuTaskbar()) {
+                val deduplicatedDesktopTasks =
+                    desktopTasks.distinctBy { Pair(it.task.key.packageName, it.task.key.userId) }
+
+                shownTasks
+                    .filter {
+                        it is SingleTask &&
+                            it.task.key.id in deduplicatedDesktopTasks.map { it.task.key.id }
+                    }
+                    .toMutableList()
+                    .apply {
+                        addAll(
+                            deduplicatedDesktopTasks.filter { currentTask ->
+                                val currentTaskKey = currentTask.task.key
+                                currentTaskKey.id !in shownTaskIds &&
+                                    shownHotseatItems.none { hotseatItem ->
+                                        currentTask.containsPackage(
+                                            hotseatItem.targetPackage,
+                                            hotseatItem.user.identifier,
+                                        )
+                                    }
+                            }
+                        )
+                    }
+            } else {
+                val desktopTaskIds = desktopTasks.map { it.task.key.id }
+                val shownHotseatItemTaskIds =
+                    shownHotseatItems.mapNotNull { it as? TaskItemInfo }.map { it.taskId }
+
+                shownTasks
+                    .filter { it is SingleTask && it.task.key.id in desktopTaskIds }
+                    .toMutableList()
+                    .apply {
+                        addAll(
+                            desktopTasks.filter { desktopTask ->
+                                desktopTask.task.key.id !in shownTaskIds
+                            }
+                        )
+                        removeAll { it is SingleTask && it.task.key.id in shownHotseatItemTaskIds }
+                    }
+            }
+
+        return newShownTasks
     }
 
     private fun computeShownRecentTasks(): List<GroupTask> {
@@ -305,7 +362,6 @@
         }
         // Remove the current task.
         val allRecentTasks = allRecentTasks.subList(0, allRecentTasks.size - 1)
-        // TODO(b/315344726 Multi-instance support): dedupe Tasks of the same package too
         var shownTasks = dedupeHotseatTasks(allRecentTasks, shownHotseatItems)
         if (shownTasks.size > MAX_RECENT_TASKS) {
             // Remove any tasks older than MAX_RECENT_TASKS.
@@ -318,10 +374,29 @@
         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)
+        // TODO: b/393476333 - Check the behavior of the Taskbar recents section when empty desks
+        // become supported.
+        return if (Flags.enableMultiInstanceMenuTaskbar()) {
+            groupTasks.filter { groupTask ->
+                // Keep tasks that are group tasks or unique package name/user combinations
+                when (groupTask) {
+                    is SingleTask ->
+                        shownHotseatItems.none {
+                            groupTask.containsPackage(it.targetPackage, it.user.identifier)
+                        }
+
+                    else -> true
+                }
+            }
+        } else {
+            val hotseatPackages = shownHotseatItems.map { it.targetPackage }
+            groupTasks.filter { groupTask ->
+                when (groupTask) {
+                    is SingleTask -> hotseatPackages.none { groupTask.containsPackage(it) }
+
+                    else -> true
+                }
+            }
         }
     }
 
@@ -338,11 +413,13 @@
                 itemInfo
             } else {
                 val foundTask =
-                    groupTasks.find { task ->
-                        task.task1.key.packageName == itemInfo.targetPackage &&
-                            task.task1.key.userId == itemInfo.user.identifier
-                    } ?: return@map itemInfo
-                TaskItemInfo(foundTask.task1.key.id, itemInfo as WorkspaceItemInfo)
+                    groupTasks
+                        .flatMap { it.tasks }
+                        .find { task ->
+                            task.key.packageName == itemInfo.targetPackage &&
+                                task.key.userId == itemInfo.user.identifier
+                        } ?: return@map itemInfo
+                TaskItemInfo(foundTask.key.id, itemInfo as WorkspaceItemInfo)
             }
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
index a64dab1..f4a7d68 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarSharedState.java
@@ -77,6 +77,8 @@
 
     public List<BubbleInfo> bubbleInfoItems;
 
+    public List<BubbleInfo> suppressedBubbleInfoItems;
+
     /** Returns whether there are a saved bubbles. */
     public boolean hasSavedBubbles() {
         return bubbleInfoItems != null && !bubbleInfoItems.isEmpty();
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
index 25db960..94cff0b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
@@ -22,6 +22,7 @@
 
 import android.content.Intent;
 import android.content.pm.LauncherApps;
+import android.content.pm.ShortcutInfo;
 import android.util.Pair;
 import android.view.KeyEvent;
 import android.view.View;
@@ -38,6 +39,7 @@
 import com.android.launcher3.util.ShortcutUtil;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.LogUtils;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
 
 import java.util.List;
 
@@ -50,6 +52,7 @@
 
     public static final int MOVE_TO_TOP_OR_LEFT = R.id.action_move_to_top_or_left;
     public static final int MOVE_TO_BOTTOM_OR_RIGHT = R.id.action_move_to_bottom_or_right;
+    public static final int CREATE_APPLICATION_BUBBLE = R.id.action_create_application_bubble;
 
     private final LauncherApps mLauncherApps;
     private final StatsLogManager mStatsLogManager;
@@ -67,6 +70,9 @@
                 MOVE_TO_BOTTOM_OR_RIGHT,
                 R.string.move_drop_target_bottom_or_right,
                 KeyEvent.KEYCODE_R));
+        mActions.put(CREATE_APPLICATION_BUBBLE, new LauncherAction(
+                CREATE_APPLICATION_BUBBLE, R.string.open_app_as_a_bubble,
+                KeyEvent.KEYCODE_L));
     }
 
     @Override
@@ -76,11 +82,27 @@
         }
         out.add(mActions.get(MOVE_TO_TOP_OR_LEFT));
         out.add(mActions.get(MOVE_TO_BOTTOM_OR_RIGHT));
+        if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
+            out.add(mActions.get(CREATE_APPLICATION_BUBBLE));
+        }
     }
 
     @Override
     protected boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) {
-        if (item instanceof ItemInfoWithIcon
+        if (action == DEEP_SHORTCUTS) {
+            mContext.showPopupMenuForIcon((BubbleTextView) host);
+            return true;
+        } else if (action == CREATE_APPLICATION_BUBBLE) {
+            if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+                    && item instanceof WorkspaceItemInfo) {
+                ShortcutInfo shortcutInfo = ((WorkspaceItemInfo) item).getDeepShortcutInfo();
+                SystemUiProxy.INSTANCE.get(mContext).showShortcutBubble(shortcutInfo);
+                return true;
+            } else if (item.getIntent() != null && item.getIntent().getPackage() != null) {
+                SystemUiProxy.INSTANCE.get(mContext).showAppBubble(item.getIntent(), item.user);
+                return true;
+            }
+        } else if (item instanceof ItemInfoWithIcon
                 && (action == MOVE_TO_TOP_OR_LEFT || action == MOVE_TO_BOTTOM_OR_RIGHT)) {
             ItemInfoWithIcon info = (ItemInfoWithIcon) item;
             int side = action == MOVE_TO_TOP_OR_LEFT
@@ -112,10 +134,6 @@
                         instanceIds.first);
             }
             return true;
-        } else if (action == DEEP_SHORTCUTS) {
-            mContext.showPopupMenuForIcon((BubbleTextView) host);
-
-            return true;
         }
         return false;
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 67be8da..95724ad 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -34,8 +34,7 @@
 import static com.android.quickstep.util.SystemUiFlagUtils.isTaskbarHidden;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 
@@ -84,8 +83,6 @@
     private static final String TAG = "TaskbarStashController";
     private static final boolean DEBUG = false;
 
-    private static boolean sEnableSoftwareImeForTests = false;
-
     /**
      * Def. value for @param shouldBubblesFollow in
      * {@link #updateAndAnimateTransientTaskbar(boolean)} */
@@ -107,6 +104,8 @@
     private static final int FLAG_DELAY_TASKBAR_BG_TAG = 1 << 12;
     public static final int FLAG_STASHED_FOR_BUBBLES = 1 << 13; // show handle for stashed hotseat
     public static final int FLAG_TASKBAR_HIDDEN = 1 << 14; // taskbar hidden during dream, etc...
+    // taskbar should always be stashed for bubble bar on phone
+    public static final int FLAG_STASHED_BUBBLE_BAR_ON_PHONE = 1 << 15;
 
     // If any of these flags are enabled, isInApp should return true.
     private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP;
@@ -129,7 +128,7 @@
     // If any of these flags are enabled, the taskbar must be stashed.
     private static final int FLAGS_FORCE_STASHED = FLAG_STASHED_SYSUI | FLAG_STASHED_DEVICE_LOCKED
             | FLAG_STASHED_IN_TASKBAR_ALL_APPS | FLAG_STASHED_SMALL_SCREEN
-            | FLAG_STASHED_FOR_BUBBLES;
+            | FLAG_STASHED_FOR_BUBBLES | FLAG_STASHED_BUBBLE_BAR_ON_PHONE;
 
     /**
      * How long to stash/unstash when manually invoked via long press.
@@ -261,8 +260,8 @@
 
     private @Nullable AnimatorSet mAnimator;
     private boolean mIsSystemGestureInProgress;
-    private boolean mIsImeShowing;
-    private boolean mIsImeSwitcherShowing;
+    /** Whether the IME is visible. */
+    private boolean mIsImeVisible;
 
     private final Alarm mTimeoutAlarm = new Alarm();
     private boolean mEnableBlockingTimeoutDuringTests = false;
@@ -271,6 +270,8 @@
     private final long mTaskbarBackgroundDuration;
     private boolean mUserIsNotGoingHome = false;
 
+    private final boolean mInAppStateAffectsDesktopTasksVisibilityInTaskbar;
+
     // Evaluate whether the handle should be stashed
     private final LongPredicate mIsStashedPredicate = flags -> {
         boolean inApp = hasAnyFlag(flags, FLAGS_IN_APP);
@@ -295,8 +296,18 @@
         mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
         mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class);
 
-        mTaskbarBackgroundDuration =
-                activity.getResources().getInteger(R.integer.taskbar_background_duration);
+        // Taskbar, via `TaskbarDesktopModeController`, depends on `TaskbarStashController` state to
+        // determine whether desktop tasks should be shown because taskbar is pinned on the home
+        // screen for freeform windowing displays. In this case, list of items shown in the taskbar
+        // needs to be updated when in-app state changes.
+        // TODO(b/390665752): Feature to "lock" pinned taskbar to home screen will be superseded by
+        //     pinning, in other launcher states, at which point this variable can be removed.
+        mInAppStateAffectsDesktopTasksVisibilityInTaskbar =
+                !DisplayController.showDesktopTaskbarForFreeformDisplay(mActivity)
+                        && DisplayController.showLockedTaskbarOnHome(mActivity);
+
+        mTaskbarBackgroundDuration = activity.getResources().getInteger(
+                R.integer.taskbar_background_duration);
         if (mActivity.isPhoneMode()) {
             mUnstashedHeight = mActivity.getResources().getDimensionPixelSize(
                     R.dimen.taskbar_phone_size);
@@ -350,6 +361,7 @@
         // 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);
+        updateStateForFlag(FLAG_STASHED_BUBBLE_BAR_ON_PHONE, mActivity.isBubbleBarOnPhone());
 
         applyState(/* duration = */ 0);
 
@@ -565,7 +577,8 @@
      */
     public void updateAndAnimateTransientTaskbar(boolean stash, boolean shouldBubblesFollow,
             boolean delayTaskbarBackground) {
-        if (!DisplayController.isTransientTaskbar(mActivity)) {
+        if (!DisplayController.isTransientTaskbar(mActivity)
+                || mActivity.isBubbleBarOnPhone()) {
             return;
         }
 
@@ -1111,7 +1124,7 @@
      */
     @VisibleForTesting
     long getTaskbarStashStartDelayForIme() {
-        if (mIsImeShowing) {
+        if (mIsImeVisible) {
             // Only delay when IME is exiting, not entering.
             return 0;
         }
@@ -1137,8 +1150,7 @@
         updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED,
                 SystemUiFlagUtils.isLocked(systemUiStateFlags));
 
-        mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
-        mIsImeSwitcherShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_SHOWING);
+        mIsImeVisible = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_VISIBLE);
         if (updateStateForFlag(FLAG_STASHED_IME, shouldStashForIme())) {
             animDuration = TASKBAR_STASH_DURATION_FOR_IME;
             startDelay = getTaskbarStashStartDelayForIme();
@@ -1154,7 +1166,7 @@
     }
 
     /**
-     * We stash when IME or IME switcher is showing.
+     * We stash when the IME is visible.
      *
      * <p>Do not stash if in small screen, with 3 button nav, and in landscape (or seascape).
      * <p>Do not stash if taskbar is transient.
@@ -1172,15 +1184,16 @@
         }
 
         // Do not stash if pinned taskbar, hardware keyboard is attached and no IME is docked
-        if (isHardwareKeyboard() && DisplayController.isPinnedTaskbar(mActivity)
+        if (mActivity.isHardwareKeyboard() && DisplayController.isPinnedTaskbar(mActivity)
                 && !mActivity.isImeDocked()) {
             return false;
         }
 
         // Do not stash if hardware keyboard is attached, in 3 button nav and desktop windowing mode
-        if (isHardwareKeyboard()
+        if (mActivity.isHardwareKeyboard()
                 && mActivity.isThreeButtonNav()
-                && mControllers.taskbarDesktopModeController.getAreDesktopTasksVisible()) {
+                && mControllers.taskbarDesktopModeController
+                    .isInDesktopModeAndNotInOverview(mActivity.getDisplayId())) {
             return false;
         }
 
@@ -1189,22 +1202,7 @@
             return false;
         }
 
-        return mIsImeShowing || mIsImeSwitcherShowing;
-    }
-
-    private boolean isHardwareKeyboard() {
-        return mActivity.isHardwareKeyboard() && !sEnableSoftwareImeForTests;
-    }
-
-    /**
-     * Overrides {@link #isHardwareKeyboard()} to {@code false} for testing, if enabled.
-     * <p>
-     * Virtual devices are sometimes in hardware keyboard mode, leading to an inconsistent
-     * testing environment.
-     */
-    @VisibleForTesting
-    static void enableSoftwareImeForTests(boolean enable) {
-        sEnableSoftwareImeForTests = enable;
+        return mIsImeVisible;
     }
 
     /**
@@ -1253,6 +1251,10 @@
         if (hasAnyFlag(changedFlags, FLAG_IN_OVERVIEW | FLAG_IN_APP)) {
             mControllers.runAfterInit(() -> mControllers.taskbarInsetsController
                     .onTaskbarOrBubblebarWindowHeightOrInsetsChanged());
+            if (mInAppStateAffectsDesktopTasksVisibilityInTaskbar) {
+                mControllers.runAfterInit(
+                        () -> mControllers.taskbarViewController.commitRunningAppsToUI());
+            }
         }
         mActivity.applyForciblyShownFlagWhileTransientTaskbarUnstashed(!isStashedInApp());
     }
@@ -1375,8 +1377,7 @@
         pw.println(prefix + "\tappliedState=" + getStateString(mStatePropertyHolder.mPrevFlags));
         pw.println(prefix + "\tmState=" + getStateString(mState));
         pw.println(prefix + "\tmIsSystemGestureInProgress=" + mIsSystemGestureInProgress);
-        pw.println(prefix + "\tmIsImeShowing=" + mIsImeShowing);
-        pw.println(prefix + "\tmIsImeSwitcherShowing=" + mIsImeSwitcherShowing);
+        pw.println(prefix + "\tmIsImeVisible=" + mIsImeVisible);
     }
 
     private static String getStateString(long flags) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index f29f95d..ea0b81e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -39,7 +39,7 @@
 import com.android.launcher3.taskbar.bubbles.BubbleBarController;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SplitTask;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskContainer;
 import com.android.quickstep.views.TaskView;
@@ -207,9 +207,18 @@
         return false;
     }
 
-    /** Returns {@code true} if Home All Apps available instead of Taskbar All Apps. */
-    protected boolean canToggleHomeAllApps() {
-        return false;
+
+    /**
+     * Toggles all apps UI. Default implementation opens Taskbar All Apps, but may be overridden to
+     * open different Alls Apps variant depending on the context.
+     * @param focusSearch indicates whether All Apps should be opened with search input focused.
+     */
+    protected void toggleAllApps(boolean focusSearch) {
+        if (focusSearch) {
+            mControllers.taskbarAllAppsController.toggleSearch();
+        } else {
+            mControllers.taskbarAllAppsController.toggle();
+        }
     }
 
     @CallSuper
@@ -283,7 +292,7 @@
                                     foundTask,
                                     taskContainer.getIconView().getDrawable(),
                                     taskContainer.getSnapshotView(),
-                                    taskContainer.getSplitAnimationThumbnail(),
+                                    taskContainer.getThumbnail(),
                                     null /* intent */,
                                     null /* user */,
                                     info);
@@ -332,7 +341,7 @@
      * Launches the given task in split-screen.
      */
     public void launchSplitTasks(
-            @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) { }
+            @NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) { }
 
     /**
      * Returns the matching view (if any) in the taskbar.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index b51409c..07b77c9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -16,31 +16,28 @@
 package com.android.launcher3.taskbar;
 
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
+import static android.window.DesktopModeFlags.ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION;
 
 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.Flags.taskbarRecentsLayoutTransition;
 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.enableTaskbarPinning;
 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
 
-import static java.util.function.Predicate.not;
-
 import android.content.Context;
 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.ArraySet;
 import android.util.AttributeSet;
 import android.view.DisplayCutout;
 import android.view.InputDevice;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
-import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 
 import androidx.annotation.LayoutRes;
@@ -63,20 +60,21 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.taskbar.customization.TaskbarAllAppsButtonContainer;
 import com.android.launcher3.taskbar.customization.TaskbarDividerContainer;
+import com.android.launcher3.uioverrides.PredictedAppIcon;
 import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
 import com.android.quickstep.views.TaskViewType;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
 
-import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
-import java.util.function.Predicate;
+import java.util.Set;
 
 /**
  * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps.
@@ -134,6 +132,9 @@
 
     private final int mNumStaticViews;
 
+    private Set<GroupTask> mPrevRecentTasks = Collections.emptySet();
+    private Set<GroupTask> mPrevOverflowTasks = Collections.emptySet();
+
     public TaskbarView(@NonNull Context context) {
         this(context, null);
     }
@@ -199,8 +200,10 @@
         // TODO: Disable touch events on QSB otherwise it can crash.
         mQsb = LayoutInflater.from(context).inflate(R.layout.search_container_hotseat, this, false);
 
-        mNumStaticViews = taskbarRecentsLayoutTransition() && !mActivityContext.isPhoneMode()
-                ? addStaticViews() : 0;
+        mNumStaticViews =
+                ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue() && !mActivityContext.isPhoneMode()
+                        ? addStaticViews()
+                        : 0;
     }
 
     /**
@@ -305,16 +308,6 @@
         mShouldTryStartAlign = mActivityContext.shouldStartAlignTaskbar();
     }
 
-    @Override
-    public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
-        if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS) {
-            announceTaskbarShown();
-        } else if (action == AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS) {
-            announceTaskbarHidden();
-        }
-        return super.performAccessibilityActionInternal(action, arguments);
-    }
-
     private void announceTaskbarShown() {
         BubbleBarLocation bubbleBarLocation = mControllerCallbacks.getBubbleBarLocationIfVisible();
         if (bubbleBarLocation == null) {
@@ -328,21 +321,12 @@
         }
     }
 
-    private void announceTaskbarHidden() {
-        BubbleBarLocation bubbleBarLocation = mControllerCallbacks.getBubbleBarLocationIfVisible();
-        if (bubbleBarLocation == null) {
-            announceForAccessibility(mContext.getString(R.string.taskbar_a11y_hidden_title));
-        } else {
-            announceForAccessibility(
-                    mContext.getString(R.string.taskbar_a11y_hidden_with_bubbles_title));
-        }
-    }
-
     protected void announceAccessibilityChanges() {
-        this.performAccessibilityAction(
-                isVisibleToUser() ? AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS
-                        : AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
-
+        // Only announce taskbar window shown. Window disappearing is generally not announce.
+        // This also aligns with talkback guidelines and unnecessary announcement to users.
+        if (isVisibleToUser()) {
+            announceTaskbarShown();
+        }
         ActivityContext.lookupContext(getContext()).getDragLayer()
                 .sendAccessibilityEvent(TYPE_WINDOW_CONTENT_CHANGED);
     }
@@ -408,14 +392,15 @@
 
     /** Inflates/binds the hotseat items and recent tasks to the view. */
     protected void updateItems(ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
+        if (mActivityContext.isDestroyed()) return;
         // Filter out unsupported items.
         hotseatItemInfos = Arrays.stream(hotseatItemInfos)
                 .filter(Objects::nonNull)
                 .toArray(ItemInfo[]::new);
         // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
-        recentTasks = recentTasks.stream().filter(not(GroupTask::supportsMultipleTasks)).toList();
+        recentTasks = recentTasks.stream().filter(it -> it instanceof SingleTask).toList();
 
-        if (taskbarRecentsLayoutTransition()) {
+        if (ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue()) {
             updateItemsWithLayoutTransition(hotseatItemInfos, recentTasks);
         } else {
             updateItemsWithoutLayoutTransition(hotseatItemInfos, recentTasks);
@@ -445,7 +430,7 @@
             mAddedDividerForRecents = true;
         }
 
-        updateRecents(recentTasks);
+        updateRecents(recentTasks, hotseatItemInfos.length);
 
         addView(mAllAppsButtonContainer, mIsRtl ? hotseatItemInfos.length : 0);
 
@@ -468,13 +453,13 @@
 
         // Skip static views and potential All Apps divider, if they are on the left.
         mNextViewIndex = mIsRtl ? 0 : mNumStaticViews;
-        if (getChildAt(mNextViewIndex) == mTaskbarDividerContainer) {
+        if (getChildAt(mNextViewIndex) == mTaskbarDividerContainer && !mAddedDividerForRecents) {
             mNextViewIndex++;
         }
 
         // Update left section.
         if (mIsRtl) {
-            updateRecents(recentTasks.reversed());
+            updateRecents(recentTasks.reversed(), hotseatItemInfos.length);
         } else {
             updateHotseatItems(hotseatItemInfos);
         }
@@ -489,11 +474,11 @@
         if (mIsRtl) {
             updateHotseatItems(hotseatItemInfos);
         } else {
-            updateRecents(recentTasks);
+            updateRecents(recentTasks, hotseatItemInfos.length);
         }
 
         // Recents divider takes priority.
-        if (!mAddedDividerForRecents) {
+        if (!mAddedDividerForRecents && !mActivityContext.areDesktopTasksVisible()) {
             updateAllAppsDivider();
         }
     }
@@ -600,10 +585,12 @@
             // Apply the Hotseat ItemInfos, or hide the view if there is none for a given index.
             if (hotseatView instanceof BubbleTextView btv
                     && hotseatItemInfo instanceof WorkspaceItemInfo workspaceInfo) {
-                boolean animate = btv.shouldAnimateIconChange((WorkspaceItemInfo) hotseatItemInfo);
-                btv.applyFromWorkspaceItem(workspaceInfo, animate, numViewsAnimated);
-                if (animate) {
-                    numViewsAnimated++;
+                if (btv instanceof PredictedAppIcon pai) {
+                    if (pai.applyFromWorkspaceItemWithAnimation(workspaceInfo, numViewsAnimated)) {
+                        numViewsAnimated++;
+                    }
+                } else {
+                    btv.applyFromWorkspaceItem(workspaceInfo);
                 }
             }
             setClickAndLongClickListenersForIcon(hotseatView);
@@ -618,49 +605,63 @@
         }
     }
 
-    private void updateRecents(List<GroupTask> recentTasks) {
-        // At this point, the all apps button has not been added as a child view, but needs to be
-        // accounted for when comparing current icon count to max number of icons.
-        int nonTaskIconsToBeAdded = 1;
-
-        boolean supportsOverflow = Flags.taskbarOverflow();
+    private void updateRecents(List<GroupTask> recentTasks, int hotseatSize) {
+        boolean supportsOverflow = Flags.taskbarOverflow() && recentTasks.size() > 1;
         int overflowSize = 0;
-        if (supportsOverflow) {
-            mIdealNumIcons = mNextViewIndex + recentTasks.size() + nonTaskIconsToBeAdded;
+        boolean hasOverflow = false;
+        if (supportsOverflow && mTaskbarOverflowView != null) {
+            // Need to account for All Apps and the divider. If we need to have an overflow, we will
+            // have a divider for recents.
+            final int nonTaskIconsToBeAdded = 2;
+            mIdealNumIcons = hotseatSize + recentTasks.size() + nonTaskIconsToBeAdded;
             overflowSize = mIdealNumIcons - mMaxNumIcons;
+            hasOverflow = overflowSize > 0;
 
-            if (overflowSize > 0 && mTaskbarOverflowView != null) {
+            if (!ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue() && hasOverflow) {
                 addView(mTaskbarOverflowView, mNextViewIndex++);
-            } else if (mTaskbarOverflowView != null) {
+            } else if (ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue()) {
+                // RTL case is handled after we add the recent icons, because the button needs to
+                // then be to the right of them.
+                if (hasOverflow && !mIsRtl) {
+                    if (mPrevOverflowTasks.isEmpty()) addView(mTaskbarOverflowView, mNextViewIndex);
+                    // NOTE: If overflow already existed, assume the overflow view is already
+                    // at the correct position.
+                    mNextViewIndex++;
+                } else if (!hasOverflow && !mPrevOverflowTasks.isEmpty()) {
+                    removeView(mTaskbarOverflowView);
+                    mTaskbarOverflowView.clearItems();
+                }
+            } else {
                 mTaskbarOverflowView.clearItems();
             }
         }
 
-        List<Task> overflownTasks = null;
         // An extra item needs to be added to overflow button to account for the space taken up by
         // the overflow button.
         final int itemsToAddToOverflow =
-                (overflowSize > 0) ? Math.min(overflowSize + 1, recentTasks.size()) : 0;
-        if (overflowSize > 0) {
-            overflownTasks = new ArrayList<>(itemsToAddToOverflow);
+                hasOverflow ? Math.min(overflowSize + 1, recentTasks.size()) : 0;
+        final Set<GroupTask> overflownRecentsSet;
+        if (hasOverflow && mTaskbarOverflowView != null) {
+            final int startIndex = mIsRtl ? recentTasks.size() - itemsToAddToOverflow : 0;
+            final int endIndex = mIsRtl ? recentTasks.size() : itemsToAddToOverflow;
+            final List<GroupTask> overflownRecents = recentTasks.subList(startIndex, endIndex);
+            mTaskbarOverflowView.setItems(
+                    overflownRecents.stream().map(t -> ((SingleTask) t).getTask()).toList());
+            overflownRecentsSet = new ArraySet<>(overflownRecents);
+        } else {
+            overflownRecentsSet = Collections.emptySet();
         }
 
         // Add Recent/Running icons.
-        for (GroupTask task : recentTasks) {
-            if (mTaskbarOverflowView != null && overflownTasks != null
-                    && overflownTasks.size() < itemsToAddToOverflow) {
-                // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
-                overflownTasks.add(task.task1);
-                if (overflownTasks.size() == itemsToAddToOverflow) {
-                    mTaskbarOverflowView.setItems(overflownTasks);
-                }
-                continue;
-            }
-
+        final Set<GroupTask> recentTasksSet = new ArraySet<>(recentTasks);
+        final int startIndex = mIsRtl ? 0 : itemsToAddToOverflow;
+        final int endIndex =
+                mIsRtl ? recentTasks.size() - itemsToAddToOverflow : recentTasks.size();
+        for (GroupTask task : recentTasks.subList(startIndex, endIndex)) {
             // Replace any Recent views with the appropriate type if it's not already that type.
             final int expectedLayoutResId;
             boolean isCollection = false;
-            if (task.supportsMultipleTasks()) {
+            if (!(task instanceof SingleTask)) {
                 if (task.taskViewType == TaskViewType.DESKTOP) {
                     // TODO(b/316004172): use Desktop tile layout.
                     expectedLayoutResId = -1;
@@ -674,12 +675,20 @@
             }
 
             View recentIcon = null;
-            while (isNextViewInSection(GroupTask.class)) {
+            // If a task is new, we should not reuse a view so that it animates in when it is added.
+            final boolean canReuseView = !ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue()
+                    || (mPrevRecentTasks.contains(task) && !mPrevOverflowTasks.contains(task));
+            while (canReuseView && isNextViewInSection(GroupTask.class)) {
                 recentIcon = getChildAt(mNextViewIndex);
+                GroupTask tag = (GroupTask) recentIcon.getTag();
 
                 // see if the view can be reused
                 if ((recentIcon.getSourceLayoutResId() != expectedLayoutResId)
-                        || (isCollection && (recentIcon.getTag() != task))) {
+                        || (isCollection && tag != task)
+                        // Remove view corresponding to removed task so that it animates out.
+                        || (ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue()
+                                && (!recentTasksSet.contains(tag)
+                                        || overflownRecentsSet.contains(tag)))) {
                     removeAndRecycle(recentIcon);
                     recentIcon = null;
                 } else {
@@ -709,6 +718,16 @@
         while (isNextViewInSection(GroupTask.class)) {
             removeAndRecycle(getChildAt(mNextViewIndex));
         }
+
+        if (ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue() && mIsRtl && hasOverflow) {
+            if (mPrevOverflowTasks.isEmpty()) {
+                addView(mTaskbarOverflowView, mNextViewIndex);
+            }
+            mNextViewIndex++;
+        }
+
+        mPrevRecentTasks = recentTasksSet;
+        mPrevOverflowTasks = overflownRecentsSet;
     }
 
     private boolean isNextViewInSection(Class<?> tagClass) {
@@ -716,18 +735,22 @@
                 && tagClass.isInstance(getChildAt(mNextViewIndex).getTag());
     }
 
-    /** Binds the GroupTask to the BubbleTextView to be ready to present to the user. */
+    /** Binds the SingleTask 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;
+        if (!(groupTask instanceof SingleTask singleTask)) {
+            // TODO(b/343289567 and b/316004172): support app pairs and desktop mode.
+            return;
+        }
+
+        Task task = singleTask.getTask();
         // TODO(b/344038728): use FastBitmapDrawable instead of Drawable, to get disabled state
         //  while dragging.
-        Drawable taskIcon = groupTask.task1.icon;
+        Drawable taskIcon = task.icon;
         if (taskIcon != null) {
             taskIcon = taskIcon.getConstantState().newDrawable().mutate();
         }
-        btv.applyIconAndLabel(taskIcon, task1.titleDescription);
-        btv.setTag(groupTask);
+        btv.applyIconAndLabel(taskIcon, task.titleDescription);
+        btv.setTag(singleTask);
     }
 
     /**
@@ -768,7 +791,7 @@
      * aligned - returns 0.
      */
     public float getTranslationXForBubbleBarPosition(BubbleBarLocation location) {
-        if (!mControllerCallbacks.isBubbleBarEnabledInPersistentTaskbar()
+        if (!mControllerCallbacks.isBubbleBarEnabled()
                 || location == mBubbleBarLocation
                 || !mActivityContext.shouldStartAlignTaskbar()
         ) {
@@ -788,7 +811,7 @@
         int iconEnd = centerAlignIconEnd;
         if (mShouldTryStartAlign) {
             int startSpacingPx = deviceProfile.inlineNavButtonsEndSpacingPx;
-            if (mControllerCallbacks.isBubbleBarEnabledInPersistentTaskbar()
+            if (mControllerCallbacks.isBubbleBarEnabled()
                     && mBubbleBarLocation != null
                     && mActivityContext.shouldStartAlignTaskbar()) {
                 iconEnd = (int) getTaskBarIconsEndForBubbleBarLocation(mBubbleBarLocation);
@@ -849,6 +872,8 @@
             iconEnd += mAllAppsButtonTranslationOffset;
         }
 
+        mControllerCallbacks.onPreLayoutChildren();
+
         int count = getChildCount();
         for (int i = count; i > 0; i--) {
             View child = getChildAt(i - 1);
@@ -1095,43 +1120,6 @@
     }
 
     /**
-     * Maps {@code op} over all the child views.
-     */
-    public void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) {
-        // map over all the shortcuts on the taskbar
-        for (int i = 0; i < getChildCount(); i++) {
-            View item = getChildAt(i);
-            // TODO(b/344657629): Support GroupTask as well for notification dots/popup
-            if (item.getTag() instanceof ItemInfo itemInfo && op.evaluate(itemInfo, item)) {
-                return;
-            }
-        }
-    }
-
-    /**
-     * Finds the first icon to match one of the given matchers, from highest to lowest priority.
-     *
-     * @return The first match, or All Apps button if no match was found.
-     */
-    public View getFirstMatch(Predicate<ItemInfo>... matchers) {
-        for (Predicate<ItemInfo> matcher : matchers) {
-            for (int i = 0; i < getChildCount(); i++) {
-                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();
-                if (matcher.test(info)) {
-                    return item;
-                }
-            }
-        }
-        return mAllAppsButtonContainer;
-    }
-
-    /**
      * This method only works for bubble bar enabled in persistent task bar and the taskbar is start
      * aligned.
      */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
index f2bd4d0..dcb9fbf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacks.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.taskbar;
 
+import static android.window.DesktopModeFlags.ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION;
+
+import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_LONG_PRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
 import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TASKBAR_OVERFLOW;
@@ -23,6 +26,7 @@
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.view.GestureDetector;
+import android.view.HapticFeedbackConstants;
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.View;
@@ -34,7 +38,6 @@
 import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
 import com.android.launcher3.util.DisplayController;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.wm.shell.Flags;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
 
 /**
@@ -64,7 +67,16 @@
         InteractionJankMonitorWrapper.begin(v, Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS,
                 /* tag= */ "TASKBAR_BUTTON");
         mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP);
-        mControllers.taskbarAllAppsController.toggle();
+        if (DisplayController.showLockedTaskbarOnHome(mActivity)
+                || DisplayController.showDesktopTaskbarForFreeformDisplay(mActivity)) {
+            // If the taskbar can be shown on the home screen, use mAllAppsToggler to toggle all
+            // apps, which will toggle the launcher activity all apps when on home screen.
+            // TODO(b/395913143): Reconsider this if a gap in taskbar all apps functionality that
+            //  prevents users to drag items to workspace is addressed.
+            mControllers.uiController.toggleAllApps(false);
+        } else {
+            mControllers.taskbarAllAppsController.toggle();
+        }
     }
 
     /** Trigger All Apps button long click action. */
@@ -110,6 +122,13 @@
         return new TaskbarHoverToolTipController(mActivity, mTaskbarView, icon);
     }
 
+    /** Callback invoked before Taskbar icons are laid out. */
+    void onPreLayoutChildren() {
+        if (enableTaskbarPinning() && ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue()) {
+            mControllers.taskbarViewController.updateTaskbarIconTranslationXForPinning();
+        }
+    }
+
     /**
      * Notifies launcher to update icon alignment.
      */
@@ -150,10 +169,9 @@
                 .orElse(0f);
     }
 
-    /** Returns true if bubble bar controllers present and enabled in persistent taskbar. */
-    public boolean isBubbleBarEnabledInPersistentTaskbar() {
-        return Flags.enableBubbleBarInPersistentTaskBar()
-                && mControllers.bubbleControllers.isPresent();
+    /** Returns true if bubble bar controllers are present. */
+    public boolean isBubbleBarEnabled() {
+        return mControllers.bubbleControllers.isPresent();
     }
 
     /** Returns on click listener for the taskbar overflow view. */
@@ -223,15 +241,19 @@
 
         @Override
         public void onLongPress(@NonNull MotionEvent event) {
-            maybeShowPinningView(event);
+            if (maybeShowPinningView(event)) {
+                mTaskbarView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
+            }
         }
 
-        private void maybeShowPinningView(@NonNull MotionEvent event) {
+        /** Returns true if the taskbar pinning popup view was shown for {@code event}. */
+        private boolean maybeShowPinningView(@NonNull MotionEvent event) {
             if (!DisplayController.isPinnedTaskbar(mActivity) || mTaskbarView.isEventOverAnyItem(
                     event)) {
-                return;
+                return false;
             }
             mControllers.taskbarPinningController.showPinningView(mTaskbarView, event.getRawX());
+            return true;
         }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 2ffa00f..a80e2c4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -15,8 +15,16 @@
  */
 package com.android.launcher3.taskbar;
 
+import static android.animation.LayoutTransition.APPEARING;
+import static android.animation.LayoutTransition.CHANGE_APPEARING;
+import static android.animation.LayoutTransition.CHANGE_DISAPPEARING;
+import static android.animation.LayoutTransition.DISAPPEARING;
+import static android.window.DesktopModeFlags.ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION;
+
+import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.app.animation.Interpolators.FINAL_FRAME;
 import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.BubbleTextView.LINE_INDICATOR_ANIM_DURATION;
 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.Flags.taskbarOverflow;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
@@ -33,6 +41,7 @@
 import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_IN_ANIM_ALPHA_DURATION_MS;
 import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_OUT_ANIM_POSITION_DURATION_MS;
 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
+import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_BUBBLE_BAR_ANIM;
 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_NAV_BAR_ANIM;
 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM;
 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_PINNING_ANIM;
@@ -40,13 +49,17 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
+import android.animation.LayoutTransition;
+import android.animation.LayoutTransition.TransitionListener;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.graphics.Rect;
+import android.util.FloatProperty;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.animation.Interpolator;
 
 import androidx.annotation.Nullable;
@@ -70,15 +83,20 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.TaskItemInfo;
 import com.android.launcher3.taskbar.bubbles.BubbleBarController;
+import com.android.launcher3.taskbar.bubbles.BubbleControllers;
+import com.android.launcher3.taskbar.customization.TaskbarAllAppsButtonContainer;
+import com.android.launcher3.taskbar.customization.TaskbarDividerContainer;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.MultiPropertyFactory;
+import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
 import com.android.launcher3.util.MultiTranslateDelegate;
 import com.android.launcher3.util.MultiValueAlpha;
+import com.android.launcher3.util.SandboxContext;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
 import com.android.systemui.shared.recents.model.Task;
-import com.android.wm.shell.Flags;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
 
 import java.io.PrintWriter;
@@ -97,6 +115,8 @@
 
     private static final Runnable NO_OP = () -> { };
 
+    public static long TRANSLATION_X_FOR_BUBBLEBAR_ANIM_DURATION_MS = 250;
+
     public static final int ALPHA_INDEX_HOME = 0;
     public static final int ALPHA_INDEX_KEYGUARD = 1;
     public static final int ALPHA_INDEX_STASH = 2;
@@ -104,9 +124,10 @@
     public static final int ALPHA_INDEX_NOTIFICATION_EXPANDED = 4;
     public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 5;
     public static final int ALPHA_INDEX_SMALL_SCREEN = 6;
-
     public static final int ALPHA_INDEX_BUBBLE_BAR = 7;
-    private static final int NUM_ALPHA_CHANNELS = 8;
+    public static final int ALPHA_INDEX_RECREATE = 8;
+
+    private static final int NUM_ALPHA_CHANNELS = 9;
 
     /** Only used for animation purposes, to position the divider between two item indices. */
     public static final float DIVIDER_VIEW_POSITION_OFFSET = 0.5f;
@@ -114,9 +135,15 @@
     /** Used if an unexpected edge case is hit in {@link #getPositionInHotseat}. */
     private static final float ERROR_POSITION_IN_HOTSEAT_NOT_FOUND = -100;
 
-    private static boolean sEnableModelLoadingForTests = true;
+    private static final int TRANSITION_DELAY = 50;
+    private static final int TRANSITION_DEFAULT_DURATION = 500;
+    private static final int TRANSITION_FADE_IN_DURATION = 167;
+    private static final int TRANSITION_FADE_OUT_DURATION = 83;
+    private static final int APPEARING_LINE_INDICATOR_ANIM_DELAY =
+            TRANSITION_DEFAULT_DURATION - LINE_INDICATOR_ANIM_DURATION;
 
     private final TaskbarActivityContext mActivity;
+    private @Nullable TaskbarDragLayerController mDragLayerController;
     private final TaskbarView mTaskbarView;
     private final MultiValueAlpha mTaskbarIconAlpha;
     private final AnimatedFloat mTaskbarIconScaleForStash = new AnimatedFloat(this::updateScale);
@@ -129,15 +156,22 @@
             this::updateTaskbarIconsScale);
 
     private final AnimatedFloat mTaskbarIconTranslationXForPinning = new AnimatedFloat(
-            this::updateTaskbarIconTranslationXForPinning);
+            () -> updateTaskbarIconTranslationXForPinning());
 
     private final AnimatedFloat mIconsTranslationXForNavbar = new AnimatedFloat(
             this::updateTranslationXForNavBar);
 
+    private final AnimatedFloat mTranslationXForBubbleBar = new AnimatedFloat(
+            this::updateTranslationXForBubbleBar);
+
     @Nullable
     private Animator mTaskbarShiftXAnim;
     @Nullable
     private BubbleBarLocation mCurrentBubbleBarLocation;
+    @Nullable
+    private BubbleControllers mBubbleControllers = null;
+    @Nullable
+    private ObjectAnimator mTranslationXAnimation;
 
     private final AnimatedFloat mTaskbarIconTranslationYForPinning = new AnimatedFloat(
             this::updateTranslationY);
@@ -158,10 +192,13 @@
 
     private final View.OnLayoutChangeListener mTaskbarViewLayoutChangeListener =
             (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
-                updateTaskbarIconTranslationXForPinning();
-                if (BubbleBarController.isBubbleBarEnabled()) {
-                    mControllers.navbarButtonsViewController.onLayoutsUpdated();
+                if (!ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue()) {
+                    // update shiftX is handled with the animation at the end of the method
+                    updateTaskbarIconTranslationXForPinning(/* updateShiftXForBubbleBar = */ false);
                 }
+                if (mBubbleControllers == null) return;
+                mControllers.navbarButtonsViewController.onLayoutsUpdated();
+                adjustTaskbarXForBubbleBar();
             };
 
     // Animation to align icons with Launcher, created lazily. This allows the controller to be
@@ -205,11 +242,24 @@
         mIsRtl = Utilities.isRtl(mTaskbarView.getResources());
         mTaskbarLeftRightMargin = mActivity.getResources().getDimensionPixelSize(
                 R.dimen.transient_taskbar_padding);
-
     }
 
-    public void init(TaskbarControllers controllers) {
+    /**
+     * Init of taskbar view controller.
+     */
+    public void init(TaskbarControllers controllers, AnimatorSet startAnimation) {
         mControllers = controllers;
+        controllers.bubbleControllers.ifPresent(bc -> mBubbleControllers = bc);
+
+        if (startAnimation != null) {
+            MultiPropertyFactory<View>.MultiProperty multiProperty =
+                    mTaskbarIconAlpha.get(ALPHA_INDEX_RECREATE);
+            multiProperty.setValue(0f);
+            Animator animator = multiProperty.animateToValue(1f);
+            animator.setInterpolator(EMPHASIZED);
+            startAnimation.play(animator);
+        }
+
         mTaskbarView.init(TaskbarViewCallbacksFactory.newInstance(mActivity).create(
                 mActivity, mControllers, mTaskbarView));
         mTaskbarView.getLayoutParams().height = mActivity.isPhoneMode()
@@ -225,7 +275,8 @@
         mTaskbarIconTranslationXForPinning.updateValue(pinningValue);
 
         mModelCallbacks.init(controllers);
-        if (mActivity.isUserSetupComplete() && sEnableModelLoadingForTests) {
+        if (mActivity.isUserSetupComplete()
+                && !(mActivity.getApplicationContext() instanceof SandboxContext)) {
             // Only load the callbacks if user setup is completed
             controllers.runAfterInit(() -> LauncherAppState.getInstance(mActivity).getModel()
                     .addCallbacksAndLoad(mModelCallbacks));
@@ -234,7 +285,7 @@
                 controllers.navbarButtonsViewController.getTaskbarNavButtonTranslationY();
         mTaskbarNavButtonTranslationYForInAppDisplay = controllers.navbarButtonsViewController
                 .getTaskbarNavButtonTranslationYForInAppDisplay();
-
+        mDragLayerController = controllers.taskbarDragLayerController;
         mActivity.addOnDeviceProfileChangeListener(mDeviceProfileChangeListener);
 
         if (ENABLE_TASKBAR_NAVBAR_UNIFICATION) {
@@ -252,24 +303,59 @@
     @Override
     public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
         updateCurrentBubbleBarLocation(location);
-        if (!shouldMoveTaskbarOnBubbleBarLocationUpdate()) return;
-        cancelTaskbarShiftAnimation();
-        // reset translation x, taskbar will position icons with the updated location
-        mIconsTranslationXForNavbar.updateValue(0);
-        mTaskbarView.onBubbleBarLocationUpdated(location);
+        if (mActivity.isTransientTaskbar()) {
+            translateTaskbarXForBubbleBar(/* animate= */ false);
+        } else if (mActivity.shouldStartAlignTaskbar()) {
+            cancelTaskbarShiftAnimation();
+            // reset translation x, taskbar will position icons with the updated location
+            mIconsTranslationXForNavbar.updateValue(0);
+            mTaskbarView.onBubbleBarLocationUpdated(location);
+        }
     }
 
     /** Animates start aligned taskbar accordingly to the bubble bar position. */
     @Override
     public void onBubbleBarLocationAnimated(BubbleBarLocation location) {
-        if (!updateCurrentBubbleBarLocation(location)
-                || !shouldMoveTaskbarOnBubbleBarLocationUpdate()) {
-            return;
+        boolean locationUpdated = updateCurrentBubbleBarLocation(location);
+        if (mActivity.isTransientTaskbar()) {
+            translateTaskbarXForBubbleBar(/* animate= */ true);
+        } else if (locationUpdated && mActivity.shouldStartAlignTaskbar()) {
+            cancelTaskbarShiftAnimation();
+            float translationX = mTaskbarView.getTranslationXForBubbleBarPosition(location);
+            mTaskbarShiftXAnim = createTaskbarIconsShiftAnimator(translationX);
+            mTaskbarShiftXAnim.start();
         }
-        cancelTaskbarShiftAnimation();
-        float translationX = mTaskbarView.getTranslationXForBubbleBarPosition(location);
-        mTaskbarShiftXAnim = createTaskbarIconsShiftAnimator(translationX);
-        mTaskbarShiftXAnim.start();
+    }
+
+    private void translateTaskbarXForBubbleBar(boolean animate) {
+        cancelCurrentTranslationXAnimation();
+        if (!mActivity.isTransientTaskbar()) return;
+        int shiftX = getTransientTaskbarShiftXForBubbleBar();
+        if (animate) {
+            mTranslationXAnimation = mTranslationXForBubbleBar.animateToValue(shiftX);
+            mTranslationXAnimation.setInterpolator(EMPHASIZED);
+            mTranslationXAnimation.setDuration(TRANSLATION_X_FOR_BUBBLEBAR_ANIM_DURATION_MS);
+            mTranslationXAnimation.start();
+        } else {
+            mTranslationXForBubbleBar.updateValue(shiftX);
+        }
+    }
+
+    private void cancelCurrentTranslationXAnimation() {
+        if (mTranslationXAnimation != null) {
+            if (mTranslationXAnimation.isRunning()) {
+                mTranslationXAnimation.cancel();
+            }
+            mTranslationXAnimation = null;
+        }
+    }
+
+    private int getTransientTaskbarShiftXForBubbleBar() {
+        if (mBubbleControllers == null || !mActivity.isTransientTaskbar()) {
+            return 0;
+        }
+        return mBubbleControllers.bubbleBarViewController
+                .getTransientTaskbarTranslationXForBubbleBar(mCurrentBubbleBarLocation);
     }
 
     /** Updates the mCurrentBubbleBarLocation, returns {@code} true if location is updated. */
@@ -282,14 +368,6 @@
         }
     }
 
-    /** Returns whether taskbar should be moved on the bubble bar location update. */
-    private boolean shouldMoveTaskbarOnBubbleBarLocationUpdate() {
-        return Flags.enableBubbleBarInPersistentTaskBar()
-                && mControllers.bubbleControllers.isPresent()
-                && mActivity.shouldStartAlignTaskbar()
-                && mActivity.isThreeButtonNav();
-    }
-
     private void cancelTaskbarShiftAnimation() {
         if (mTaskbarShiftXAnim != null) {
             mTaskbarShiftXAnim.cancel();
@@ -303,6 +381,15 @@
         mTaskbarView.announceAccessibilityChanges();
     }
 
+    /**
+     * Called with destroying Taskbar with animation.
+     */
+    public void onDestroyAnimation(AnimatorSet animatorSet) {
+        animatorSet.play(
+                mTaskbarIconAlpha.get(TaskbarViewController.ALPHA_INDEX_RECREATE).animateToValue(
+                        0f));
+    }
+
     public void onDestroy() {
         if (enableTaskbarPinning()) {
             mTaskbarView.removeOnLayoutChangeListener(mTaskbarViewLayoutChangeListener);
@@ -433,17 +520,27 @@
         }
     }
 
-    private void updateTaskbarIconTranslationXForPinning() {
+    void updateTaskbarIconTranslationXForPinning() {
+        updateTaskbarIconTranslationXForPinning(/* updateShiftXForBubbleBar = */ true);
+    }
+
+    void updateTaskbarIconTranslationXForPinning(boolean updateShiftXForBubbleBar) {
         View[] iconViews = mTaskbarView.getIconViews();
         float scale = mTaskbarIconTranslationXForPinning.value;
         float transientTaskbarAllAppsOffset = mActivity.getResources().getDimension(
                 mTaskbarView.getAllAppsButtonContainer().getAllAppsButtonTranslationXOffset(true));
         float persistentTaskbarAllAppsOffset = mActivity.getResources().getDimension(
                 mTaskbarView.getAllAppsButtonContainer().getAllAppsButtonTranslationXOffset(false));
-
+        if (mBubbleControllers != null && updateShiftXForBubbleBar) {
+            cancelCurrentTranslationXAnimation();
+            int translationXForTransientTaskbar = mBubbleControllers.bubbleBarViewController
+                    .getTransientTaskbarTranslationXForBubbleBar(mCurrentBubbleBarLocation);
+            float currentTranslationXForTransientTaskbar = mapRange(scale,
+                    translationXForTransientTaskbar, 0);
+            mTranslationXForBubbleBar.updateValue(currentTranslationXForTransientTaskbar);
+        }
         float allAppIconTranslateRange = mapRange(scale, transientTaskbarAllAppsOffset,
                 persistentTaskbarAllAppsOffset);
-
         // Task icons are laid out so the taskbar content is centered. The taskbar width (used for
         // centering taskbar icons) depends on the all apps button X translation, and is different
         // for persistent and transient taskbar. If the offset used for current taskbar layout is
@@ -535,13 +632,23 @@
     }
 
     private void updateTranslationXForNavBar() {
+        updateIconViewsTranslationX(INDEX_NAV_BAR_ANIM, mIconsTranslationXForNavbar.value);
+    }
+
+    private void updateTranslationXForBubbleBar() {
+        float translationX = mTranslationXForBubbleBar.value;
+        updateIconViewsTranslationX(INDEX_BUBBLE_BAR_ANIM, translationX);
+        if (mDragLayerController != null) {
+            mDragLayerController.setTranslationXForBubbleBar(translationX);
+        }
+    }
+
+    private void updateIconViewsTranslationX(int translationXChannel, float translationX) {
         View[] iconViews = mTaskbarView.getIconViews();
-        float translationX = mIconsTranslationXForNavbar.value;
-        for (int iconIndex = 0; iconIndex < iconViews.length; iconIndex++) {
-            View iconView = iconViews[iconIndex];
+        for (View iconView : iconViews) {
             MultiTranslateDelegate translateDelegate =
                     ((Reorderable) iconView).getTranslateDelegate();
-            translateDelegate.getTranslationX(INDEX_NAV_BAR_ANIM).setValue(translationX);
+            translateDelegate.getTranslationX(translationXChannel).setValue(translationX);
         }
     }
 
@@ -632,11 +739,22 @@
     public void updateIconViewsRunningStates() {
         for (View iconView : getIconViews()) {
             if (iconView instanceof BubbleTextView btv) {
-                btv.updateRunningState(getRunningAppState(btv));
+                updateRunningState(btv);
+                if (shouldUpdateIconContentDescription(btv)) {
+                    btv.setContentDescription(
+                            btv.getContentDescription() + " " + btv.getIconStateDescription());
+                }
             }
         }
     }
 
+    private boolean shouldUpdateIconContentDescription(BubbleTextView btv) {
+        boolean isInDesktopMode = mControllers.taskbarDesktopModeController.isInDesktopMode();
+        boolean isAllAppsButton = btv instanceof TaskbarAllAppsButtonContainer;
+        boolean isDividerButton = btv instanceof TaskbarDividerContainer;
+        return isInDesktopMode && !isAllAppsButton && !isDividerButton;
+    }
+
     /**
      * @return A set of Task ids of running apps that are pinned in the taskbar.
      */
@@ -655,15 +773,19 @@
         return pinnedAppsWithTasks;
     }
 
+    private void updateRunningState(BubbleTextView btv) {
+        btv.updateRunningState(getRunningAppState(btv), mTaskbarView.getLayoutTransition() != null);
+    }
+
     private BubbleTextView.RunningAppState getRunningAppState(BubbleTextView btv) {
         Object tag = btv.getTag();
         if (tag instanceof TaskItemInfo itemInfo) {
             return mControllers.taskbarRecentAppsController.getRunningAppState(
                     itemInfo.getTaskId());
         }
-        if (tag instanceof GroupTask groupTask && !groupTask.hasMultipleTasks()) {
+        if (tag instanceof SingleTask singleTask) {
             return mControllers.taskbarRecentAppsController.getRunningAppState(
-                    groupTask.task1.key.id);
+                    singleTask.getTask().key.id);
         }
         return BubbleTextView.RunningAppState.NOT_RUNNING;
     }
@@ -795,6 +917,13 @@
         if (mTaskbarView.updateMaxNumIcons()) {
             commitRunningAppsToUI();
         }
+        adjustTaskbarXForBubbleBar();
+    }
+
+    private void adjustTaskbarXForBubbleBar() {
+        if (mBubbleControllers != null && mActivity.isTransientTaskbar()) {
+            translateTaskbarXForBubbleBar(/* animate= */ true);
+        }
     }
 
     /**
@@ -825,7 +954,17 @@
         setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, interpolator);
         setter.setFloat(mTaskbarNavButtonTranslationY, VALUE, -offsetY, interpolator);
         setter.setFloat(mTaskbarNavButtonTranslationYForInAppDisplay, VALUE, offsetY, interpolator);
-
+        if (mBubbleControllers != null
+                && mCurrentBubbleBarLocation != null
+                && mActivity.isTransientTaskbar()) {
+            int offsetX = mBubbleControllers.bubbleBarViewController
+                    .getTransientTaskbarTranslationXForBubbleBar(mCurrentBubbleBarLocation);
+            if (offsetX != 0) {
+                // if taskbar should be adjusted for the bubble bar adjust the taskbar translation
+                mTranslationXForBubbleBar.updateValue(offsetX);
+                setter.setFloat(mTranslationXForBubbleBar, VALUE, 0, interpolator);
+            }
+        }
         int collapsedHeight = mActivity.getDefaultTaskbarWindowSize();
         int expandedHeight = Math.max(collapsedHeight, taskbarDp.taskbarHeight + offsetY);
         setter.addOnFrameListener(anim -> mActivity.setTaskbarWindowSize(
@@ -1026,8 +1165,8 @@
     }
 
     private boolean bubbleBarHasBubbles() {
-        return mControllers.bubbleControllers.isPresent()
-                && mControllers.bubbleControllers.get().bubbleBarViewController.hasBubbles();
+        return mBubbleControllers != null
+                && mBubbleControllers.bubbleBarViewController.hasBubbles();
     }
 
     public void onRotationChanged(DeviceProfile deviceProfile) {
@@ -1048,11 +1187,8 @@
         mTaskbarNavButtonTranslationY.updateValue(-deviceProfile.getTaskbarOffsetY());
     }
 
-    /**
-     * Maps the given operator to all the top-level children of TaskbarView.
-     */
-    public void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) {
-        mTaskbarView.mapOverItems(op);
+    public LauncherBindableItemsContainer getContent() {
+        return mModelCallbacks;
     }
 
     /**
@@ -1062,8 +1198,8 @@
      * 3) All Apps button
      */
     public View getFirstIconMatch(Predicate<ItemInfo> matcher) {
-        Predicate<ItemInfo> collectionMatcher = ItemInfoMatcher.forFolderMatch(matcher);
-        return mTaskbarView.getFirstMatch(matcher, collectionMatcher);
+        View icon = mModelCallbacks.getFirstMatch(matcher, ItemInfoMatcher.forFolderMatch(matcher));
+        return icon != null ? icon : mTaskbarView.getAllAppsButtonContainer();
     }
 
     /**
@@ -1077,6 +1213,99 @@
     /** Called when there's a change in running apps to update the UI. */
     public void commitRunningAppsToUI() {
         mModelCallbacks.commitRunningAppsToUI();
+        if (ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue()
+                && mTaskbarView.getLayoutTransition() == null) {
+            // Set up after the first commit so that the initial recents do not animate (janky).
+            mTaskbarView.setLayoutTransition(createLayoutTransitionForRunningApps());
+        }
+    }
+
+    private LayoutTransition createLayoutTransitionForRunningApps() {
+        LayoutTransition layoutTransition = new LayoutTransition();
+        layoutTransition.setDuration(TRANSITION_DEFAULT_DURATION);
+        layoutTransition.addTransitionListener(new TransitionListener() {
+
+            @Override
+            public void startTransition(
+                    LayoutTransition transition, ViewGroup container, View view, int type) {
+                if (type == APPEARING) {
+                    view.setAlpha(0f);
+                    view.setScaleX(0f);
+                    view.setScaleY(0f);
+                    if (view instanceof BubbleTextView btv) {
+                        // Defer so that app is mostly scaled in before showing indicator.
+                        btv.setLineIndicatorAnimStartDelay(APPEARING_LINE_INDICATOR_ANIM_DELAY);
+                    }
+                } else if (type == DISAPPEARING && view instanceof BubbleTextView btv) {
+                    // Running state updates happen after removing this view, so update it here.
+                    updateRunningState(btv);
+                }
+            }
+
+            @Override
+            public void endTransition(
+                    LayoutTransition transition, ViewGroup container, View view, int type) {
+                if (type == APPEARING && view instanceof BubbleTextView btv) {
+                    btv.setLineIndicatorAnimStartDelay(0);
+                }
+            }
+        });
+
+        // Appearing.
+        AnimatorSet appearingSet = new AnimatorSet();
+        Animator appearingAlphaAnimator = ObjectAnimator.ofFloat(null, "alpha", 0f, 1f);
+        appearingAlphaAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0f,
+                (float) TRANSITION_FADE_IN_DURATION / TRANSITION_DEFAULT_DURATION));
+        Animator appearingScaleAnimator = ObjectAnimator.ofFloat(null, SCALE_PROPERTY, 0f, 1f);
+        appearingScaleAnimator.setInterpolator(EMPHASIZED);
+        appearingSet.playTogether(appearingAlphaAnimator, appearingScaleAnimator);
+        layoutTransition.setAnimator(APPEARING, appearingSet);
+        layoutTransition.setStartDelay(APPEARING, TRANSITION_DELAY);
+
+        // Disappearing.
+        AnimatorSet disappearingSet = new AnimatorSet();
+        Animator disappearingAlphaAnimator = ObjectAnimator.ofFloat(null, "alpha", 1f, 0f);
+        disappearingAlphaAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR,
+                (float) TRANSITION_DELAY / TRANSITION_DEFAULT_DURATION,
+                (float) (TRANSITION_DELAY + TRANSITION_FADE_OUT_DURATION)
+                        / TRANSITION_DEFAULT_DURATION));
+        Animator disappearingScaleAnimator = ObjectAnimator.ofFloat(null, SCALE_PROPERTY, 1f, 0f);
+        disappearingScaleAnimator.setInterpolator(EMPHASIZED);
+        disappearingSet.playTogether(disappearingAlphaAnimator, disappearingScaleAnimator);
+        layoutTransition.setAnimator(DISAPPEARING, disappearingSet);
+
+        // Change transitions.
+        FloatProperty<View> translateXPinning = new FloatProperty<>("translateXPinning") {
+            @Override
+            public void setValue(View view, float value) {
+                getTranslationXForPinning(view).setValue(value);
+            }
+
+            @Override
+            public Float get(View view) {
+                return getTranslationXForPinning(view).getValue();
+            }
+
+            private MultiProperty getTranslationXForPinning(View view) {
+                return ((Reorderable) view).getTranslateDelegate()
+                        .getTranslationX(INDEX_TASKBAR_PINNING_ANIM);
+            }
+        };
+        AnimatorSet changeSet = new AnimatorSet();
+        changeSet.playTogether(
+                layoutTransition.getAnimator(CHANGE_APPEARING),
+                ObjectAnimator.ofFloat(null, translateXPinning, 0f, 1f));
+
+        // Change appearing.
+        layoutTransition.setAnimator(CHANGE_APPEARING, changeSet);
+        layoutTransition.setInterpolator(CHANGE_APPEARING, EMPHASIZED);
+
+        // Change disappearing.
+        layoutTransition.setAnimator(CHANGE_DISAPPEARING, changeSet);
+        layoutTransition.setInterpolator(CHANGE_DISAPPEARING, EMPHASIZED);
+        layoutTransition.setStartDelay(CHANGE_DISAPPEARING, TRANSITION_DELAY);
+
+        return layoutTransition;
     }
 
     /**
@@ -1116,17 +1345,11 @@
         mModelCallbacks.dumpLogs(prefix + "\t", pw);
     }
 
-    /** Enables model loading for tests. */
-    @VisibleForTesting
-    public static void enableModelLoadingForTests(boolean enable) {
-        sEnableModelLoadingForTests = enable;
-    }
-
     private ObjectAnimator createTaskbarIconsShiftAnimator(float translationX) {
         ObjectAnimator animator = mIconsTranslationXForNavbar.animateToValue(translationX);
         animator.setStartDelay(FADE_OUT_ANIM_POSITION_DURATION_MS);
         animator.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS);
-        animator.setInterpolator(Interpolators.EMPHASIZED);
+        animator.setInterpolator(EMPHASIZED);
         return animator;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
index 07d86e4..6c55b28 100644
--- a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -35,7 +35,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.function.Predicate;
 /**
  * Handles the all apps overlay window initialization, updates, and its data.
  * <p>
@@ -120,13 +119,6 @@
         mZeroStateSearchSuggestions = zeroStateSearchSuggestions;
     }
 
-    /** Updates the current notification dots. */
-    public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
-        if (mAppsView != null) {
-            mAppsView.getAppsStore().updateNotificationDots(updatedDots);
-        }
-    }
-
     /** Toggles visibility of {@link TaskbarAllAppsContainerView} in the overlay window. */
     public void toggle() {
         toggle(false);
@@ -141,6 +133,7 @@
         if (isOpen()) {
             mSlideInView.close(true);
         } else {
+            mControllers.taskbarPopupController.maybeCloseMultiInstanceMenu();
             show(true, showKeyboard);
         }
     }
@@ -217,6 +210,11 @@
         mAppsView = null;
     }
 
+    @Nullable
+    public TaskbarAllAppsContainerView getAppsView() {
+        return mAppsView;
+    }
+
     @VisibleForTesting
     public int getTaskbarAllAppsTopPadding() {
         // Allow null-pointer since this should only be null if the apps view is not showing.
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
index 249773d..97be2e8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -44,10 +44,13 @@
     private val arrowVisibleHeight: Float
 
     private val strokeAlpha: Int
+    private val strokeColor: Int
+    private val strokeColorDropTarget: Int
     private val shadowAlpha: Int
     private val shadowBlur: Float
     private val keyShadowDistance: Float
     private var arrowHeightFraction = 1f
+    private var isShowingDropTarget: Boolean = false
 
     var arrowPositionX: Float = 0f
         private set
@@ -100,7 +103,9 @@
         fillPaint.flags = Paint.ANTI_ALIAS_FLAG
         fillPaint.style = Paint.Style.FILL
         // configure stroke paint
-        strokePaint.color = context.getColor(R.color.taskbar_stroke)
+        strokeColor = context.getColor(R.color.taskbar_stroke)
+        strokeColorDropTarget = context.getColor(com.android.internal.R.color.system_primary_fixed)
+        strokePaint.color = strokeColor
         strokePaint.flags = Paint.ANTI_ALIAS_FLAG
         strokePaint.style = Paint.Style.STROKE
         strokePaint.strokeWidth = res.getDimension(R.dimen.transient_taskbar_stroke_width)
@@ -235,9 +240,25 @@
         return max(0f, getScaledArrowHeight() - (arrowHeight - arrowVisibleHeight))
     }
 
+    /** Set whether the background should show the drop target */
+    fun showDropTarget(isDropTarget: Boolean) {
+        if (isShowingDropTarget == isDropTarget) {
+            return
+        }
+        isShowingDropTarget = isDropTarget
+        val strokeColor = if (isDropTarget) strokeColorDropTarget else strokeColor
+        val alpha = if (isDropTarget) DRAG_STROKE_ALPHA else strokeAlpha
+        strokePaint.color = strokeColor
+        strokePaint.alpha = alpha
+        invalidateSelf()
+    }
+
+    fun isShowingDropTarget() = isShowingDropTarget
+
     companion object {
         private const val DARK_THEME_STROKE_ALPHA = 51
         private const val LIGHT_THEME_STROKE_ALPHA = 41
+        private const val DRAG_STROKE_ALPHA = 255
         private const val DARK_THEME_SHADOW_ALPHA = 51
         private const val LIGHT_THEME_SHADOW_ALPHA = 25
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 5b3c233..5ddbe03 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -19,8 +19,7 @@
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
@@ -35,6 +34,8 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.taskbar.TaskbarSharedState;
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
 import com.android.launcher3.util.Executors.SimpleThreadFactory;
@@ -91,10 +92,9 @@
     private static final long MASK_HIDE_BUBBLE_BAR = SYSUI_STATE_BOUNCER_SHOWING
             | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
             | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
-            | SYSUI_STATE_IME_SHOWING
+            | SYSUI_STATE_IME_VISIBLE
             | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
-            | SYSUI_STATE_QUICK_SETTINGS_EXPANDED
-            | SYSUI_STATE_IME_SWITCHER_SHOWING;
+            | SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 
     private static final long MASK_HIDE_HANDLE_VIEW = SYSUI_STATE_BOUNCER_SHOWING
             | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
@@ -107,6 +107,7 @@
     private final Context mContext;
     private final BubbleBarView mBarView;
     private final ArrayMap<String, BubbleBarBubble> mBubbles = new ArrayMap<>();
+    private final ArrayMap<String, BubbleBarBubble> mSuppressedBubbles = new ArrayMap<>();
 
     private static final Executor BUBBLE_STATE_EXECUTOR = Executors.newSingleThreadExecutor(
             new SimpleThreadFactory("BubbleStateUpdates-", THREAD_PRIORITY_BACKGROUND));
@@ -188,6 +189,10 @@
             }
         });
         mSharedState.bubbleInfoItems = Arrays.asList(bubbleInfoItems);
+        mSharedState.suppressedBubbleInfoItems = new ArrayList<>(mSuppressedBubbles.size());
+        for (int i = 0; i < mSuppressedBubbles.size(); i++) {
+            mSharedState.suppressedBubbleInfoItems.add(mSuppressedBubbles.valueAt(i).getInfo());
+        }
     }
 
     /** Initializes controllers. */
@@ -233,7 +238,7 @@
 
         boolean sysuiLocked = (flags & MASK_SYSUI_LOCKED) != 0;
         mBubbleStashController.setSysuiLocked(sysuiLocked);
-        mIsImeVisible = (flags & SYSUI_STATE_IME_SHOWING) != 0;
+        mIsImeVisible = (flags & SYSUI_STATE_IME_VISIBLE) != 0;
         if (mIsImeVisible) {
             mBubbleBarViewController.onImeVisible();
         }
@@ -290,7 +295,11 @@
         if (sharedState.bubbleBarLocation != null) {
             updateBubbleBarLocationInternal(sharedState.bubbleBarLocation);
         }
-        List<BubbleInfo> bubbleInfos = sharedState.bubbleInfoItems;
+        restoreSavedBubbles(sharedState.bubbleInfoItems);
+        restoreSuppressed(sharedState.suppressedBubbleInfoItems);
+    }
+
+    private void restoreSavedBubbles(List<BubbleInfo> bubbleInfos) {
         if (bubbleInfos == null || bubbleInfos.isEmpty()) return;
         // Iterate in reverse because new bubbles are added in front and the list is in order.
         for (int i = bubbleInfos.size() - 1; i >= 0; i--) {
@@ -304,6 +313,18 @@
         }
     }
 
+    private void restoreSuppressed(List<BubbleInfo> bubbleInfos) {
+        if (bubbleInfos == null || bubbleInfos.isEmpty()) return;
+        for (BubbleInfo bubbleInfo : bubbleInfos.reversed()) {
+            BubbleBarBubble bb = mBubbleCreator.populateBubble(mContext, bubbleInfo,
+                    mBarView, /* existingBubble= */
+                    null);
+            if (bb != null) {
+                mSuppressedBubbles.put(bb.getKey(), bb);
+            }
+        }
+    }
+
     private void applyViewChanges(BubbleBarViewUpdate update) {
         final boolean isCollapsed = (update.expandedChanged && !update.expanded)
                 || (!update.expandedChanged && !mBubbleBarViewController.isExpanded());
@@ -318,28 +339,43 @@
             // clear restored state
             mBubbleBarViewController.removeAllBubbles();
             mBubbles.clear();
+            mBubbleBarViewController.showOverflow(update.showOverflow);
         }
 
+        if (update.addedBubble != null) {
+            mBubbles.put(update.addedBubble.getKey(), update.addedBubble);
+        }
         BubbleBarBubble bubbleToSelect = null;
-
+        if (update.selectedBubbleKey != null) {
+            if (mSelectedBubble == null
+                    || !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
+                BubbleBarBubble newlySelected = mBubbles.get(update.selectedBubbleKey);
+                if (newlySelected != null) {
+                    bubbleToSelect = newlySelected;
+                } else {
+                    Log.w(TAG, "trying to select bubble that doesn't exist:"
+                            + update.selectedBubbleKey);
+                }
+            }
+        }
         if (Flags.enableOptionalBubbleOverflow()
                 && update.showOverflowChanged && !update.showOverflow && update.addedBubble != null
-                && update.removedBubbles.isEmpty()) {
+                && update.removedBubbles.isEmpty()
+                && !mBubbles.isEmpty()) {
             // A bubble was added from the overflow (& now it's empty / not showing)
-            mBubbles.put(update.addedBubble.getKey(), update.addedBubble);
-            mBubbleBarViewController.removeOverflowAndAddBubble(update.addedBubble);
+            mBubbleBarViewController.removeOverflowAndAddBubble(update.addedBubble, bubbleToSelect);
         } else if (update.addedBubble != null && update.removedBubbles.size() == 1) {
             // we're adding and removing a bubble at the same time. handle this as a single update.
             RemovedBubble removedBubble = update.removedBubbles.get(0);
             BubbleBarBubble bubbleToRemove = mBubbles.remove(removedBubble.getKey());
-            mBubbles.put(update.addedBubble.getKey(), update.addedBubble);
             boolean showOverflow = update.showOverflowChanged && update.showOverflow;
             if (bubbleToRemove != null) {
                 mBubbleBarViewController.addBubbleAndRemoveBubble(update.addedBubble,
-                        bubbleToRemove, isExpanding, suppressAnimation, showOverflow);
+                        bubbleToRemove, bubbleToSelect, isExpanding, suppressAnimation,
+                        showOverflow);
             } else {
                 mBubbleBarViewController.addBubble(update.addedBubble, isExpanding,
-                        suppressAnimation);
+                        suppressAnimation, bubbleToSelect);
                 Log.w(TAG, "trying to remove bubble that doesn't exist: " + removedBubble.getKey());
             }
         } else {
@@ -352,7 +388,7 @@
                     if (bubble != null && overflowNeedsToBeAdded) {
                         // First removal, show the overflow
                         overflowNeedsToBeAdded = false;
-                        mBubbleBarViewController.addOverflowAndRemoveBubble(bubble);
+                        mBubbleBarViewController.addOverflowAndRemoveBubble(bubble, bubbleToSelect);
                     } else if (bubble != null) {
                         mBubbleBarViewController.removeBubble(bubble);
                     } else {
@@ -362,9 +398,8 @@
                 }
             }
             if (update.addedBubble != null) {
-                mBubbles.put(update.addedBubble.getKey(), update.addedBubble);
                 mBubbleBarViewController.addBubble(update.addedBubble, isExpanding,
-                        suppressAnimation);
+                        suppressAnimation, bubbleToSelect);
             }
             if (Flags.enableOptionalBubbleOverflow()
                     && update.showOverflowChanged
@@ -375,12 +410,10 @@
 
         // if a bubble was updated upstream, but removed before the update was received, add it back
         if (update.updatedBubble != null && !mBubbles.containsKey(update.updatedBubble.getKey())) {
-            mBubbles.put(update.updatedBubble.getKey(), update.updatedBubble);
-            mBubbleBarViewController.addBubble(
-                    update.updatedBubble, isExpanding, suppressAnimation);
+            addBubbleInternally(update.updatedBubble, isExpanding, suppressAnimation);
         }
 
-        if (update.addedBubble != null && isCollapsed) {
+        if (update.addedBubble != null && isCollapsed && bubbleToSelect == null) {
             // If we're collapsed, the most recently added bubble will be selected.
             bubbleToSelect = update.addedBubble;
         }
@@ -391,7 +424,7 @@
                 BubbleBarBubble bubble = update.currentBubbles.get(i);
                 if (bubble != null) {
                     addBubbleInternally(bubble, isExpanding, suppressAnimation);
-                    if (isCollapsed) {
+                    if (isCollapsed && bubbleToSelect == null) {
                         // If we're collapsed, the most recently added bubble will be selected.
                         bubbleToSelect = bubble;
                     }
@@ -405,10 +438,29 @@
             mBubbleBarViewController.showOverflow(true);
         }
 
-        // Update the visibility if this is the initial state or if there are no bubbles.
+        if (update.suppressedBubbleKey != null) {
+            BubbleBarBubble bb = mBubbles.remove(update.suppressedBubbleKey);
+            if (bb != null) {
+                mSuppressedBubbles.put(update.suppressedBubbleKey, bb);
+                mBubbleBarViewController.removeBubble(bb);
+            }
+        }
+        if (update.unsuppressedBubbleKey != null) {
+            BubbleBarBubble bb = mSuppressedBubbles.remove(update.unsuppressedBubbleKey);
+            if (bb != null) {
+                // Unsuppressing an existing bubble should not cause the bar to expand or animate
+                addBubbleInternally(bb, /* isExpanding= */ false, /* suppressAnimation= */ true);
+                if (mBubbleBarViewController.isHiddenForNoBubbles()) {
+                    mBubbleBarViewController.setHiddenForBubbles(false);
+                }
+            }
+        }
+
+        // Update the visibility if this is the initial state, if there are no bubbles, or if the
+        // animation is suppressed.
         // If this is the initial bubble, the bubble bar will become visible as part of the
         // animation.
-        if (update.initialState || mBubbles.isEmpty()) {
+        if (update.initialState || mBubbles.isEmpty() || suppressAnimation) {
             mBubbleBarViewController.setHiddenForBubbles(mBubbles.isEmpty());
         }
         mBubbleStashedHandleViewController.ifPresent(
@@ -439,24 +491,6 @@
                 mBubbleBarViewController.reorderBubbles(newOrder);
             }
         }
-        if (update.suppressedBubbleKey != null) {
-            // TODO: (b/273316505) handle suppression
-        }
-        if (update.unsuppressedBubbleKey != null) {
-            // TODO: (b/273316505) handle suppression
-        }
-        if (update.selectedBubbleKey != null) {
-            if (mSelectedBubble == null
-                    || !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
-                BubbleBarBubble newlySelected = mBubbles.get(update.selectedBubbleKey);
-                if (newlySelected != null) {
-                    bubbleToSelect = newlySelected;
-                } else {
-                    Log.w(TAG, "trying to select bubble that doesn't exist:"
-                            + update.selectedBubbleKey);
-                }
-            }
-        }
         if (bubbleToSelect != null) {
             setSelectedBubbleInternal(bubbleToSelect);
         }
@@ -555,6 +589,27 @@
                 });
     }
 
+    @Override
+    public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation bubbleBarLocation) {
+        MAIN_EXECUTOR.execute(() -> {
+            mBubbleBarViewController.onDragItemOverBubbleBarDragZone(bubbleBarLocation);
+            if (mBubbleBarViewController.isLocationUpdatedForDropTarget()) {
+                mBubbleBarLocationListener.onBubbleBarLocationAnimated(bubbleBarLocation);
+            }
+        });
+    }
+
+    @Override
+    public void onItemDraggedOutsideBubbleBarDropZone() {
+        MAIN_EXECUTOR.execute(() -> {
+            if (mBubbleBarViewController.isLocationUpdatedForDropTarget()) {
+                BubbleBarLocation original = mBubbleBarViewController.getBubbleBarLocation();
+                mBubbleBarLocationListener.onBubbleBarLocationAnimated(original);
+            }
+            mBubbleBarViewController.onItemDraggedOutsideBubbleBarDropZone();
+        });
+    }
+
     /** Notifies WMShell to show the expanded view. */
     void showExpandedView() {
         mSystemUiProxy.showExpandedView();
@@ -575,7 +630,8 @@
     private void addBubbleInternally(BubbleBarBubble bubble, boolean isExpanding,
             boolean suppressAnimation) {
         mBubbles.put(bubble.getKey(), bubble);
-        mBubbleBarViewController.addBubble(bubble, isExpanding, suppressAnimation);
+        mBubbleBarViewController.addBubble(bubble, isExpanding,
+                suppressAnimation, /* bubbleToSelect = */ null);
     }
 
     /** Listener of {@link BubbleBarLocation} updates. */
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarLocationDropTarget.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarLocationDropTarget.kt
new file mode 100644
index 0000000..383f4d2
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarLocationDropTarget.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.bubbles
+
+import android.graphics.Rect
+import android.view.View
+import com.android.launcher3.DropTarget
+import com.android.launcher3.dragndrop.DragOptions
+import com.android.launcher3.model.data.ItemInfo
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+
+/**
+ * Implementation of the {@link DropTarget} that handles drag and drop events over the bubble bar
+ * locations.
+ */
+class BubbleBarLocationDropTarget(
+    private val bubbleBarLocation: BubbleBarLocation,
+    private val bubbleBarDragListener: BubbleBarDragListener,
+) : DropTarget {
+
+    /** Controller that takes care of the bubble bar drag events inside launcher process. */
+    interface BubbleBarDragListener {
+
+        /** Called when the drag event is over the bubble bar drop zone. */
+        fun onLauncherItemDraggedOverBubbleBarDragZone(location: BubbleBarLocation)
+
+        /** Called when the drag event leaves the bubble bar drop zone. */
+        fun onLauncherItemDraggedOutsideBubbleBarDropZone()
+
+        /** Called when the drop event happens over the bubble bar drop zone. */
+        fun onLauncherItemDroppedOverBubbleBarDragZone(
+            location: BubbleBarLocation,
+            itemInfo: ItemInfo,
+        )
+
+        /** Gets the hit [rect][android.graphics.Rect] of the bubble bar location. */
+        fun getBubbleBarLocationHitRect(bubbleBarLocation: BubbleBarLocation, outRect: Rect)
+
+        /** Provides the view that will accept the drop. */
+        fun getDropView(): View
+    }
+
+    private var isShowingDropTarget = false
+
+    override fun isDropEnabled(): Boolean = true
+
+    override fun onDrop(dragObject: DropTarget.DragObject, options: DragOptions) {
+        val itemInfo = dragObject.dragInfo ?: return
+        // TODO(b/397459664) : fix task bar icon animation after drop
+        // TODO(b/397459664) : update bubble bar location
+        bubbleBarDragListener.onLauncherItemDroppedOverBubbleBarDragZone(
+            bubbleBarLocation,
+            itemInfo,
+        )
+    }
+
+    override fun onDragEnter(dragObject: DropTarget.DragObject) {}
+
+    override fun onDragOver(dragObject: DropTarget.DragObject) {
+        if (isShowingDropTarget) return
+        isShowingDropTarget = true
+        bubbleBarDragListener.onLauncherItemDraggedOverBubbleBarDragZone(bubbleBarLocation)
+    }
+
+    override fun onDragExit(dragObject: DropTarget.DragObject) {
+        // TODO(b/397459664) : fix the issue for no bubbles, when moving task bar icon out of
+        // the bubble bar drag zone drag ends and swipes gesture swipes the overview
+        if (!isShowingDropTarget) return
+        isShowingDropTarget = false
+        bubbleBarDragListener.onLauncherItemDraggedOutsideBubbleBarDropZone()
+    }
+
+    override fun acceptDrop(dragObject: DropTarget.DragObject): Boolean = true
+
+    override fun prepareAccessibilityDrop() {}
+
+    override fun getHitRectRelativeToDragLayer(outRect: Rect) {
+        bubbleBarDragListener.getBubbleBarLocationHitRect(bubbleBarLocation, outRect)
+    }
+
+    override fun getDropView(): View = bubbleBarDragListener.getDropView()
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 37c6194..d43ebe2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -536,6 +536,16 @@
         return (float) (displayWidth - getWidth() - margin);
     }
 
+    /** Set whether the background should show the drop target */
+    public void showDropTarget(boolean isDropTarget) {
+        mBubbleBarBackground.showDropTarget(isDropTarget);
+    }
+
+    /** Returns whether the Bubble Bar is currently displaying a drop target. */
+    public boolean isShowingDropTarget() {
+        return mBubbleBarBackground.isShowingDropTarget();
+    }
+
     /**
      * Animate bubble bar to the given location transiently. Does not modify the layout or the value
      * returned by {@link #getBubbleBarLocation()}.
@@ -681,8 +691,18 @@
         return mRelativePivotY;
     }
 
-    /** Add a new bubble to the bubble bar. */
+    /** Add a new bubble to the bubble bar without updating the selected bubble. */
     public void addBubble(BubbleView bubble) {
+        addBubble(bubble, /* bubbleToSelect = */ null);
+    }
+
+    /**
+     * Add a new bubble to the bubble bar and selects the provided bubble.
+     *
+     * @param bubble         bubble to add
+     * @param bubbleToSelect if {@code null}, then selected bubble does not change
+     */
+    public void addBubble(BubbleView bubble, @Nullable BubbleView bubbleToSelect) {
         FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) mIconSize, (int) mIconSize,
                 Gravity.LEFT);
         final int index = bubble.isOverflow() ? getChildCount() : 0;
@@ -718,7 +738,12 @@
                     invalidate();
                 }
             };
-            mBubbleAnimator.animateNewBubble(indexOfChild(mSelectedBubbleView), listener);
+            if (bubbleToSelect != null) {
+                mBubbleAnimator.animateNewBubble(indexOfChild(mSelectedBubbleView),
+                        indexOfChild(bubbleToSelect), listener);
+            } else {
+                mBubbleAnimator.animateNewBubble(indexOfChild(mSelectedBubbleView), listener);
+            }
         } else {
             addView(bubble, index, lp);
         }
@@ -726,34 +751,32 @@
 
     /** Add a new bubble and remove an old bubble from the bubble bar. */
     public void addBubbleAndRemoveBubble(BubbleView addedBubble, BubbleView removedBubble,
-            Runnable onEndRunnable) {
+            @Nullable BubbleView bubbleToSelect, Runnable onEndRunnable) {
         FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams((int) mIconSize, (int) mIconSize,
                 Gravity.LEFT);
-        boolean isOverflowSelected = mSelectedBubbleView.isOverflow();
-        boolean removingOverflow = removedBubble.isOverflow();
-        boolean addingOverflow = addedBubble.isOverflow();
-
+        int addedIndex = addedBubble.isOverflow() ? getChildCount() : 0;
         if (!isExpanded()) {
             removeView(removedBubble);
-            int index = addingOverflow ? getChildCount() : 0;
-            addView(addedBubble, index, lp);
+            addView(addedBubble, addedIndex, lp);
             if (onEndRunnable != null) {
                 onEndRunnable.run();
             }
             return;
         }
-        int index = addingOverflow ? getChildCount() : 0;
         addedBubble.setScaleX(0f);
         addedBubble.setScaleY(0f);
-        addView(addedBubble, index, lp);
-
-        if (isOverflowSelected && removingOverflow) {
-            // The added bubble will be selected
-            mSelectedBubbleView = addedBubble;
-        }
-        int indexOfSelectedBubble = indexOfChild(mSelectedBubbleView);
+        addView(addedBubble, addedIndex, lp);
+        int indexOfCurrentSelectedBubble = indexOfChild(mSelectedBubbleView);
         int indexOfBubbleToRemove = indexOfChild(removedBubble);
-
+        int indexOfNewlySelectedBubble = bubbleToSelect == null
+                ? indexOfCurrentSelectedBubble : indexOfChild(bubbleToSelect);
+        // Since removed bubble is kept till the end of the animation we should check if there are
+        // more than one bubble. In such a case the bar will remain open without the selected bubble
+        if (mSelectedBubbleView == removedBubble
+                && bubbleToSelect == null
+                && getBubbleChildCount() > 1) {
+            Log.w(TAG, "Remove the currently selected bubble without selecting a new one.");
+        }
         mBubbleAnimator = new BubbleAnimator(mIconSize, mExpandedBarIconsSpacing,
                 getChildCount(), mBubbleBarLocation.isOnLeft(isLayoutRtl()));
         BubbleAnimator.Listener listener = new BubbleAnimator.Listener() {
@@ -786,8 +809,8 @@
                 invalidate();
             }
         };
-        mBubbleAnimator.animateNewAndRemoveOld(indexOfSelectedBubble, indexOfBubbleToRemove,
-                listener);
+        mBubbleAnimator.animateNewAndRemoveOld(indexOfCurrentSelectedBubble,
+                indexOfNewlySelectedBubble, indexOfBubbleToRemove, addedIndex, listener);
     }
 
     @Override
@@ -1326,6 +1349,14 @@
         return getScaledIconSize() + mIconOverlapAmount + 2 * mBubbleBarPadding;
     }
 
+    float getCollapsedWidthForIconSizeAndPadding(int iconSize, int bubbleBarPadding) {
+        final int bubbleChildCount = Math.min(getBubbleChildCount(), MAX_VISIBLE_BUBBLES_COLLAPSED);
+        if (bubbleChildCount == 0) return 0;
+        final int spacesCount = bubbleChildCount - 1;
+        final float horizontalPadding = 2 * bubbleBarPadding;
+        return iconSize * bubbleChildCount + mIconOverlapAmount * spacesCount + horizontalPadding;
+    }
+
     /** Returns the child count excluding the overflow if it's present. */
     int getBubbleChildCount() {
         return hasOverflow() ? getChildCount() - 1 : getChildCount();
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index dd1b0ca..277dbbf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -24,6 +24,8 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
+import android.content.Intent;
+import android.content.pm.ShortcutInfo;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -43,11 +45,15 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.taskbar.TaskbarControllers;
+import com.android.launcher3.taskbar.TaskbarDragController;
 import com.android.launcher3.taskbar.TaskbarInsetsController;
 import com.android.launcher3.taskbar.TaskbarSharedState;
 import com.android.launcher3.taskbar.TaskbarStashController;
+import com.android.launcher3.taskbar.bubbles.BubbleBarLocationDropTarget.BubbleBarDragListener;
 import com.android.launcher3.taskbar.bubbles.animation.BubbleBarViewAnimator;
 import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutController;
 import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutPositioner;
@@ -59,6 +65,7 @@
 import com.android.quickstep.SystemUiProxy;
 import com.android.wm.shell.Flags;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.DeviceConfig;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -117,12 +124,66 @@
         updateTranslationY();
         setBubbleBarScaleAndPadding(pinningProgress);
     });
+    private final BubbleBarDragListener mDragListener = new BubbleBarDragListener() {
+
+        @Override
+        public void getBubbleBarLocationHitRect(@NonNull BubbleBarLocation bubbleBarLocation,
+                Rect outRect) {
+            Point screenSize = DisplayController.INSTANCE.get(mActivity).getInfo().currentSize;
+            outRect.top = screenSize.y - mBubbleBarDropTargetSize;
+            outRect.bottom = screenSize.y;
+            if (bubbleBarLocation.isOnLeft(mBarView.isLayoutRtl())) {
+                outRect.left = 0;
+                outRect.right = mBubbleBarDropTargetSize;
+            } else {
+                outRect.left = screenSize.x - mBubbleBarDropTargetSize;
+                outRect.right = screenSize.x;
+            }
+        }
+
+        @Override
+        public void onLauncherItemDroppedOverBubbleBarDragZone(@NonNull BubbleBarLocation location,
+                @NonNull ItemInfo itemInfo) {
+            if (itemInfo instanceof WorkspaceItemInfo) {
+                ShortcutInfo shortcutInfo = ((WorkspaceItemInfo) itemInfo).getDeepShortcutInfo();
+                if (shortcutInfo != null) {
+                    mSystemUiProxy.showShortcutBubble(shortcutInfo, location);
+                    return;
+                }
+            }
+            Intent itemIntent = itemInfo.getIntent();
+            if (itemIntent != null && itemIntent.getComponent() != null) {
+                itemIntent.setPackage(itemIntent.getComponent().getPackageName());
+                mSystemUiProxy.showAppBubble(itemIntent, itemInfo.user, location);
+            }
+        }
+
+        @Override
+        public void onLauncherItemDraggedOutsideBubbleBarDropZone() {
+            onItemDraggedOutsideBubbleBarDropZone();
+            mSystemUiProxy.showBubbleDropTarget(/* show = */ false);
+        }
+
+        @Override
+        public void onLauncherItemDraggedOverBubbleBarDragZone(
+                @NonNull BubbleBarLocation location) {
+            onDragItemOverBubbleBarDragZone(location);
+            mSystemUiProxy.showBubbleDropTarget(/* show = */ true, location);
+        }
+
+        @NonNull
+        @Override
+        public View getDropView() {
+            return mBarView;
+        }
+    };
 
     // Modified when swipe up is happening on the bubble bar or task bar.
     private float mBubbleBarSwipeUpTranslationY;
     // Modified when bubble bar is springing back into the stash handle.
     private float mBubbleBarStashTranslationY;
-
+    // Minimum distance between the BubbleBar and the taskbar
+    private final int mBubbleBarTaskbarMinDistance;
     // Whether the bar is hidden for a sysui state.
     private boolean mHiddenForSysui;
     // Whether the bar is hidden because there are no bubbles.
@@ -130,15 +191,20 @@
     // Whether the bar is hidden when stashed
     private boolean mHiddenForStashed;
     private boolean mShouldShowEducation;
-
     public boolean mOverflowAdded;
+    private boolean mIsLocationUpdatedForDropTarget = false;
 
     private BubbleBarViewAnimator mBubbleBarViewAnimator;
     private final FrameLayout mBubbleBarContainer;
     private BubbleBarFlyoutController mBubbleBarFlyoutController;
+    private BubbleBarPinController mBubbleBarPinController;
     private TaskbarSharedState mTaskbarSharedState;
+    private TaskbarDragController mTaskbarDragController;
+    private final BubbleBarLocationDropTarget mBubbleBarLeftDropTarget;
+    private final BubbleBarLocationDropTarget mBubbleBarRightDropTarget;
     private final TimeSource mTimeSource = System::currentTimeMillis;
     private final int mTaskbarTranslationDelta;
+    private final int mBubbleBarDropTargetSize;
 
     @Nullable
     private BubbleBarBoundsChangeListener mBoundsChangeListener;
@@ -150,20 +216,32 @@
         mBubbleBarContainer = bubbleBarContainer;
         mSystemUiProxy = SystemUiProxy.INSTANCE.get(mActivity);
         mBubbleBarAlpha = new MultiValueAlpha(mBarView, 1 /* num alpha channels */);
-        mIconSize = activity.getResources().getDimensionPixelSize(
-                R.dimen.bubblebar_icon_size);
-        mDragElevation = activity.getResources().getDimensionPixelSize(
-                R.dimen.bubblebar_drag_elevation);
+        Resources res = activity.getResources();
+        mIconSize = res.getDimensionPixelSize(R.dimen.bubblebar_icon_size);
+        mBubbleBarTaskbarMinDistance = res.getDimensionPixelSize(
+                R.dimen.bubblebar_transient_taskbar_min_distance);
+        mDragElevation = res.getDimensionPixelSize(R.dimen.bubblebar_drag_elevation);
         mTaskbarTranslationDelta = getBubbleBarTranslationDeltaForTaskbar(activity);
+        if (DeviceConfig.isSmallTablet(mActivity)) {
+            mBubbleBarDropTargetSize = res.getDimensionPixelSize(R.dimen.drag_zone_bubble_fold);
+        } else {
+            mBubbleBarDropTargetSize = res.getDimensionPixelSize(R.dimen.drag_zone_bubble_tablet);
+        }
+        mBubbleBarLeftDropTarget = new BubbleBarLocationDropTarget(BubbleBarLocation.LEFT,
+                mDragListener);
+        mBubbleBarRightDropTarget = new BubbleBarLocationDropTarget(BubbleBarLocation.RIGHT,
+                mDragListener);
     }
 
     /** Initializes controller. */
     public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers,
             TaskbarViewPropertiesProvider taskbarViewPropertiesProvider) {
+        mTaskbarDragController = controllers.taskbarDragController;
         mTaskbarSharedState = controllers.getSharedState();
         mBubbleStashController = bubbleControllers.bubbleStashController;
         mBubbleBarController = bubbleControllers.bubbleBarController;
         mBubbleDragController = bubbleControllers.bubbleDragController;
+        mBubbleBarPinController = bubbleControllers.bubbleBarPinController;
         mTaskbarStashController = controllers.taskbarStashController;
         mTaskbarInsetsController = controllers.taskbarInsetsController;
         mBubbleBarFlyoutController = new BubbleBarFlyoutController(
@@ -183,6 +261,11 @@
         if (!Flags.enableOptionalBubbleOverflow()) {
             showOverflow(true);
         }
+        if (!mBubbleStashController.isTransientTaskBar()) {
+            // TODO(b/380274085) for transient taskbar mode, the click is also handled by the input
+            //  consumer. This check can be removed once b/380274085 is fixed.
+            mBarView.setOnClickListener(v -> setExpanded(!mBarView.isExpanded()));
+        }
         mBarView.addOnLayoutChangeListener(
                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
                     mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
@@ -255,6 +338,8 @@
                 mBubbleBarController.updateBubbleBarLocation(location, source);
             }
         };
+        mTaskbarDragController.addDropTarget(mBubbleBarLeftDropTarget);
+        mTaskbarDragController.addDropTarget(mBubbleBarRightDropTarget);
     }
 
     /** Returns animated float property responsible for pinning transition animation. */
@@ -267,7 +352,10 @@
 
             @Override
             public boolean isOnLeft() {
-                return mBarView.getBubbleBarLocation().isOnLeft(mBarView.isLayoutRtl());
+                boolean shouldRevertLocation =
+                        mBarView.isShowingDropTarget() && mIsLocationUpdatedForDropTarget;
+                boolean isOnLeft = mBarView.getBubbleBarLocation().isOnLeft(mBarView.isLayoutRtl());
+                return shouldRevertLocation != isOnLeft;
             }
 
             @Override
@@ -476,7 +564,7 @@
 
     /** Whether the bubble bar has bubbles. */
     public boolean hasBubbles() {
-        return mBubbleBarController.getSelectedBubbleKey() != null;
+        return mBarView.getBubbleChildCount() > 0;
     }
 
     /**
@@ -517,6 +605,63 @@
         mBarView.animateToBubbleBarLocation(bubbleBarLocation);
     }
 
+    /** Returns whether the Bubble Bar is currently displaying a drop target. */
+    public boolean isShowingDropTarget() {
+        return mBarView.isShowingDropTarget();
+    }
+
+    /**
+     * Notifies the controller that a drag event is over the Bubble Bar drop zone. The controller
+     * will display the appropriate drop target and enter drop target mode. The controller will also
+     * update the return value of {@link #isLocationUpdatedForDropTarget()} to true if location was
+     * updated.
+     */
+    public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation bubbleBarLocation) {
+        mBarView.showDropTarget(/* isDropTarget = */ true);
+        boolean isRtl = mBarView.isLayoutRtl();
+        mIsLocationUpdatedForDropTarget = getBubbleBarLocation().isOnLeft(isRtl)
+                != bubbleBarLocation.isOnLeft(isRtl);
+        if (mIsLocationUpdatedForDropTarget) {
+            animateBubbleBarLocation(bubbleBarLocation);
+        }
+        if (!hasBubbles()) {
+            mBubbleBarPinController.showDropTarget(bubbleBarLocation);
+        }
+    }
+
+    /**
+     * Returns {@code true} if location was updated after most recent
+     * {@link #onDragItemOverBubbleBarDragZone}}.
+     */
+    public boolean isLocationUpdatedForDropTarget() {
+        return mIsLocationUpdatedForDropTarget;
+    }
+
+    /**
+     * Notifies the controller that the drag event is outside the Bubble Bar drop zone.
+     * This will hide the drop target zone if there are no bubbles or return the
+     * Bubble Bar to its original location. The controller will also exit drop target
+     * mode and reset the value returned from {@link #isLocationUpdatedForDropTarget()} to false.
+     */
+    public void onItemDraggedOutsideBubbleBarDropZone() {
+        mBarView.showDropTarget(/* isDropTarget = */ false);
+        if (mIsLocationUpdatedForDropTarget) {
+            animateBubbleBarLocation(getBubbleBarLocation());
+        }
+        mBubbleBarPinController.hideDropTarget();
+        mIsLocationUpdatedForDropTarget = false;
+    }
+
+    /**
+     * Notifies the controller that the drag has completed over the Bubble Bar drop zone.
+     * The controller will hide the drop target if there are no bubbles and exit drop target mode.
+     */
+    public void onItemDroppedInBubbleBarDragZone() {
+        mBarView.showDropTarget(/* isDropTarget = */ false);
+        mBubbleBarPinController.hideDropTarget();
+        mIsLocationUpdatedForDropTarget = false;
+    }
+
     /**
      * The bounds of the bubble bar.
      */
@@ -581,7 +726,7 @@
 
     /** Returns maximum height of the bubble bar with the flyout view. */
     public int getBubbleBarWithFlyoutMaximumHeight() {
-        if (!isBubbleBarVisible() && !isAnimatingNewBubble()) return 0;
+        if (!hasBubbles() && !isAnimatingNewBubble()) return 0;
         int bubbleBarTopOnHome = (int) (mBubbleStashController.getBubbleBarVerticalCenterForHome()
                 + mBarView.getBubbleBarCollapsedHeight() / 2 + mBarView.getArrowHeight());
         if (isAnimatingNewBubble()) {
@@ -650,13 +795,54 @@
     }
 
     private void updateVisibilityForStateChange() {
-        if (!mHiddenForSysui && !mHiddenForNoBubbles && !mHiddenForStashed) {
-            mBarView.setVisibility(VISIBLE);
-        } else {
+        boolean hiddenForStashedAndNotAnimating =
+                mHiddenForStashed && !mBubbleBarViewAnimator.isAnimating();
+        if (mHiddenForSysui || mHiddenForNoBubbles || hiddenForStashedAndNotAnimating) {
             mBarView.setVisibility(INVISIBLE);
+        } else {
+            mBarView.setVisibility(VISIBLE);
         }
     }
 
+    /**
+     * Returns the translation X of the transient taskbar according to the bubble bar location
+     * regardless of the current taskbar mode.
+     */
+    public int getTransientTaskbarTranslationXForBubbleBar(BubbleBarLocation location) {
+        int taskbarShift = 0;
+        if (!isBubbleBarVisible() || mTaskbarViewPropertiesProvider == null) return taskbarShift;
+        Rect taskbarViewBounds = mTaskbarViewPropertiesProvider.getTaskbarViewBounds();
+        if (taskbarViewBounds.isEmpty()) return taskbarShift;
+        int actualDistance =
+                getDistanceBetweenTransientTaskbarAndBubbleBar(location, taskbarViewBounds);
+        if (actualDistance < mBubbleBarTaskbarMinDistance) {
+            taskbarShift = mBubbleBarTaskbarMinDistance - actualDistance;
+            if (!location.isOnLeft(mBarView.isLayoutRtl())) {
+                taskbarShift = -taskbarShift;
+            }
+        }
+        return taskbarShift;
+    }
+
+    private int getDistanceBetweenTransientTaskbarAndBubbleBar(BubbleBarLocation location,
+            Rect taskbarViewBounds) {
+        Resources res = mActivity.getResources();
+        DeviceProfile transientDp = mActivity.getTransientTaskbarDeviceProfile();
+        int transientIconSize = getBubbleBarIconSizeFromDeviceProfile(res, transientDp);
+        int transientPadding = getBubbleBarPaddingFromDeviceProfile(res, transientDp);
+        int transientWidthWithMargin = (int) (mBarView.getCollapsedWidthForIconSizeAndPadding(
+                transientIconSize, transientPadding) + mBarView.getHorizontalMargin());
+        int distance;
+        if (location.isOnLeft(mBarView.isLayoutRtl())) {
+            distance = taskbarViewBounds.left - transientWidthWithMargin;
+        } else {
+            int displayWidth = res.getDisplayMetrics().widthPixels;
+            int bubbleBarLeft = displayWidth - transientWidthWithMargin;
+            distance = bubbleBarLeft - taskbarViewBounds.right;
+        }
+        return distance;
+    }
+
     //
     // Modifying view related properties.
     //
@@ -836,11 +1022,12 @@
     }
 
     /** Adds a new bubble and removes an old bubble at the same time. */
-    public void addBubbleAndRemoveBubble(BubbleBarBubble addedBubble,
-            BubbleBarBubble removedBubble, boolean isExpanding, boolean suppressAnimation,
-            boolean addOverflowToo) {
+    public void addBubbleAndRemoveBubble(BubbleBarBubble addedBubble, BubbleBarBubble removedBubble,
+            @Nullable BubbleBarBubble bubbleToSelect, boolean isExpanding,
+            boolean suppressAnimation, boolean addOverflowToo) {
+        BubbleView bubbleToSelectView = bubbleToSelect == null ? null : bubbleToSelect.getView();
         mBarView.addBubbleAndRemoveBubble(addedBubble.getView(), removedBubble.getView(),
-                addOverflowToo ? () -> showOverflow(true) : null);
+                bubbleToSelectView, addOverflowToo ? () -> showOverflow(true) : null);
         addedBubble.getView().setOnClickListener(mBubbleClickListener);
         addedBubble.getView().setController(mBubbleViewController);
         removedBubble.getView().setController(null);
@@ -871,22 +1058,26 @@
     }
 
     /** Adds the overflow view to the bubble bar while animating a view away. */
-    public void addOverflowAndRemoveBubble(BubbleBarBubble removedBubble) {
+    public void addOverflowAndRemoveBubble(BubbleBarBubble removedBubble,
+            @Nullable BubbleBarBubble bubbleToSelect) {
         if (mOverflowAdded) return;
         mOverflowAdded = true;
+        BubbleView bubbleToSelectView = bubbleToSelect == null ? null : bubbleToSelect.getView();
         mBarView.addBubbleAndRemoveBubble(mOverflowBubble.getView(), removedBubble.getView(),
-                null /* onEndRunnable */);
+                bubbleToSelectView, null /* onEndRunnable */);
         mOverflowBubble.getView().setOnClickListener(mBubbleClickListener);
         mOverflowBubble.getView().setController(mBubbleViewController);
         removedBubble.getView().setController(null);
     }
 
     /** Removes the overflow view to the bubble bar while animating a view in. */
-    public void removeOverflowAndAddBubble(BubbleBarBubble addedBubble) {
+    public void removeOverflowAndAddBubble(BubbleBarBubble addedBubble,
+            @Nullable BubbleBarBubble bubbleToSelect) {
         if (!mOverflowAdded) return;
         mOverflowAdded = false;
+        BubbleView bubbleToSelectView = bubbleToSelect == null ? null : bubbleToSelect.getView();
         mBarView.addBubbleAndRemoveBubble(addedBubble.getView(), mOverflowBubble.getView(),
-                null /* onEndRunnable */);
+                bubbleToSelectView, null /* onEndRunnable */);
         addedBubble.getView().setOnClickListener(mBubbleClickListener);
         addedBubble.getView().setController(mBubbleViewController);
         mOverflowBubble.getView().setController(null);
@@ -895,9 +1086,15 @@
     /**
      * Adds the provided bubble to the bubble bar.
      */
-    public void addBubble(BubbleBarItem b, boolean isExpanding, boolean suppressAnimation) {
+    public void addBubble(BubbleBarItem b,
+            boolean isExpanding,
+            boolean suppressAnimation,
+            @Nullable BubbleBarBubble bubbleToSelect
+    ) {
         if (b != null) {
-            mBarView.addBubble(b.getView());
+            BubbleView bubbleToSelectView =
+                    bubbleToSelect == null ? null : bubbleToSelect.getView();
+            mBarView.addBubble(b.getView(), bubbleToSelectView);
             b.getView().setOnClickListener(mBubbleClickListener);
             mBubbleDragController.setupBubbleView(b.getView());
             b.getView().setController(mBubbleViewController);
@@ -937,7 +1134,12 @@
         boolean isInApp = mTaskbarStashController.isInApp();
         // if this is the first bubble, animate to the initial state.
         if (mBarView.getBubbleChildCount() == 1 && !isUpdate) {
-            mBubbleBarViewAnimator.animateToInitialState(bubble, isInApp, isExpanding);
+            // If a drop target is visible and the first bubble is added, hide the empty drop target
+            if (mBarView.isShowingDropTarget()) {
+                mBubbleBarPinController.hideDropTarget();
+            }
+            mBubbleBarViewAnimator.animateToInitialState(bubble, isInApp, isExpanding,
+                    mBarView.isShowingDropTarget());
             return;
         }
         // if we're not stashed or we're in persistent taskbar, animate for collapsed state.
@@ -1135,6 +1337,7 @@
 
     /** Removes all existing bubble views */
     public void removeAllBubbles() {
+        mOverflowAdded = false;
         mBarView.removeAllViews();
     }
 
@@ -1153,6 +1356,21 @@
     /** Called when the controller is destroyed. */
     public void onDestroy() {
         adjustTaskbarAndHotseatToBubbleBarState(/*isBubbleBarExpanded = */false);
+        mTaskbarDragController.removeDropTarget(mBubbleBarLeftDropTarget);
+        mTaskbarDragController.removeDropTarget(mBubbleBarRightDropTarget);
+    }
+
+    /**
+     * Removes the bubble from the bubble bar and notifies sysui that the bubble should move to
+     * full screen.
+     */
+    public void moveBubbleToFullscreen(@NonNull BubbleView bubbleView) {
+        if (bubbleView.getBubble() == null) {
+            return;
+        }
+        String key = bubbleView.getBubble().getKey();
+        mSystemUiProxy.moveBubbleToFullscreen(key);
+        onBubbleDismissed(bubbleView);
     }
 
     /**
@@ -1215,6 +1433,7 @@
         pw.println("Bubble bar view controller state:");
         pw.println("  mHiddenForSysui: " + mHiddenForSysui);
         pw.println("  mHiddenForNoBubbles: " + mHiddenForNoBubbles);
+        pw.println("  mHiddenForStashed: " + mHiddenForStashed);
         pw.println("  mShouldShowEducation: " + mShouldShowEducation);
         pw.println("  mBubbleBarTranslationY.value: " + mBubbleBarTranslationY.value);
         pw.println("  mBubbleBarSwipeUpTranslationY: " + mBubbleBarSwipeUpTranslationY);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt
index a8002a5..3245fd1e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDismissViewExt.kt
@@ -18,6 +18,7 @@
 package com.android.launcher3.taskbar.bubbles
 
 import com.android.launcher3.R
+import com.android.wm.shell.shared.R as SharedR
 import com.android.wm.shell.shared.bubbles.DismissView
 
 /**
@@ -36,8 +37,8 @@
             bottomMarginResId = R.dimen.bubblebar_dismiss_target_bottom_margin,
             floatingGradientHeightResId = R.dimen.bubblebar_dismiss_floating_gradient_height,
             floatingGradientColorResId = android.R.color.system_neutral1_900,
-            backgroundResId = R.drawable.bg_bubble_dismiss_circle,
-            iconResId = R.drawable.ic_bubble_dismiss_white
+            backgroundResId = SharedR.drawable.floating_dismiss_background,
+            iconResId = SharedR.drawable.floating_dismiss_ic_close,
         )
     )
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
index fd4cf0e..f77b934 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragController.java
@@ -21,6 +21,8 @@
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -28,7 +30,16 @@
 
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.wm.shell.shared.bubbles.BaseBubblePinController.LocationChangeListener;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.DeviceConfig;
+import com.android.wm.shell.shared.bubbles.DragZone;
+import com.android.wm.shell.shared.bubbles.DragZoneFactory;
+import com.android.wm.shell.shared.bubbles.DragZoneFactory.DesktopWindowModeChecker;
+import com.android.wm.shell.shared.bubbles.DragZoneFactory.SplitScreenModeChecker;
+import com.android.wm.shell.shared.bubbles.DraggedObject;
+import com.android.wm.shell.shared.bubbles.DropTargetManager;
+import com.android.wm.shell.shared.bubbles.DropTargetManager.DragZoneChangedListener;
 
 /**
  * Controls bubble bar drag interactions.
@@ -76,11 +87,36 @@
     private BubbleDismissController mBubbleDismissController;
     private BubbleBarPinController mBubbleBarPinController;
     private BubblePinController mBubblePinController;
+    private final DropTargetManager mDropTargetManager;
+    private final DragZoneFactory mDragZoneFactory;
+    private final BubbleDragZoneChangedListener mBubbleDragZoneChangedListener;
 
     private boolean mIsDragging;
 
-    public BubbleDragController(TaskbarActivityContext activity) {
+    public BubbleDragController(TaskbarActivityContext activity, FrameLayout dropTargetParent) {
         mActivity = activity;
+        WindowManager windowManager =
+                mActivity.getApplicationContext().getSystemService(WindowManager.class);
+        DeviceConfig deviceConfig =
+                DeviceConfig.create(mActivity.getApplicationContext(), windowManager);
+        SplitScreenModeChecker splitScreenModeChecker = new SplitScreenModeChecker() {
+            @NonNull
+            @Override
+            public SplitScreenMode getSplitScreenMode() {
+                return SplitScreenMode.NONE;
+            }
+        };
+        DesktopWindowModeChecker desktopWindowModeChecker = new DesktopWindowModeChecker() {
+            @Override
+            public boolean isSupported() {
+                return false;
+            }
+        };
+        mDragZoneFactory = new DragZoneFactory(mActivity.getApplicationContext(), deviceConfig,
+                splitScreenModeChecker, desktopWindowModeChecker);
+        mBubbleDragZoneChangedListener = new BubbleDragZoneChangedListener();
+        mDropTargetManager = new DropTargetManager(mActivity.getApplicationContext(),
+                dropTargetParent, mBubbleDragZoneChangedListener);
     }
 
     /**
@@ -130,47 +166,89 @@
                         }
                     };
 
+            private BubbleBarLocation getBubbleBarLocationDuringDrag() {
+                return BubbleAnythingFlagHelper.enableBubbleToFullscreen()
+                        ? mBubbleDragZoneChangedListener.mBubbleBarLocation
+                        : mReleasedLocation;
+            }
+
             @Override
             void onDragStart() {
-                mBubblePinController.setListener(mLocationChangeListener);
                 mBubbleBarViewController.onBubbleDragStart(bubbleView);
-                mBubblePinController.onDragStart(
-                        mBubbleBarViewController.getBubbleBarLocation().isOnLeft(
-                                bubbleView.isLayoutRtl()));
+                if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+                    DraggedObject.Bubble draggedBubble =
+                            new DraggedObject.Bubble(
+                                    mBubbleBarViewController.getBubbleBarLocation());
+                    mDropTargetManager.onDragStarted(draggedBubble,
+                            mDragZoneFactory.createSortedDragZones(draggedBubble));
+                } else {
+                    mBubblePinController.setListener(mLocationChangeListener);
+                    mBubblePinController.onDragStart(
+                            mBubbleBarViewController.getBubbleBarLocation().isOnLeft(
+                                    bubbleView.isLayoutRtl()));
+                }
             }
 
             @Override
             protected void onDragUpdate(float x, float y, float newTx, float newTy) {
                 bubbleView.setDragTranslationX(newTx);
                 bubbleView.setTranslationY(newTy);
-                mBubblePinController.onDragUpdate(x, y);
+                if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+                    mDropTargetManager.onDragUpdated((int) x, (int) y);
+                } else {
+                    mBubblePinController.onDragUpdate(x, y);
+                }
             }
 
             @Override
             protected void onDragRelease() {
-                mBubblePinController.onDragEnd();
-                mBubbleBarViewController.onBubbleDragRelease(mReleasedLocation);
+                if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+                    mDropTargetManager.onDragEnded();
+                    if (!mBubbleDragZoneChangedListener.isDraggedToFullscreen()) {
+                        // TODO b/393173014: check for desktop window and split once they're
+                        //  implemented. this notifies wm shell that the dragged bubble was
+                        //  released so that we can show the expanded view. we only want to show it
+                        //  after releasing in a Bubble zone. But Split and Desktop Window aren't
+                        //  implemented yet, so we only check for full screen for now.
+                        mBubbleBarViewController.onBubbleDragRelease(
+                                getBubbleBarLocationDuringDrag());
+                    }
+                } else {
+                    mBubblePinController.onDragEnd();
+                    mBubbleBarViewController.onBubbleDragRelease(getBubbleBarLocationDuringDrag());
+                }
             }
 
             @Override
             protected void onDragDismiss() {
-                mBubblePinController.onDragEnd();
+                if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+                    mDropTargetManager.onDragEnded();
+                } else {
+                    mBubblePinController.onDragEnd();
+                }
                 mBubbleBarViewController.onBubbleDismissed(bubbleView);
                 mBubbleBarViewController.onBubbleDragEnd();
             }
 
             @Override
             void onDragEnd() {
-                mBubbleBarController.updateBubbleBarLocation(mReleasedLocation,
+                mBubbleBarController.updateBubbleBarLocation(getBubbleBarLocationDuringDrag(),
                         BubbleBarLocation.UpdateSource.DRAG_BUBBLE);
+                if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+                    mDropTargetManager.onDragEnded();
+                    if (mBubbleDragZoneChangedListener.isDraggedToFullscreen()) {
+                        mBubbleBarViewController.moveBubbleToFullscreen(bubbleView);
+                    }
+                } else {
+                    mBubblePinController.setListener(null);
+                }
                 mBubbleBarViewController.onBubbleDragEnd();
-                mBubblePinController.setListener(null);
             }
 
             @Override
             protected PointF getRestingPosition() {
                 return mBubbleBarViewController.getDraggedBubbleReleaseTranslation(
-                        getInitialPosition(), mReleasedLocation);
+                        getInitialPosition(), getBubbleBarLocationDuringDrag());
             }
         });
     }
@@ -188,6 +266,12 @@
             private final LocationChangeListener mLocationChangeListener =
                     location -> mReleasedLocation = location;
 
+            private BubbleBarLocation getBubbleBarLocationDuringDrag() {
+                return BubbleAnythingFlagHelper.enableBubbleToFullscreen()
+                        ? mBubbleDragZoneChangedListener.mBubbleBarLocation
+                        : mReleasedLocation;
+            }
+
             @Override
             protected boolean onTouchDown(@NonNull View view, @NonNull MotionEvent event) {
                 if (bubbleBarView.isExpanded()) return false;
@@ -196,50 +280,74 @@
 
             @Override
             void onDragStart() {
-                mBubbleBarPinController.setListener(mLocationChangeListener);
                 initialRelativePivot.set(bubbleBarView.getRelativePivotX(),
                         bubbleBarView.getRelativePivotY());
                 // By default the bubble bar view pivot is in bottom right corner, while dragging
                 // it should be centered in order to align it with the dismiss target view
                 bubbleBarView.setRelativePivot(/* x = */ 0.5f, /* y = */ 0.5f);
                 bubbleBarView.setIsDragging(true);
-                mBubbleBarPinController.onDragStart(
-                        bubbleBarView.getBubbleBarLocation().isOnLeft(bubbleBarView.isLayoutRtl()));
+                if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+                    DraggedObject.BubbleBar draggedBubbleBar = new DraggedObject.BubbleBar(
+                            mBubbleBarViewController.getBubbleBarLocation());
+                    mDropTargetManager.onDragStarted(draggedBubbleBar,
+                            mDragZoneFactory.createSortedDragZones(draggedBubbleBar));
+                } else {
+                    mBubbleBarPinController.setListener(mLocationChangeListener);
+                    mBubbleBarPinController.onDragStart(
+                            bubbleBarView.getBubbleBarLocation().isOnLeft(
+                                    bubbleBarView.isLayoutRtl()));
+                }
             }
 
             @Override
             protected void onDragUpdate(float x, float y, float newTx, float newTy) {
                 bubbleBarView.setTranslationX(newTx);
                 bubbleBarView.setTranslationY(newTy);
-                mBubbleBarPinController.onDragUpdate(x, y);
+                if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+                    mDropTargetManager.onDragUpdated((int) x, (int) y);
+                } else {
+                    mBubbleBarPinController.onDragUpdate(x, y);
+                }
             }
 
             @Override
             protected void onDragRelease() {
-                mBubbleBarPinController.onDragEnd();
+                if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+                    mDropTargetManager.onDragEnded();
+                } else {
+                    mBubbleBarPinController.onDragEnd();
+                }
             }
 
             @Override
             protected void onDragDismiss() {
-                mBubbleBarPinController.onDragEnd();
+                if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+                    mDropTargetManager.onDragEnded();
+                } else {
+                    mBubbleBarPinController.onDragEnd();
+                }
             }
 
             @Override
             void onDragEnd() {
                 // Make sure to update location as the first thing. Pivot update causes a relayout
-                mBubbleBarController.updateBubbleBarLocation(mReleasedLocation,
+                mBubbleBarController.updateBubbleBarLocation(getBubbleBarLocationDuringDrag(),
                         BubbleBarLocation.UpdateSource.DRAG_BAR);
                 bubbleBarView.setIsDragging(false);
                 // Restoring the initial pivot for the bubble bar view
                 bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y);
                 mBubbleBarViewController.onBubbleBarDragEnd();
-                mBubbleBarPinController.setListener(null);
+                if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+                    mDropTargetManager.onDragEnded();
+                } else {
+                    mBubbleBarPinController.setListener(null);
+                }
             }
 
             @Override
             protected PointF getRestingPosition() {
                 return mBubbleBarViewController.getBubbleBarDragReleaseTranslation(
-                        getInitialPosition(), mReleasedLocation);
+                        getInitialPosition(), getBubbleBarLocationDuringDrag());
             }
         });
     }
@@ -299,7 +407,7 @@
         private final PointF mTouchDownLocation = new PointF();
         private final PointF mViewInitialPosition = new PointF();
         private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
-        private final long mPressToDragTimeout = ViewConfiguration.getLongPressTimeout() / 2;
+        private final long mPressToDragTimeout = ViewConfiguration.getLongPressTimeout();
         private State mState = State.IDLE;
         private int mTouchSlop = -1;
         private BubbleDragAnimator mAnimator;
@@ -479,8 +587,17 @@
                 mAnimator.animateDismiss(mViewInitialPosition, onComplete);
             } else {
                 onDragRelease();
-                mAnimator.animateToRestingState(getRestingPosition(), getCurrentVelocity(),
+                if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+                    if (mBubbleDragZoneChangedListener.isDraggedToFullscreen()) {
+                        onComplete.run();
+                    } else {
+                        mAnimator.animateToRestingState(getRestingPosition(), getCurrentVelocity(),
+                                onComplete);
+                    }
+                } else {
+                    mAnimator.animateToRestingState(getRestingPosition(), getCurrentVelocity(),
                         onComplete);
+                }
             }
             mBubbleDismissController.hideDismissView();
         }
@@ -520,4 +637,46 @@
             return new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
         }
     }
+
+    private class BubbleDragZoneChangedListener implements DragZoneChangedListener {
+
+        private BubbleBarLocation mBubbleBarLocation = BubbleBarLocation.DEFAULT;
+        private DragZone mDragZone;
+
+        boolean isDraggedToFullscreen() {
+            return mDragZone instanceof DragZone.FullScreen;
+        }
+
+        @Override
+        public void onInitialDragZoneSet(@NonNull DragZone dragZone) {
+            mDragZone = dragZone;
+            if (dragZone instanceof DragZone.Bubble.Left) {
+                mBubbleBarLocation = BubbleBarLocation.LEFT;
+            } else if (dragZone instanceof DragZone.Bubble.Right) {
+                mBubbleBarLocation = BubbleBarLocation.RIGHT;
+            }
+        }
+
+        @Override
+        public void onDragZoneChanged(@NonNull DraggedObject draggedObject, @NonNull DragZone from,
+                @NonNull DragZone to) {
+            mDragZone = to;
+            if (to instanceof DragZone.Bubble.Left
+                    && mBubbleBarLocation != BubbleBarLocation.LEFT) {
+                if (draggedObject instanceof DraggedObject.Bubble) {
+                    mBubbleBarController.animateBubbleBarLocation(BubbleBarLocation.LEFT);
+                }
+                mBubbleBarLocation = BubbleBarLocation.LEFT;
+            } else if (to instanceof DragZone.Bubble.Right
+                    && mBubbleBarLocation != BubbleBarLocation.RIGHT) {
+                if (draggedObject instanceof DraggedObject.Bubble) {
+                    mBubbleBarController.animateBubbleBarLocation(BubbleBarLocation.RIGHT);
+                }
+                mBubbleBarLocation = BubbleBarLocation.RIGHT;
+            }
+        }
+
+        @Override
+        public void onDragEnded(@NonNull DragZone zone) {}
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt
index 3604167..26d6ccc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleAnimator.kt
@@ -18,6 +18,8 @@
 
 import androidx.core.animation.Animator
 import androidx.core.animation.ValueAnimator
+import kotlin.math.max
+import kotlin.math.min
 
 /**
  * Animates individual bubbles within the bubble bar while the bubble bar is expanded.
@@ -39,9 +41,14 @@
     private var state: State = State.Idle
     private lateinit var animator: ValueAnimator
 
-    fun animateNewBubble(selectedBubbleIndex: Int, listener: Listener) {
+    @JvmOverloads
+    fun animateNewBubble(
+        selectedBubbleIndex: Int,
+        newlySelectedBubbleIndex: Int? = null,
+        listener: Listener,
+    ) {
         animator = createAnimator(listener)
-        state = State.AddingBubble(selectedBubbleIndex)
+        state = State.AddingBubble(selectedBubbleIndex, newlySelectedBubbleIndex)
         animator.start()
     }
 
@@ -65,14 +72,18 @@
 
     fun animateNewAndRemoveOld(
         selectedBubbleIndex: Int,
+        newlySelectedBubbleIndex: Int,
         removedBubbleIndex: Int,
+        addedBubbleIndex: Int,
         listener: Listener,
     ) {
         animator = createAnimator(listener)
         state =
             State.AddingAndRemoving(
                 selectedBubbleIndex = selectedBubbleIndex,
+                newlySelectedBubbleIndex = newlySelectedBubbleIndex,
                 removedBubbleIndex = removedBubbleIndex,
+                addedBubbleIndex = addedBubbleIndex,
             )
         animator.start()
     }
@@ -132,6 +143,7 @@
                 getBubbleTranslationXWhileAddingBubbleAtLimit(
                     bubbleIndex = bubbleIndex,
                     removedBubbleIndex = state.removedBubbleIndex,
+                    addedBubbleIndex = state.addedBubbleIndex,
                     addedBubbleScale = animator.animatedFraction,
                     removedBubbleScale = 1 - animator.animatedFraction,
                 )
@@ -180,31 +192,33 @@
     fun getArrowPosition(): Float {
         return when (val state = state) {
             State.Idle -> 0f
-            is State.AddingBubble -> {
-                val tx =
-                    getBubbleTranslationXWhileScalingBubble(
-                        bubbleIndex = state.selectedBubbleIndex,
-                        scalingBubbleIndex = 0,
-                        bubbleScale = animator.animatedFraction,
-                    )
-                tx + iconSize / 2f
-            }
-
+            is State.AddingBubble -> getArrowPositionWhenAddingBubble(state)
             is State.RemovingBubble -> getArrowPositionWhenRemovingBubble(state)
-            is State.AddingAndRemoving -> {
-                // we never remove the selected bubble, so the arrow stays pointing to its center
-                val tx =
-                    getBubbleTranslationXWhileAddingBubbleAtLimit(
-                        bubbleIndex = state.selectedBubbleIndex,
-                        removedBubbleIndex = state.removedBubbleIndex,
-                        addedBubbleScale = animator.animatedFraction,
-                        removedBubbleScale = 1 - animator.animatedFraction,
-                    )
-                tx + iconSize / 2f
-            }
+            is State.AddingAndRemoving -> getArrowPositionWhenAddingAndRemovingBubble(state)
         }
     }
 
+    private fun getArrowPositionWhenAddingBubble(state: State.AddingBubble): Float {
+        val scale = animator.animatedFraction
+        var tx =
+            getBubbleTranslationXWhileScalingBubble(
+                bubbleIndex = state.selectedBubbleIndex,
+                scalingBubbleIndex = 0,
+                bubbleScale = scale,
+            ) + iconSize / 2f
+        if (state.newlySelectedBubbleIndex != null) {
+            val selectedBubbleScale = if (state.newlySelectedBubbleIndex == 0) scale else 1f
+            val finalTx =
+                getBubbleTranslationXWhileScalingBubble(
+                    bubbleIndex = state.newlySelectedBubbleIndex,
+                    scalingBubbleIndex = 0,
+                    bubbleScale = scale,
+                ) + iconSize * selectedBubbleScale / 2f
+            tx += (finalTx - tx) * animator.animatedFraction
+        }
+        return tx
+    }
+
     private fun getArrowPositionWhenRemovingBubble(state: State.RemovingBubble): Float =
         if (state.selectedBubbleIndex != state.bubbleIndex || state.removingLastRemainingBubble) {
             // if we're not removing the selected bubble or if we're removing the last remaining
@@ -250,6 +264,46 @@
             }
         }
 
+    private fun getArrowPositionWhenAddingAndRemovingBubble(state: State.AddingAndRemoving): Float {
+        // The bubble bar keeps constant width while adding and removing bubble. So we just need to
+        // find selected bubble arrow position on the animation start and newly selected bubble
+        // arrow position on the animation end interpolating the arrow between these positions
+        // during the animation.
+        // The indexes in the state are provided for the bubble bar containing all bubbles. So for
+        // certain circumstances indexes should be adjusted.
+        // When animation is started added bubble has zero scale as well as removed bubble when the
+        // animation is ended, so for both cases we should compute translation as it is one less
+        // bubble.
+        val bubbleCountOnEnd = bubbleCount - 1
+        var selectedIndex = state.selectedBubbleIndex
+        // We only need to adjust the selected index if added bubble was added before the selected.
+        if (selectedIndex > state.addedBubbleIndex) {
+            // If the selectedIndex is higher index than the added bubble index, we need to reduce
+            // selectedIndex by one because the added bubble  has zero scale when animation is
+            // started.
+            selectedIndex--
+        }
+        var newlySelectedIndex = state.newlySelectedBubbleIndex
+        // We only need to adjust newlySelectedIndex if removed bubble was removed before the newly
+        // selected bubble.
+        if (newlySelectedIndex > state.removedBubbleIndex) {
+            // If the newlySelectedIndex is higher index than the removed bubble index, we need to
+            // reduce newlySelectedIndex by one because the removed bubble has zero scale when
+            // animation is ended.
+            newlySelectedIndex--
+        }
+        val iconAndSpacing: Float = iconSize + expandedBarIconSpacing
+        val startTx = getBubblesToTheLeft(selectedIndex, bubbleCountOnEnd) * iconAndSpacing
+        val endTx = getBubblesToTheLeft(newlySelectedIndex, bubbleCountOnEnd) * iconAndSpacing
+        val tx = startTx + (endTx - startTx) * animator.animatedFraction
+        return tx + iconSize / 2f
+    }
+
+    private fun getBubblesToTheLeft(bubbleIndex: Int, bubbleCount: Int = this.bubbleCount): Int =
+        // when bar is on left the index - 0 corresponds to the right - most bubble and when the
+        // bubble bar is on the right - 0 corresponds to the left - most bubble.
+        if (onLeft) bubbleCount - bubbleIndex - 1 else bubbleIndex
+
     /**
      * 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.
@@ -274,6 +328,7 @@
                     // 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
@@ -283,6 +338,7 @@
                     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
@@ -294,10 +350,12 @@
                     // 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
@@ -309,61 +367,67 @@
     private fun getBubbleTranslationXWhileAddingBubbleAtLimit(
         bubbleIndex: Int,
         removedBubbleIndex: Int,
+        addedBubbleIndex: Int,
         addedBubbleScale: Float,
         removedBubbleScale: Float,
     ): Float {
         val iconAndSpacing = iconSize + expandedBarIconSpacing
         // the bubbles are scaling from the center, so we need to adjust their translation so
         // that the distance to the adjacent bubble scales at the same rate.
-        val addedBubblePivotAdjustment = -(1 - addedBubbleScale) * iconSize / 2f
-        val removedBubblePivotAdjustment = -(1 - removedBubbleScale) * iconSize / 2f
+        val addedBubblePivotAdjustment = (addedBubbleScale - 1) * iconSize / 2f
+        val removedBubblePivotAdjustment = (removedBubbleScale - 1) * iconSize / 2f
 
-        return if (onLeft) {
-            // this is how many bubbles there are to the left of the current bubble.
-            // when the bubble bar is on the right the added bubble is the right-most bubble so it
-            // doesn't affect the translation of any other bubble.
-            // when the removed bubble is to the left of the current bubble, we need to subtract it
-            // from bubblesToLeft and use removedBubbleScale instead when calculating the
-            // translation.
-            val bubblesToLeft = bubbleCount - bubbleIndex - 1
-            when {
-                bubbleIndex == 0 ->
-                    // this is the added bubble and it's the right-most bubble. account for all the
-                    // other bubbles -- including the removed bubble -- and adjust for the added
-                    // bubble pivot.
-                    (bubblesToLeft - 1 + removedBubbleScale) * iconAndSpacing +
-                        addedBubblePivotAdjustment
-                bubbleIndex < removedBubbleIndex ->
+        val minAddedRemovedIndex = min(addedBubbleIndex, removedBubbleIndex)
+        val maxAddedRemovedIndex = max(addedBubbleIndex, removedBubbleIndex)
+        val isBetweenAddedAndRemoved =
+            bubbleIndex in (minAddedRemovedIndex + 1)..<maxAddedRemovedIndex
+        val isRemovedBubbleToLeftOfAddedBubble = onLeft == addedBubbleIndex < removedBubbleIndex
+        val bubblesToLeft = getBubblesToTheLeft(bubbleIndex)
+        return when {
+            isBetweenAddedAndRemoved -> {
+                if (isRemovedBubbleToLeftOfAddedBubble) {
                     // the removed bubble is to the left so account for it
                     (bubblesToLeft - 1 + removedBubbleScale) * iconAndSpacing
-                bubbleIndex == removedBubbleIndex -> {
-                    // this is the removed bubble. all the bubbles to the left are at full scale
-                    // but we need to scale the spacing between the removed bubble and the bubble to
-                    // its left because the removed bubble disappears towards the left side
+                } else {
+                    // the added bubble is to the left so account for it
+                    (bubblesToLeft - 1 + addedBubbleScale) * iconAndSpacing
+                }
+            }
+
+            bubbleIndex == addedBubbleIndex -> {
+                if (isRemovedBubbleToLeftOfAddedBubble) {
+                    // the removed bubble is to the left so account for it
+                    (bubblesToLeft - 1 + removedBubbleScale) * iconAndSpacing
+                } else {
+                    // it's the left-most scaling bubble, all bubbles on the left are at full scale
+                    bubblesToLeft * iconAndSpacing
+                } + addedBubblePivotAdjustment
+            }
+
+            bubbleIndex == removedBubbleIndex -> {
+                if (isRemovedBubbleToLeftOfAddedBubble) {
+                    // All the bubbles to the left are at full scale, but we need to scale the
+                    // spacing between the removed bubble and the bubble next to it
                     val totalIconSize = bubblesToLeft * iconSize
                     val totalSpacing =
                         (bubblesToLeft - 1 + removedBubbleScale) * expandedBarIconSpacing
-                    totalIconSize + totalSpacing + removedBubblePivotAdjustment
-                }
-                else ->
-                    // both added and removed bubbles are to the right so they don't affect the tx
-                    bubblesToLeft * iconAndSpacing
+                    totalIconSize + totalSpacing
+                } else {
+                    // The added bubble is to the left, so account for it
+                    (bubblesToLeft - 1 + addedBubbleScale) * iconAndSpacing
+                } + removedBubblePivotAdjustment
             }
-        } else {
-            when {
-                bubbleIndex == 0 -> addedBubblePivotAdjustment // we always add bubbles at index 0
-                bubbleIndex < removedBubbleIndex ->
-                    // the bar is on the right and the removed bubble is on the right. the current
-                    // bubble is unaffected by the removed bubble. only need to factor in the added
-                    // bubble's scale.
-                    iconAndSpacing * (bubbleIndex - 1 + addedBubbleScale)
-                bubbleIndex == removedBubbleIndex ->
-                    // the bar is on the right, and this is the animating bubble.
-                    iconAndSpacing * (bubbleIndex - 1 + addedBubbleScale) +
-                        removedBubblePivotAdjustment
-                else ->
-                    // both the added and the removed bubbles are to the left of the current bubble
-                    iconAndSpacing * (bubbleIndex - 2 + addedBubbleScale + removedBubbleScale)
+
+            else -> {
+                // if bubble index is on the right side of the animated bubbles, we need to deduct
+                // one, since both the added and the removed bubbles takes a single place
+                val onTheRightOfAnimatedBubbles =
+                    if (onLeft) {
+                        bubbleIndex < minAddedRemovedIndex
+                    } else {
+                        bubbleIndex > maxAddedRemovedIndex
+                    }
+                (bubblesToLeft - if (onTheRightOfAnimatedBubbles) 1 else 0) * iconAndSpacing
             }
         }
     }
@@ -378,7 +442,12 @@
         data object Idle : State
 
         /** A new bubble is being added to the bubble bar. */
-        data class AddingBubble(val selectedBubbleIndex: Int) : State
+        data class AddingBubble(
+            /** The index of the selected bubble. */
+            val selectedBubbleIndex: Int,
+            /** The index of the newly selected bubble. */
+            val newlySelectedBubbleIndex: Int?,
+        ) : State
 
         /** A bubble is being removed from the bubble bar. */
         data class RemovingBubble(
@@ -393,8 +462,16 @@
         ) : State
 
         /** A new bubble is being added and an old bubble is being removed from the bubble bar. */
-        data class AddingAndRemoving(val selectedBubbleIndex: Int, val removedBubbleIndex: Int) :
-            State
+        data class AddingAndRemoving(
+            /** The index of the selected bubble. */
+            val selectedBubbleIndex: Int,
+            /** The index of the newly selected bubble. */
+            val newlySelectedBubbleIndex: Int,
+            /** The index of the bubble being removed. */
+            val removedBubbleIndex: Int,
+            /** The index of the added bubble. */
+            val addedBubbleIndex: Int,
+        ) : State
     }
 
     /** Callbacks for the animation. */
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 3bff58b..0da8c1f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimator.kt
@@ -215,6 +215,7 @@
         animator.spring(DynamicAnimation.TRANSLATION_Y, totalTranslationY, initialVelocity ?: 0f)
         animator.addUpdateListener { handle, values ->
             val ty = values[DynamicAnimation.TRANSLATION_Y]?.value ?: return@addUpdateListener
+            if (animatingBubble == null) return@addUpdateListener
             when {
                 ty >= stashedHandleTranslationYForAnimation -> {
                     // we're in the first leg of the animation. only animate the handle. the bubble
@@ -365,7 +366,12 @@
     }
 
     /** Animates to the initial state of the bubble bar, when there are no previous bubbles. */
-    fun animateToInitialState(b: BubbleBarBubble, isInApp: Boolean, isExpanding: Boolean) {
+    fun animateToInitialState(
+        b: BubbleBarBubble,
+        isInApp: Boolean,
+        isExpanding: Boolean,
+        isDragging: Boolean = false,
+    ) {
         val bubbleView = b.view
         val animator = PhysicsAnimator.getInstance(bubbleView)
         if (animator.isRunning()) animator.cancel()
@@ -374,15 +380,12 @@
         // bubble bar to the handle if we're in an app.
         val showAnimation = buildBubbleBarSpringInAnimation()
         val hideAnimation =
-            if (isInApp && !isExpanding) {
+            if (isInApp && !isExpanding && !isDragging) {
                 buildBubbleBarToHandleAnimation()
             } else {
                 Runnable {
-                    moveToState(AnimatingBubble.State.ANIMATING_OUT)
-                    bubbleBarFlyoutController.collapseFlyout {
-                        onFlyoutRemoved()
-                        clearAnimatingBubble()
-                    }
+                    collapseFlyoutAndUpdateState()
+                    if (isDragging) return@Runnable
                     bubbleStashController.showBubbleBarImmediate()
                     bubbleStashController.updateTaskbarTouchRegion()
                 }
@@ -440,11 +443,7 @@
         // first bounce the bubble bar and show the flyout. Then hide the flyout.
         val showAnimation = buildBubbleBarBounceAnimation()
         val hideAnimation = Runnable {
-            moveToState(AnimatingBubble.State.ANIMATING_OUT)
-            bubbleBarFlyoutController.collapseFlyout {
-                onFlyoutRemoved()
-                clearAnimatingBubble()
-            }
+            collapseFlyoutAndUpdateState()
             bubbleStashController.showBubbleBarImmediate()
             bubbleStashController.updateTaskbarTouchRegion()
         }
@@ -454,6 +453,14 @@
         scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
     }
 
+    private fun collapseFlyoutAndUpdateState() {
+        moveToState(AnimatingBubble.State.ANIMATING_OUT)
+        bubbleBarFlyoutController.collapseFlyout {
+            onFlyoutRemoved()
+            clearAnimatingBubble()
+        }
+    }
+
     /**
      * 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
@@ -531,7 +538,7 @@
         bubbleStashController.getStashedHandlePhysicsAnimator().cancelIfRunning()
         resetBubbleBarPropertiesOnInterrupt()
         bubbleStashController.onNewBubbleAnimationInterrupted(
-            /* isStashed= */ bubbleBarView.alpha == 0f,
+            /* isStashed= */ bubbleStashController.isStashed,
             bubbleBarView.translationY,
         )
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
index 595dac3..fec1eaf 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
@@ -24,6 +24,7 @@
 import com.android.launcher3.taskbar.bubbles.BubbleBarView
 import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
 import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
+import com.android.launcher3.util.MultiPropertyFactory
 import com.android.wm.shell.shared.animation.PhysicsAnimator
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation
 import java.io.PrintWriter
@@ -172,6 +173,9 @@
     /** Returns bounds of the handle */
     fun getHandleBounds(bounds: Rect)
 
+    /** Returns MultiValueAlpha of the handle view when the handle view is shown. */
+    fun getHandleViewAlpha(): MultiPropertyFactory<View>.MultiProperty? = null
+
     /**
      * Returns bubble bar Y position according to [isBubblesShowingOnHome] and
      * [isBubblesShowingOnOverview] values. Default implementation only analyse
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
index 3e3f569..9c148e2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
@@ -512,6 +512,14 @@
         }
     }
 
+    override fun getHandleViewAlpha(): MultiPropertyFactory<View>.MultiProperty? =
+        // only return handle alpha if the bubble bar is stashed and has bubbles
+        if (isStashed && bubbleBarViewController.hasBubbles()) {
+            stashHandleViewAlpha
+        } else {
+            null
+        }
+
     private fun Animator.updateTouchRegionOnAnimationEnd(): Animator {
         doOnEnd { onIsStashedChanged() }
         return this
diff --git a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
index 822ca64..f1ed6c5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/customization/TaskbarSpecsEvaluator.kt
@@ -26,6 +26,8 @@
     numColumns: Int = taskbarActivityContext.deviceProfile.inv.numColumns,
 ) {
     var taskbarIconSize: TaskbarIconSize = getIconSizeByGrid(numColumns, numRows)
+    val numShownHotseatIcons
+        get() = taskbarActivityContext.deviceProfile.numShownHotseatIcons
 
     // TODO(b/341146605) : initialize it to taskbar container in later cl.
     private var taskbarContainer: List<TaskbarContainer> = emptyList()
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestInformationRequest.java b/quickstep/src/com/android/launcher3/taskbar/growth/GrowthConstants.java
similarity index 60%
rename from tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestInformationRequest.java
rename to quickstep/src/com/android/launcher3/taskbar/growth/GrowthConstants.java
index 38282032..78ef152 100644
--- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestInformationRequest.java
+++ b/quickstep/src/com/android/launcher3/taskbar/growth/GrowthConstants.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,17 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package com.android.launcher3.testing.shared;
-
-import android.os.Parcelable;
+package com.android.launcher3.taskbar.growth;
 
 /**
- * A Request sent to TestInformationHandler can implement this interface to carry more information.
+ * Constants for registering Growth framework.
  */
-public interface TestInformationRequest extends Parcelable {
+public final class GrowthConstants {
     /**
-     * The name for handler to dispatch request.
+     * For Taskbar broadcast intent filter.
      */
-    String getRequestName();
+    public static final String BROADCAST_SHOW_NUDGE =
+            "com.android.launcher3.growth.BROADCAST_SHOW_NUDGE";
+    private GrowthConstants() {}
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/growth/NudgePayload.kt b/quickstep/src/com/android/launcher3/taskbar/growth/NudgePayload.kt
new file mode 100644
index 0000000..7498cbc
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/growth/NudgePayload.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.taskbar.growth
+
+sealed interface Action {
+    data class Dismiss(
+        val markAsDismissed: Boolean = true,
+        val dismissRetentionInDays: Int? = null,
+    ) : Action
+
+    data class OpenUrl(val url: String) : Action
+}
+
+sealed class Image {
+    data class ResourceId(val resId: Int) : Image()
+
+    data class Url(val url: String) : Image()
+}
+
+data class ButtonPayload(val label: String, val actions: List<Action>)
+
+data class NudgePayload(
+    val titleText: String,
+    val bodyText: String,
+    val image: Image?,
+    val primaryButton: ButtonPayload?,
+    val secondaryButton: ButtonPayload?,
+
+    // TODO: b/396223717 - add anchoring information.
+)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
index 22a3630..e032430 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -17,6 +17,7 @@
 package com.android.launcher3.taskbar.navbutton
 
 import android.content.res.Resources
+import android.os.SystemProperties
 import android.view.Gravity
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
@@ -39,7 +40,7 @@
     startContextualContainer: ViewGroup,
     imeSwitcher: ImageView?,
     a11yButton: ImageView?,
-    space: Space?
+    space: Space?,
 ) :
     AbstractNavButtonLayoutter(
         resources,
@@ -48,11 +49,15 @@
         startContextualContainer,
         imeSwitcher,
         a11yButton,
-        space
+        space,
     ) {
     private val mNavButtonsView = navButtonsView
 
     override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
+        val SUWTheme = SystemProperties.get("setupwizard.theme", "")
+        if (SUWTheme == "glif_expressive" || SUWTheme == "glif_expressive_light") {
+            return
+        }
         // Since setup wizard only has back button enabled, it looks strange to be
         // end-aligned, so start-align instead.
         val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
@@ -80,7 +85,7 @@
             adjustForSetupInPhoneMode(
                 navButtonsLayoutParams,
                 navButtonsViewLayoutParams,
-                deviceProfile
+                deviceProfile,
             )
         }
         mNavButtonsView.layoutParams = navButtonsViewLayoutParams
@@ -97,7 +102,7 @@
             WRAP_CONTENT,
             contextualMargin,
             contextualMargin,
-            Gravity.START
+            Gravity.START,
         )
 
         if (imeSwitcher != null) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
index 64cc47c..55bb0f9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/overlay/TaskbarOverlayContext.java
@@ -18,12 +18,11 @@
 import android.content.Context;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
-import com.android.launcher3.dot.DotInfo;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.taskbar.BaseTaskbarContext;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
@@ -65,6 +64,7 @@
         mStashedTaskbarHeight = controllers.taskbarStashController.getStashedHeight();
 
         mUiController = controllers.uiController;
+        onViewCreated();
     }
 
     public @Nullable TaskbarSearchSessionController getSearchSessionController() {
@@ -116,11 +116,6 @@
     }
 
     @Override
-    public boolean isBindingItems() {
-        return mTaskbarContext.isBindingItems();
-    }
-
-    @Override
     public View.OnClickListener getItemOnClickListener() {
         return mTaskbarContext.getItemOnClickListener();
     }
@@ -130,6 +125,7 @@
         return mDragController::startDragOnLongClick;
     }
 
+    @NonNull
     @Override
     public PopupDataProvider getPopupDataProvider() {
         return mTaskbarContext.getPopupDataProvider();
@@ -141,11 +137,6 @@
     }
 
     @Override
-    public DotInfo getDotInfoForItem(ItemInfo info) {
-        return mTaskbarContext.getDotInfoForItem(info);
-    }
-
-    @Override
     public void onDragStart() {}
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
deleted file mode 100644
index 721c831..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.uioverrides;
-
-import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
-import static com.android.app.animation.Interpolators.AGGRESSIVE_EASE_IN_OUT;
-import static com.android.app.animation.Interpolators.FINAL_FRAME;
-import static com.android.app.animation.Interpolators.INSTANT;
-import static com.android.app.animation.Interpolators.LINEAR;
-import static com.android.launcher3.Flags.enableLargeDesktopWindowingTile;
-import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
-import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
-import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
-import static com.android.quickstep.views.RecentsView.DESKTOP_CAROUSEL_DETACH_PROGRESS;
-import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
-import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
-import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
-import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA;
-
-import android.util.FloatProperty;
-import android.view.animation.Interpolator;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.statemanager.StateManager.StateHandler;
-import com.android.launcher3.states.StateAnimationConfig;
-import com.android.quickstep.views.RecentsView;
-
-/**
- * State handler for recents view. Manages UI changes and animations for recents view based off the
- * current {@link LauncherState}.
- *
- * @param <T> the recents view
- */
-public abstract class BaseRecentsViewStateController<T extends RecentsView>
-        implements StateHandler<LauncherState> {
-    protected final T mRecentsView;
-    protected final QuickstepLauncher mLauncher;
-
-    public BaseRecentsViewStateController(@NonNull QuickstepLauncher launcher) {
-        mLauncher = launcher;
-        mRecentsView = launcher.getOverviewPanel();
-    }
-
-    @Override
-    public void setState(@NonNull LauncherState state) {
-        float[] scaleAndOffset = state.getOverviewScaleAndOffset(mLauncher);
-        RECENTS_SCALE_PROPERTY.set(mRecentsView, scaleAndOffset[0]);
-        ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mRecentsView, scaleAndOffset[1]);
-        TASK_SECONDARY_TRANSLATION.set(mRecentsView, 0f);
-
-        getContentAlphaProperty().set(mRecentsView, state.isRecentsViewVisible ? 1f : 0);
-        getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
-        RECENTS_GRID_PROGRESS.set(mRecentsView,
-                state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f);
-        TASK_THUMBNAIL_SPLASH_ALPHA.set(mRecentsView, state.showTaskThumbnailSplash() ? 1f : 0f);
-        if (enableLargeDesktopWindowingTile()) {
-            DESKTOP_CAROUSEL_DETACH_PROGRESS.set(mRecentsView,
-                    state.detachDesktopCarousel() ? 1f : 0f);
-        }
-    }
-
-    @Override
-    public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
-            PendingAnimation builder) {
-        if (config.hasAnimationFlag(SKIP_OVERVIEW)) {
-            return;
-        }
-        setStateWithAnimationInternal(toState, config, builder);
-        builder.addEndListener(success -> {
-            if (!success && !toState.isRecentsViewVisible) {
-                mRecentsView.reset();
-            }
-        });
-    }
-
-    /**
-     * Core logic for animating the recents view UI.
-     *
-     * @param toState state to animate to
-     * @param config current animation config
-     * @param setter animator set builder
-     */
-    void setStateWithAnimationInternal(@NonNull final LauncherState toState,
-            @NonNull StateAnimationConfig config, @NonNull PendingAnimation setter) {
-        float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
-        setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
-                config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
-        setter.setFloat(mRecentsView, ADJACENT_PAGE_HORIZONTAL_OFFSET, scaleAndOffset[1],
-                config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
-        setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
-                config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
-
-        boolean exitingOverview =
-                !FeatureFlags.enableSplitContextually() && !toState.isRecentsViewVisible;
-        if (mRecentsView.isSplitSelectionActive() && exitingOverview) {
-            setter.add(mRecentsView.getSplitSelectController().getSplitAnimationController()
-                    .createPlaceholderDismissAnim(mLauncher, LAUNCHER_SPLIT_SELECTION_EXIT_HOME,
-                            setter.getDuration()));
-            setter.setViewAlpha(
-                    mRecentsView.getSplitInstructionsView(),
-                    0,
-                    config.getInterpolator(
-                            ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE,
-                            LINEAR
-                    )
-            );
-        }
-
-        setter.setFloat(mRecentsView, getContentAlphaProperty(),
-                toState.isRecentsViewVisible ? 1 : 0,
-                config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
-
-        setter.setFloat(
-                mRecentsView, getTaskModalnessProperty(),
-                toState.getOverviewModalness(),
-                config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
-
-        LauncherState fromState = mLauncher.getStateManager().getState();
-        setter.setFloat(mRecentsView, TASK_THUMBNAIL_SPLASH_ALPHA,
-                toState.showTaskThumbnailSplash() ? 1f : 0f,
-                getOverviewInterpolator(fromState, toState));
-
-        setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
-                toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f,
-                getOverviewInterpolator(fromState, toState));
-
-        if (enableLargeDesktopWindowingTile()) {
-            setter.setFloat(mRecentsView, DESKTOP_CAROUSEL_DETACH_PROGRESS,
-                    toState.detachDesktopCarousel() ? 1f : 0f,
-                    getOverviewInterpolator(fromState, toState));
-        }
-    }
-
-    private Interpolator getOverviewInterpolator(LauncherState fromState, LauncherState toState) {
-        return fromState == QUICK_SWITCH_FROM_HOME
-                ? ACCELERATE_DECELERATE
-                : toState.isRecentsViewVisible ? INSTANT : FINAL_FRAME;
-    }
-
-    abstract FloatProperty getTaskModalnessProperty();
-
-    /**
-     * Get property for content alpha for the recents view.
-     *
-     * @return the float property for the view's content alpha
-     */
-    abstract FloatProperty getContentAlphaProperty();
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index 535ae1c..15a27d1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -16,8 +16,8 @@
 package com.android.launcher3.uioverrides;
 
 import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
-import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
 import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
+import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -26,8 +26,6 @@
 import android.animation.Keyframe;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
-import android.animation.ValueAnimator;
-import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.BlurMaskFilter;
 import android.graphics.Canvas;
@@ -37,9 +35,9 @@
 import android.graphics.Path;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Process;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
+import android.util.Log;
 import android.util.Property;
 import android.view.LayoutInflater;
 import android.view.ViewGroup;
@@ -47,15 +45,15 @@
 import androidx.core.graphics.ColorUtils;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.celllayout.DelegatedCellDrawing;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.GraphicsUtils;
-import com.android.launcher3.icons.IconNormalizer;
+import com.android.launcher3.graphics.ThemeManager;
+import com.android.launcher3.icons.FastBitmapDrawable;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -64,10 +62,6 @@
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.DoubleShadowBubbleTextView;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
 /**
  * A BubbleTextView with a ring around it's drawable
  */
@@ -75,8 +69,7 @@
 
     private static final float RING_SCALE_START_VALUE = 0.75f;
     private static final int RING_SHADOW_COLOR = 0x99000000;
-    private static final float RING_EFFECT_RATIO = 0.095f;
-
+    private static final float RING_EFFECT_RATIO = Flags.enableLauncherIconShapes() ? 0.1f : 0.095f;
     private static final long ICON_CHANGE_ANIM_DURATION = 360;
     private static final long ICON_CHANGE_ANIM_STAGGER = 50;
 
@@ -105,12 +98,12 @@
     private final BlurMaskFilter mShadowFilter;
 
     private boolean mIsPinned = false;
-    private int mPlateColor;
+    private final AnimColorHolder mPlateColor = new AnimColorHolder();
     boolean mDrawForDrag = false;
 
-    // Used for the "slot-machine" education animation.
-    private List<Drawable> mSlotMachineIcons;
-    private Animator mSlotMachineAnim;
+    // Used for the "slot-machine" animation when prediction changes.
+    private final Rect mSlotIconBound = new Rect(0, 0, getIconSize(), getIconSize());
+    private Drawable mSlotMachineIcon;
     private float mSlotMachineIconTranslationY;
 
     // Used to animate the "ring" around predicted icons
@@ -118,6 +111,8 @@
     private boolean mForceHideRing = false;
     private Animator mRingScaleAnim;
 
+    private int mWidth;
+
     private static final FloatProperty<PredictedAppIcon> SLOT_MACHINE_TRANSLATION_Y =
             new FloatProperty<PredictedAppIcon>("slotMachineTranslationY") {
         @Override
@@ -143,44 +138,36 @@
     public PredictedAppIcon(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         mDeviceProfile = ActivityContext.lookupContext(context).getDeviceProfile();
-        mNormalizedIconSize = IconNormalizer.getNormalizedCircleSize(getIconSize());
+        mNormalizedIconSize = Math.round(getIconSize() * ICON_VISIBLE_AREA_FACTOR);
         int shadowSize = context.getResources().getDimensionPixelSize(
                 R.dimen.blur_size_thin_outline);
         mShadowFilter = new BlurMaskFilter(shadowSize, BlurMaskFilter.Blur.OUTER);
-        mShapePath = GraphicsUtils.getShapePath(context, mNormalizedIconSize);
+        mShapePath = ThemeManager.INSTANCE.get(context).getIconShape().getPath(mNormalizedIconSize);
     }
 
     @Override
     public void onDraw(Canvas canvas) {
         int count = canvas.save();
-        boolean isSlotMachineAnimRunning = mSlotMachineAnim != null;
+        boolean isSlotMachineAnimRunning = mSlotMachineIcon != null;
         if (!mIsPinned) {
-            drawEffect(canvas);
+            drawRingEffect(canvas);
             if (isSlotMachineAnimRunning) {
                 // Clip to to outside of the ring during the slot machine animation.
                 canvas.clipPath(mRingPath);
             }
-            canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
-            canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
+            canvas.scale(1 - 2f * RING_EFFECT_RATIO, 1 - 2f * RING_EFFECT_RATIO,
+                    getWidth() * .5f, getHeight() * .5f);
+            if (isSlotMachineAnimRunning) {
+                canvas.translate(0, mSlotMachineIconTranslationY);
+                mSlotMachineIcon.setBounds(mSlotIconBound);
+                mSlotMachineIcon.draw(canvas);
+                canvas.translate(0, getSlotMachineIconPlusSpacingSize());
+            }
         }
-        if (isSlotMachineAnimRunning) {
-            drawSlotMachineIcons(canvas);
-        } else {
-            super.onDraw(canvas);
-        }
+        super.onDraw(canvas);
         canvas.restoreToCount(count);
     }
 
-    private void drawSlotMachineIcons(Canvas canvas) {
-        canvas.translate((getWidth() - getIconSize()) / 2f,
-                (getHeight() - getIconSize()) / 2f + mSlotMachineIconTranslationY);
-        for (Drawable icon : mSlotMachineIcons) {
-            icon.setBounds(0, 0, getIconSize(), getIconSize());
-            icon.draw(canvas);
-            canvas.translate(0, getSlotMachineIconPlusSpacingSize());
-        }
-    }
-
     private float getSlotMachineIconPlusSpacingSize() {
         return getIconSize() + getOutlineOffsetY();
     }
@@ -196,104 +183,88 @@
         mIsDrawingDot = false;
     }
 
-    @Override
-    public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
-        // Create the slot machine animation first, since it uses the current icon to start.
-        Animator slotMachineAnim = animate
-                ? createSlotMachineAnim(Collections.singletonList(info.bitmap), false)
-                : null;
-        super.applyFromWorkspaceItem(info, animate, staggerIndex);
-        int oldPlateColor = mPlateColor;
+    /**
+     * Returns whether the newInfo differs from the current getTag().
+     */
+    private boolean shouldAnimateIconChange(WorkspaceItemInfo newInfo) {
+        boolean changedIcons = getTag() instanceof WorkspaceItemInfo oldInfo
+                && oldInfo.getTargetComponent() != null
+                && newInfo.getTargetComponent() != null
+                && !oldInfo.getTargetComponent().equals(newInfo.getTargetComponent());
+        return changedIcons && isShown();
+    }
 
-        int newPlateColor;
+    @Override
+    public void applyIconAndLabel(ItemInfoWithIcon info) {
+        super.applyIconAndLabel(info);
         if (getIcon().isThemed()) {
-            newPlateColor = getResources().getColor(android.R.color.system_accent1_300);
+            mPlateColor.endColor = getResources().getColor(android.R.color.system_accent1_300);
         } else {
             float[] hctPlateColor = new float[3];
             ColorUtils.colorToM3HCT(mDotParams.appColor, hctPlateColor);
-            newPlateColor = ColorUtils.M3HCTToColor(hctPlateColor[0], 36, 85);
+            mPlateColor.endColor = ColorUtils.M3HCTToColor(hctPlateColor[0], 36, 85);
         }
+        mPlateColor.onUpdate();
+    }
+
+    /**
+     * Tries to apply the icon with animation and returns true if the icon was indeed animated
+     */
+    public boolean applyFromWorkspaceItemWithAnimation(WorkspaceItemInfo info, int staggerIndex) {
+        boolean animate = shouldAnimateIconChange(info);
+        Drawable oldIcon = getIcon();
+        int oldPlateColor = mPlateColor.currentColor;
+        applyFromWorkspaceItem(info);
+
+        setContentDescription(
+                mIsPinned ? info.contentDescription :
+                        getContext().getString(R.string.hotseat_prediction_content_description,
+                                info.contentDescription));
 
         if (!animate) {
-            mPlateColor = newPlateColor;
-        }
-        if (mIsPinned) {
-            setContentDescription(info.contentDescription);
+            mPlateColor.startColor = mPlateColor.endColor;
+            mPlateColor.progress.value = 1;
+            mPlateColor.onUpdate();
         } else {
-            setContentDescription(
-                    getContext().getString(R.string.hotseat_prediction_content_description,
-                            info.contentDescription));
-        }
+            mPlateColor.startColor = oldPlateColor;
+            mPlateColor.progress.value = 0;
+            mPlateColor.onUpdate();
 
-        if (animate) {
-            ValueAnimator plateColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(),
-                    oldPlateColor, newPlateColor);
-            plateColorAnim.addUpdateListener(valueAnimator -> {
-                mPlateColor = (int) valueAnimator.getAnimatedValue();
-                invalidate();
-            });
             AnimatorSet changeIconAnim = new AnimatorSet();
-            if (slotMachineAnim != null) {
+
+            ObjectAnimator plateColorAnim =
+                    ObjectAnimator.ofFloat(mPlateColor.progress, AnimatedFloat.VALUE, 0, 1);
+            plateColorAnim.setAutoCancel(true);
+            changeIconAnim.play(plateColorAnim);
+
+            if (!mIsPinned && oldIcon != null) {
+                // Play the slot machine icon
+                mSlotMachineIcon = oldIcon;
+
+                float finalTrans = -getSlotMachineIconPlusSpacingSize();
+                Keyframe[] keyframes = new Keyframe[] {
+                        Keyframe.ofFloat(0f, 0f),
+                        Keyframe.ofFloat(0.82f, finalTrans - getOutlineOffsetY() / 2f), // Overshoot
+                        Keyframe.ofFloat(1f, finalTrans) // Ease back into the final position
+                };
+                keyframes[1].setInterpolator(ACCELERATE_DECELERATE);
+                keyframes[2].setInterpolator(ACCELERATE_DECELERATE);
+
+                ObjectAnimator slotMachineAnim = ObjectAnimator.ofPropertyValuesHolder(this,
+                        PropertyValuesHolder.ofKeyframe(SLOT_MACHINE_TRANSLATION_Y, keyframes));
+                slotMachineAnim.addListener(AnimatorListeners.forEndCallback(() -> {
+                    mSlotMachineIcon = null;
+                    mSlotMachineIconTranslationY = 0;
+                    invalidate();
+                }));
+                slotMachineAnim.setAutoCancel(true);
                 changeIconAnim.play(slotMachineAnim);
             }
-            changeIconAnim.play(plateColorAnim);
+
             changeIconAnim.setStartDelay(staggerIndex * ICON_CHANGE_ANIM_STAGGER);
             changeIconAnim.setDuration(ICON_CHANGE_ANIM_DURATION).start();
         }
-    }
-
-    /**
-     * Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
-     * and ending with the original icon.
-     */
-    public @Nullable Animator createSlotMachineAnim(List<BitmapInfo> iconsToAnimate) {
-        return createSlotMachineAnim(iconsToAnimate, true);
-    }
-
-    /**
-     * Returns an Animator that translates the given icons in a "slot-machine" fashion, beginning
-     * with the original icon, then cycling through the given icons, optionally ending back with
-     * the original icon.
-     * @param endWithOriginalIcon Whether we should land back on the icon we started with, rather
-     *                            than the last item in iconsToAnimate.
-     */
-    public @Nullable Animator createSlotMachineAnim(List<BitmapInfo> iconsToAnimate,
-            boolean endWithOriginalIcon) {
-        if (mIsPinned || iconsToAnimate == null || iconsToAnimate.isEmpty()) {
-            return null;
-        }
-        if (mSlotMachineAnim != null) {
-            mSlotMachineAnim.end();
-        }
-
-        // Bookend the other animating icons with the original icon on both ends.
-        mSlotMachineIcons = new ArrayList<>(iconsToAnimate.size() + 2);
-        mSlotMachineIcons.add(getIcon());
-        iconsToAnimate.stream()
-                .map(iconInfo -> iconInfo.newIcon(mContext, FLAG_THEMED))
-                .forEach(mSlotMachineIcons::add);
-        if (endWithOriginalIcon) {
-            mSlotMachineIcons.add(getIcon());
-        }
-
-        float finalTrans = -getSlotMachineIconPlusSpacingSize() * (mSlotMachineIcons.size() - 1);
-        Keyframe[] keyframes = new Keyframe[] {
-                Keyframe.ofFloat(0f, 0f),
-                Keyframe.ofFloat(0.82f, finalTrans - getOutlineOffsetY() / 2f), // Overshoot
-                Keyframe.ofFloat(1f, finalTrans) // Ease back into the final position
-        };
-        keyframes[1].setInterpolator(ACCELERATE_DECELERATE);
-        keyframes[2].setInterpolator(ACCELERATE_DECELERATE);
-
-        mSlotMachineAnim = ObjectAnimator.ofPropertyValuesHolder(this,
-                PropertyValuesHolder.ofKeyframe(SLOT_MACHINE_TRANSLATION_Y, keyframes));
-        mSlotMachineAnim.addListener(AnimatorListeners.forEndCallback(() -> {
-            mSlotMachineIcons = null;
-            mSlotMachineAnim = null;
-            mSlotMachineIconTranslationY = 0;
-            invalidate();
-        }));
-        return mSlotMachineAnim;
+        return animate;
     }
 
     /**
@@ -332,7 +303,13 @@
     }
 
     private int getOutlineOffsetX() {
-        return (getMeasuredWidth() - mNormalizedIconSize) / 2;
+        int measuredWidth = getMeasuredWidth();
+        if (mDisplay != DISPLAY_TASKBAR) {
+            Log.d("b/387844520", "getOutlineOffsetX: measured width = " + measuredWidth
+                    + ", mNormalizedIconSize = " + mNormalizedIconSize
+                    + ", last updated width = " + mWidth);
+        }
+        return (mWidth - mNormalizedIconSize) / 2;
     }
 
     private int getOutlineOffsetY() {
@@ -345,6 +322,11 @@
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
+        mWidth = w;
+        mSlotIconBound.offsetTo((w - getIconSize()) / 2, (h - getIconSize()) / 2);
+        if (mDisplay != DISPLAY_TASKBAR) {
+            Log.d("b/387844520", "calling updateRingPath from onSizeChanged");
+        }
         updateRingPath();
     }
 
@@ -355,18 +337,13 @@
     }
 
     private void updateRingPath() {
-        boolean isBadged = false;
-        if (getTag() instanceof WorkspaceItemInfo) {
-            WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
-            isBadged = !Process.myUserHandle().equals(info.user)
-                    || info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
-        }
-
         mRingPath.reset();
+        mTmpMatrix.reset();
         mTmpMatrix.setTranslate(getOutlineOffsetX(), getOutlineOffsetY());
-
         mRingPath.addPath(mShapePath, mTmpMatrix);
-        if (isBadged) {
+
+        FastBitmapDrawable icon = getIcon();
+        if (icon != null && icon.getBadge() != null) {
             float outlineSize = mNormalizedIconSize * RING_EFFECT_RATIO;
             float iconSize = getIconSize() * (1 - 2 * RING_EFFECT_RATIO);
             float badgeSize = LauncherIcons.getBadgeSizeForIconSize((int) iconSize) + outlineSize;
@@ -376,6 +353,7 @@
             mTmpMatrix.preTranslate(-mNormalizedIconSize, -mNormalizedIconSize);
             mRingPath.addPath(mShapePath, mTmpMatrix);
         }
+        invalidate();
     }
 
     @Override
@@ -410,7 +388,7 @@
         mRingScaleAnim.start();
     }
 
-    private void drawEffect(Canvas canvas) {
+    private void drawRingEffect(Canvas canvas) {
         // Don't draw ring effect if item is about to be dragged or if the icon is not visible.
         if (mDrawForDrag || !mIsIconVisible || mForceHideRing) {
             return;
@@ -418,12 +396,28 @@
         mIconRingPaint.setColor(RING_SHADOW_COLOR);
         mIconRingPaint.setMaskFilter(mShadowFilter);
         int count = canvas.save();
-        if (Float.compare(1, mRingScale) != 0) {
+        if (Flags.enableLauncherIconShapes()) {
+            // Scale canvas properly to for ring to be inner stroke and not exceed bounds.
+            // Since STROKE draws half on either side of Path, scale canvas down by 1x stroke ratio.
+            canvas.scale(
+                    mRingScale * (1f - RING_EFFECT_RATIO),
+                    mRingScale * (1f - RING_EFFECT_RATIO),
+                    canvas.getWidth() / 2f,
+                    canvas.getHeight() / 2f);
+        } else if (Float.compare(1, mRingScale) != 0) {
             canvas.scale(mRingScale, mRingScale, canvas.getWidth() / 2f, canvas.getHeight() / 2f);
         }
+        // Draw ring shadow around canvas.
         canvas.drawPath(mRingPath, mIconRingPaint);
-        mIconRingPaint.setColor(mPlateColor);
+        mIconRingPaint.setColor(mPlateColor.currentColor);
+        if (Flags.enableLauncherIconShapes()) {
+            mIconRingPaint.setStrokeWidth(canvas.getWidth() * RING_EFFECT_RATIO);
+            // Using FILL_AND_STROKE as there is still some gap to fill,
+            // between inner curve of ring / outer curve of icon.
+            mIconRingPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+        }
         mIconRingPaint.setMaskFilter(null);
+        // Draw ring around canvas.
         canvas.drawPath(mRingPath, mIconRingPaint);
         canvas.restoreToCount(count);
     }
@@ -474,6 +468,21 @@
         return icon;
     }
 
+    private class AnimColorHolder {
+
+        public final AnimatedFloat progress = new AnimatedFloat(this::onUpdate, 1);
+        public final ArgbEvaluator evaluator = ArgbEvaluator.getInstance();
+        public Integer startColor = 0;
+        public Integer endColor = 0;
+
+        public int currentColor = 0;
+
+        private void onUpdate() {
+            currentColor = (Integer) evaluator.evaluate(progress.value, startColor, endColor);
+            invalidate();
+        }
+    }
+
     /**
      * Draws Predicted Icon outline on cell layout
      */
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 999b13e..cd0a4f3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -23,6 +23,7 @@
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
+import static com.android.launcher3.Flags.enableExpressiveDismissTaskMotion;
 import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
 import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
@@ -39,7 +40,6 @@
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
 import static com.android.launcher3.Utilities.isRtl;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
-import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED;
@@ -64,9 +64,6 @@
 import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
 import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
-import static com.android.wm.shell.Flags.enableBubbleAnything;
-import static com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -86,6 +83,8 @@
 import android.os.IRemoteCallback;
 import android.os.SystemProperties;
 import android.os.Trace;
+import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
@@ -118,6 +117,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
+import com.android.launcher3.allapps.AllAppsRecyclerView;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.apppairs.AppPairIcon;
@@ -150,7 +150,10 @@
 import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
-import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewDismissTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewLaunchTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewRecentsTouchContext;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchControllerDeprecated;
 import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
 import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
 import com.android.launcher3.util.ActivityOptionsWrapper;
@@ -164,23 +167,24 @@
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
+import com.android.launcher3.util.StableViewInfo;
 import com.android.launcher3.util.StartActivityParams;
 import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.FloatingIconView;
 import com.android.launcher3.widget.LauncherWidgetHolder;
 import com.android.quickstep.OverviewCommandHelper;
 import com.android.quickstep.OverviewComponentObserver;
 import com.android.quickstep.OverviewComponentObserver.OverviewChangeListener;
-import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.TouchInteractionService.TISBinder;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
 import com.android.quickstep.util.AsyncClockEventDelegate;
-import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LauncherUnfoldAnimationController;
 import com.android.quickstep.util.QuickstepOnboardingPrefs;
 import com.android.quickstep.util.SplitSelectStateController;
+import com.android.quickstep.util.SplitTask;
 import com.android.quickstep.util.SplitToWorkspaceController;
 import com.android.quickstep.util.SplitWithKeyboardShortcutController;
 import com.android.quickstep.util.TISBindHelper;
@@ -202,6 +206,7 @@
 import com.android.systemui.unfold.dagger.UnfoldMain;
 import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver;
 import com.android.systemui.unfold.updates.RotationChangeProvider;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 
@@ -266,6 +271,25 @@
 
     private final OverviewChangeListener mOverviewChangeListener = this::onOverviewTargetChanged;
 
+    private final TaskViewRecentsTouchContext mTaskViewRecentsTouchContext =
+            new TaskViewRecentsTouchContext() {
+                @Override
+                public boolean isRecentsInteractive() {
+                    return isInState(OVERVIEW) || isInState(OVERVIEW_MODAL_TASK);
+                }
+
+                @Override
+                public boolean isRecentsModal() {
+                    return isInState(OVERVIEW_MODAL_TASK);
+                }
+
+                @Override
+                public void onUserControlledAnimationCreated(
+                        AnimatorPlaybackController animController) {
+                    getStateManager().setCurrentUserControlledAnimation(animController);
+                }
+            };
+
     public static QuickstepLauncher getLauncher(Context context) {
         return fromContext(context);
     }
@@ -282,7 +306,6 @@
                         getDepthController(), getStatsLogManager(),
                         systemUiProxy, RecentsModel.INSTANCE.get(this),
                         () -> onStateBack());
-        RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(asContext());
         if (DesktopModeStatus.canEnterDesktopMode(this)) {
             mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
                     getStateManager(), systemUiProxy, getIApplicationThread(),
@@ -290,8 +313,8 @@
         }
         overviewPanel.init(mActionsView, mSplitSelectStateController,
                 mDesktopRecentsTransitionController);
-        mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(this,
-                mSplitSelectStateController, deviceState);
+        mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(
+                this, mSplitSelectStateController);
         mSplitToWorkspaceController = new SplitToWorkspaceController(this,
                 mSplitSelectStateController);
         mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
@@ -440,7 +463,7 @@
 
     protected void onItemClicked(View view) {
         if (!mSplitToWorkspaceController.handleSecondAppSelectionForSplit(view)) {
-            QuickstepLauncher.super.getItemOnClickListener().onClick(view);
+            super.getItemOnClickListener().onClick(view);
         }
     }
 
@@ -454,6 +477,7 @@
         // Order matters as it affects order of appearance in popup container
         List<SystemShortcut.Factory> shortcuts = new ArrayList(Arrays.asList(
                 APP_INFO, WellbeingModel.SHORTCUT_FACTORY, mHotseatPredictionController));
+
         shortcuts.addAll(getSplitShortcuts());
         shortcuts.add(WIDGETS);
         shortcuts.add(INSTALL);
@@ -466,7 +490,7 @@
         if (Flags.enablePrivateSpace()) {
             shortcuts.add(UNINSTALL_APP);
         }
-        if (enableBubbleAnything()) {
+        if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
             shortcuts.add(BUBBLE_SHORTCUT);
         }
         return shortcuts.stream();
@@ -560,9 +584,13 @@
             mSplitSelectStateController.onDestroy();
         }
 
+        RecentsView recentsView = getOverviewPanel();
+        if (recentsView != null) {
+            recentsView.destroy();
+        }
+
         super.onDestroy();
         mHotseatPredictionController.destroy();
-        mSplitWithKeyboardShortcutController.onDestroy();
         if (mViewCapture != null) mViewCapture.close();
         removeBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler());
     }
@@ -595,7 +623,7 @@
             case QUICK_SWITCH_STATE_ORDINAL: {
                 RecentsView rv = getOverviewPanel();
                 TaskView currentPageTask = rv.getCurrentPageTaskView();
-                TaskView fallbackTask = rv.getTaskViewAt(0);
+                TaskView fallbackTask = rv.getFirstTaskView();
                 if (currentPageTask != null || fallbackTask != null) {
                     TaskView taskToLaunch = currentPageTask;
                     if (currentPageTask == null) {
@@ -659,7 +687,12 @@
             list.add(new StatusBarTouchController(this));
         }
 
-        list.add(new LauncherTaskViewController(this));
+        if (enableExpressiveDismissTaskMotion()) {
+            list.add(new TaskViewLaunchTouchController<>(this, mTaskViewRecentsTouchContext));
+            list.add(new TaskViewDismissTouchController<>(this, mTaskViewRecentsTouchContext));
+        } else {
+            list.add(new TaskViewTouchControllerDeprecated<>(this, mTaskViewRecentsTouchContext));
+        }
         return list.toArray(new TouchController[list.size()]);
     }
 
@@ -700,6 +733,9 @@
         final boolean ret = super.initDeviceProfile(idp);
         mDeviceProfile.isPredictiveBackSwipe =
                 getApplicationInfo().isOnBackInvokedCallbackEnabled();
+        if (ret) {
+            SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
+        }
         return ret;
     }
 
@@ -717,11 +753,7 @@
                     splitSelectSource.alreadyRunningTaskId = taskWasFound
                             ? foundTask.key.id
                             : INVALID_TASK_ID;
-                    if (enableSplitContextually()) {
-                        startSplitToHome(splitSelectSource);
-                    } else {
-                        recentsView.initiateSplitSelect(splitSelectSource);
-                    }
+                    startSplitToHome(splitSelectSource);
                 }
         );
     }
@@ -811,15 +843,13 @@
 
         super.onPause();
 
-        if (enableSplitContextually()) {
-            // If Launcher pauses before both split apps are selected, exit split screen.
-            if (!mSplitSelectStateController.isBothSplitAppsConfirmed() &&
-                    !mSplitSelectStateController.isLaunchingFirstAppFullscreen()) {
-                mSplitSelectStateController
-                        .logExitReason(LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
-                mSplitSelectStateController.getSplitAnimationController()
-                        .playPlaceholderDismissAnim(this, LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
-            }
+        // If Launcher pauses before both split apps are selected, exit split screen.
+        if (!mSplitSelectStateController.isBothSplitAppsConfirmed() &&
+                !mSplitSelectStateController.isLaunchingFirstAppFullscreen()) {
+            mSplitSelectStateController
+                    .logExitReason(LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
+            mSplitSelectStateController.getSplitAnimationController()
+                    .playPlaceholderDismissAnim(this, LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
         }
 
         if (mTaskbarUIController != null && FeatureFlags.enableHomeTransitionListener()) {
@@ -1014,10 +1044,10 @@
 
     @Override
     public void setResumed() {
-        DesktopVisibilityController desktopVisibilityController = getDesktopVisibilityController();
+        DesktopVisibilityController desktopVisibilityController =
+                DesktopVisibilityController.INSTANCE.get(this);
         if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
-                && desktopVisibilityController != null
-                && desktopVisibilityController.areDesktopTasksVisible()
+                && desktopVisibilityController.isInDesktopModeAndNotInOverview(getDisplayId())
                 && !desktopVisibilityController.isRecentsGestureInProgress()) {
             // Return early to skip setting activity to appear as resumed
             // TODO: b/333533253 - Remove after flag rollout
@@ -1129,17 +1159,15 @@
     /** Provides the translation X for the hotseat item. */
     public int getHotseatItemTranslationX(ItemInfo itemInfo) {
         int translationX = 0;
-        if (isBubbleBarEnabled()
-                && enableBubbleBarInPersistentTaskBar()
-                && mBubbleBarLocation != null) {
+        if (isBubbleBarEnabled() && mBubbleBarLocation != null) {
             boolean isBubblesOnLeft = mBubbleBarLocation.isOnLeft(isRtl(getResources()));
             translationX += mDeviceProfile
                     .getHotseatTranslationXForNavBar(this, isBubblesOnLeft);
         }
         if (isBubbleBarEnabled()
-                && mDeviceProfile.shouldAdjustHotseatForBubbleBar(getContext(), hasBubbles())) {
+                && mDeviceProfile.shouldAdjustHotseatForBubbleBar(asContext(), hasBubbles())) {
             translationX += (int) mDeviceProfile
-                    .getHotseatAdjustedTranslation(getContext(), itemInfo.cellX);
+                    .getHotseatAdjustedTranslation(asContext(), itemInfo.cellX);
         }
         return translationX;
     }
@@ -1185,12 +1213,6 @@
     }
 
     @Nullable
-    @Override
-    public DesktopVisibilityController getDesktopVisibilityController() {
-        return mTISBindHelper.getDesktopVisibilityController();
-    }
-
-    @Nullable
     public UnfoldTransitionProgressProvider getUnfoldTransitionProgressProvider() {
         return mUnfoldTransitionProgressProvider;
     }
@@ -1220,6 +1242,7 @@
         }
     }
 
+    @NonNull
     @Override
     public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
         ActivityOptionsWrapper activityOptions = mAppTransitionManager.getActivityLaunchOptions(
@@ -1251,7 +1274,7 @@
         options.setPendingIntentBackgroundActivityStartMode(
                 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
 
-        IRemoteCallback endCallback = completeRunnableListCallback(callbacks);
+        IRemoteCallback endCallback = completeRunnableListCallback(callbacks, this);
         options.setOnAnimationAbortListener(endCallback);
         options.setOnAnimationFinishedListener(endCallback);
         return new ActivityOptionsWrapper(options, callbacks);
@@ -1345,17 +1368,8 @@
 
     @Override
     public boolean areDesktopTasksVisible() {
-        DesktopVisibilityController desktopVisibilityController = getDesktopVisibilityController();
-        if (desktopVisibilityController != null) {
-            return desktopVisibilityController.areDesktopTasksVisible();
-        }
-        return false;
-    }
-
-    @Override
-    protected void onDeviceProfileInitiated() {
-        super.onDeviceProfileInitiated();
-        SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
+        return DesktopVisibilityController.INSTANCE.get(this)
+                .isInDesktopModeAndNotInOverview(getDisplayId());
     }
 
     @Override
@@ -1366,38 +1380,26 @@
         SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
         TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
         if (taskbarManager != null) {
-            taskbarManager.debugWhyTaskbarNotDestroyed("QuickstepLauncher#onDeviceProfileChanged");
+            taskbarManager.debugPrimaryTaskbar("QuickstepLauncher#onDeviceProfileChanged",
+                    true);
         }
     }
 
     /**
-     * Launches the given {@link GroupTask} in splitscreen.
+     * Launches the given {@link SplitTask} in splitscreen.
      */
     public void launchSplitTasks(
-            @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
-        // SplitBounds can be null if coming from Taskbar launch.
-        final boolean firstTaskIsLeftTopTask = isFirstTaskLeftTopTask(groupTask);
-        // task2 should never be null when calling this method. Allow a crash to catch invalid calls
-        Task task1 = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
-        Task task2 = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
-        mSplitSelectStateController.launchExistingSplitPair(
-                null /* launchingTaskView */,
-                task1.key.id,
-                task2.key.id,
+            @NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) {
+        mSplitSelectStateController.launchExistingSplitPair(null /* launchingTaskView */,
+                splitTask.getTopLeftTask().key.id,
+                splitTask.getBottomRightTask().key.id,
                 SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
                 /* callback= */ success -> mSplitSelectStateController.resetState(),
                 /* freezeTaskList= */ false,
-                groupTask.mSplitBounds == null
-                        ? SNAP_TO_2_50_50
-                        : groupTask.mSplitBounds.snapPosition,
+                splitTask.getSplitBounds().snapPosition,
                 remoteTransition);
     }
 
-    private static boolean isFirstTaskLeftTopTask(@NonNull GroupTask groupTask) {
-        return groupTask.mSplitBounds == null
-                || groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
-    }
-
     /**
      * Launches two apps as an app pair.
      */
@@ -1429,7 +1431,7 @@
 
     @Override
     public boolean handleIncorrectSplitTargetSelection() {
-        if (!enableSplitContextually() || !mSplitSelectStateController.isSplitSelectActive()) {
+        if (!mSplitSelectStateController.isSplitSelectActive()) {
             return false;
         }
         mSplitSelectStateController.getSplitInstructionsView().goBoing();
@@ -1443,9 +1445,9 @@
     }
 
     @Override
-    public void showAppBubble(Intent intent) {
+    public void showAppBubble(Intent intent, UserHandle user) {
         if (intent == null || intent.getPackage() == null) return;
-        SystemUiProxy.INSTANCE.get(this).showAppBubble(intent);
+        SystemUiProxy.INSTANCE.get(this).showAppBubble(intent, user);
     }
 
     /** Sets the location of the bubble bar */
@@ -1453,27 +1455,43 @@
         mBubbleBarLocation = bubbleBarLocation;
     }
 
-    private static final class LauncherTaskViewController extends
-            TaskViewTouchController<QuickstepLauncher> {
+    /**
+     * Similar to {@link #getFirstHomeElementForAppClose} but also matches all apps if its visible
+     */
+    @Nullable
+    public View getFirstVisibleElementForAppClose(
+            @Nullable StableViewInfo svi, String packageName, UserHandle user) {
+        if (isInState(LauncherState.ALL_APPS)) {
+            AllAppsRecyclerView activeRecyclerView = getAppsView().getActiveRecyclerView();
+            View v = null;
+            if (svi != null) {
+                // Preferred item match
+                v = activeRecyclerView.findViewByPredicate(view ->
+                        view.isAggregatedVisible()
+                                && view.getTag() instanceof ItemInfo info && svi.matches(info));
+            }
+            if (v == null) {
+                // Package user match
+                v = activeRecyclerView.findViewByPredicate(view ->
+                        view.isAggregatedVisible() && view.getTag() instanceof ItemInfo info
+                                && info.itemType == ITEM_TYPE_APPLICATION
+                                && info.user.equals(user)
+                                && TextUtils.equals(info.getTargetPackage(), packageName));
+            }
 
-        LauncherTaskViewController(QuickstepLauncher activity) {
-            super(activity);
+            if (v != null && activeRecyclerView.computeVerticalScrollOffset() > 0) {
+                RectF locationBounds = new RectF();
+                FloatingIconView.getLocationBoundsForView(this, v, false, locationBounds,
+                        new Rect());
+                if (locationBounds.top < getAppsView().getHeaderBottom()) {
+                    // Icon is covered by scrim, return null to play fallback animation.
+                    return null;
+                }
+            }
+            return v;
         }
 
-        @Override
-        protected boolean isRecentsInteractive() {
-            return mContainer.isInState(OVERVIEW) || mContainer.isInState(OVERVIEW_MODAL_TASK);
-        }
-
-        @Override
-        protected boolean isRecentsModal() {
-            return mContainer.isInState(OVERVIEW_MODAL_TASK);
-        }
-
-        @Override
-        protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
-            mContainer.getStateManager().setCurrentUserControlledAnimation(animController);
-        }
+        return getFirstHomeElementForAppClose(svi, packageName, user);
     }
 
     @Override
@@ -1530,4 +1548,9 @@
     public void setCanShowAllAppsEducationView(boolean canShowAllAppsEducationView) {
         mCanShowAllAppsEducationView = canShowAllAppsEducationView;
     }
+
+    @Override
+    public void returnToHomescreen() {
+        getStateManager().goToState(LauncherState.NORMAL);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
deleted file mode 100644
index 111069f..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.uioverrides;
-
-import static com.android.app.animation.Interpolators.LINEAR;
-import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
-import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
-import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
-import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
-import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
-import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
-import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
-import static com.android.quickstep.views.RecentsView.TASK_PRIMARY_SPLIT_TRANSLATION;
-import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION;
-import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
-import static com.android.wm.shell.Flags.enableSplitContextual;
-
-import android.animation.AnimatorSet;
-import android.annotation.TargetApi;
-import android.os.Build;
-import android.util.FloatProperty;
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimatedFloat;
-import com.android.launcher3.anim.AnimatorListeners;
-import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.states.StateAnimationConfig;
-import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
-import com.android.quickstep.util.AnimUtils;
-import com.android.quickstep.util.SplitAnimationTimings;
-import com.android.quickstep.views.ClearAllButton;
-import com.android.quickstep.views.LauncherRecentsView;
-import com.android.quickstep.views.RecentsView;
-
-/**
- * State handler for handling UI changes for {@link LauncherRecentsView}. In addition to managing
- * the basic view properties, this class also manages changes in the task visuals.
- */
-@TargetApi(Build.VERSION_CODES.O)
-public final class RecentsViewStateController extends
-        BaseRecentsViewStateController<LauncherRecentsView> {
-
-    public RecentsViewStateController(QuickstepLauncher launcher) {
-        super(launcher);
-    }
-
-    @Override
-    public void setState(@NonNull LauncherState state) {
-        super.setState(state);
-        if (state.isRecentsViewVisible) {
-            mRecentsView.updateEmptyMessage();
-        } else {
-            mRecentsView.resetTaskVisuals();
-        }
-        setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, new StateAnimationConfig(), state);
-        mRecentsView.setFullscreenProgress(state.getOverviewFullscreenProgress());
-        // In Overview, we may be layering app surfaces behind Launcher, so we need to notify
-        // DepthController to prevent optimizations which might occlude the layers behind
-        mLauncher.getDepthController().setHasContentBehindLauncher(state.isRecentsViewVisible);
-
-        PendingAnimation builder =
-                new PendingAnimation(state.getTransitionDuration(mLauncher, true));
-
-        handleSplitSelectionState(state, builder, /* animate */false);
-    }
-
-    @Override
-    void setStateWithAnimationInternal(@NonNull LauncherState toState,
-            @NonNull StateAnimationConfig config, @NonNull PendingAnimation builder) {
-        super.setStateWithAnimationInternal(toState, config, builder);
-
-        if (toState.isRecentsViewVisible) {
-            // While animating into recents, update the visible task data as needed
-            builder.addOnFrameCallback(() -> mRecentsView.loadVisibleTaskData(FLAG_UPDATE_ALL));
-            mRecentsView.updateEmptyMessage();
-            // TODO(b/246283207): Remove logging once root cause of flake detected.
-            if (Utilities.isRunningInTestHarness()) {
-                Log.d("b/246283207", "RecentsView#setStateWithAnimationInternal getCurrentPage(): "
-                                + mRecentsView.getCurrentPage()
-                                + ", getScrollForPage(getCurrentPage())): "
-                                + mRecentsView.getScrollForPage(mRecentsView.getCurrentPage()));
-            }
-        } else {
-            builder.addListener(
-                    AnimatorListeners.forSuccessCallback(mRecentsView::resetTaskVisuals));
-        }
-        // In Overview, we may be layering app surfaces behind Launcher, so we need to notify
-        // DepthController to prevent optimizations which might occlude the layers behind
-        builder.addListener(AnimatorListeners.forSuccessCallback(() ->
-                mLauncher.getDepthController().setHasContentBehindLauncher(
-                        toState.isRecentsViewVisible)));
-
-        handleSplitSelectionState(toState, builder, /* animate */true);
-
-        setAlphas(builder, config, toState);
-        builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
-                toState.getOverviewFullscreenProgress(), LINEAR);
-    }
-
-    /**
-     * Create or dismiss split screen select animations.
-     * @param builder if null then this will run the split select animations right away, otherwise
-     *                will add animations to builder.
-     */
-    private void handleSplitSelectionState(@NonNull LauncherState toState,
-            @NonNull PendingAnimation builder, boolean animate) {
-        boolean goingToOverviewFromWorkspaceContextual = enableSplitContextual() &&
-                toState == OVERVIEW && mLauncher.isSplitSelectionActive();
-        if (toState != OVERVIEW_SPLIT_SELECT && !goingToOverviewFromWorkspaceContextual) {
-            // Not going to split
-            return;
-        }
-
-        // Create transition animations to split select
-        RecentsPagedOrientationHandler orientationHandler =
-                ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler();
-        Pair<FloatProperty<RecentsView>, FloatProperty<RecentsView>> taskViewsFloat =
-                orientationHandler.getSplitSelectTaskOffset(
-                        TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
-                        mLauncher.getDeviceProfile());
-
-        SplitAnimationTimings timings =
-                AnimUtils.getDeviceOverviewToSplitTimings(mLauncher.getDeviceProfile().isTablet);
-        if (!goingToOverviewFromWorkspaceContextual) {
-            // This animation is already done for the contextual case, don't redo it
-            mRecentsView.createSplitSelectInitAnimation(builder,
-                    toState.getTransitionDuration(mLauncher, true /* isToState */));
-        }
-        // Shift tasks vertically downward to get out of placeholder view
-        builder.setFloat(mRecentsView, taskViewsFloat.first,
-                toState.getSplitSelectTranslation(mLauncher),
-                timings.getGridSlidePrimaryInterpolator());
-        // Zero out horizontal translation
-        builder.setFloat(mRecentsView, taskViewsFloat.second,
-                0,
-                timings.getGridSlideSecondaryInterpolator());
-
-        mRecentsView.handleDesktopTaskInSplitSelectState(builder,
-                timings.getDesktopTaskFadeInterpolator());
-
-        if (!animate) {
-            AnimatorSet as = builder.buildAnim();
-            as.start();
-            as.end();
-        }
-    }
-
-    private void setAlphas(PropertySetter propertySetter, StateAnimationConfig config,
-            LauncherState state) {
-        float clearAllButtonAlpha = state.areElementsVisible(mLauncher, CLEAR_ALL_BUTTON) ? 1 : 0;
-        propertySetter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
-                clearAllButtonAlpha, LINEAR);
-        float overviewButtonAlpha = state.areElementsVisible(mLauncher, OVERVIEW_ACTIONS) ? 1 : 0;
-        propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
-                AnimatedFloat.VALUE, overviewButtonAlpha, config.getInterpolator(
-                        ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
-    }
-
-    @Override
-    FloatProperty<RecentsView> getTaskModalnessProperty() {
-        return TASK_MODALNESS;
-    }
-
-    @Override
-    FloatProperty<RecentsView> getContentAlphaProperty() {
-        return CONTENT_ALPHA;
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
new file mode 100644
index 0000000..23dc81d
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.kt
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides
+
+import com.android.app.animation.Interpolators.ACCELERATE_DECELERATE
+import com.android.app.animation.Interpolators.AGGRESSIVE_EASE_IN_OUT
+import com.android.app.animation.Interpolators.FINAL_FRAME
+import com.android.app.animation.Interpolators.INSTANT
+import com.android.app.animation.Interpolators.LINEAR
+import com.android.launcher3.Flags.enableDesktopExplodedView
+import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
+import com.android.launcher3.LauncherState
+import com.android.launcher3.anim.AnimatedFloat
+import com.android.launcher3.anim.AnimatorListeners.forSuccessCallback
+import com.android.launcher3.anim.PendingAnimation
+import com.android.launcher3.anim.PropertySetter
+import com.android.launcher3.statemanager.StateManager.StateHandler
+import com.android.launcher3.states.StateAnimationConfig
+import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE
+import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE
+import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL
+import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE
+import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X
+import com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y
+import com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW
+import com.android.quickstep.util.AnimUtils
+import com.android.quickstep.views.AddDesktopButton
+import com.android.quickstep.views.ClearAllButton
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET
+import com.android.quickstep.views.RecentsView.CONTENT_ALPHA
+import com.android.quickstep.views.RecentsView.DESKTOP_CAROUSEL_DETACH_PROGRESS
+import com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS
+import com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS
+import com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY
+import com.android.quickstep.views.RecentsView.TASK_MODALNESS
+import com.android.quickstep.views.RecentsView.TASK_PRIMARY_SPLIT_TRANSLATION
+import com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION
+import com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION
+import com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA
+import com.android.quickstep.views.RecentsViewUtils.Companion.DESK_EXPLODE_PROGRESS
+import com.android.quickstep.views.TaskView.Companion.FLAG_UPDATE_ALL
+
+/**
+ * State handler for handling UI changes for [com.android.quickstep.views.LauncherRecentsView]. In
+ * addition to managing the basic view properties, this class also manages changes in the task
+ * visuals.
+ */
+class RecentsViewStateController(private val launcher: QuickstepLauncher) :
+    StateHandler<LauncherState> {
+    private val recentsView: RecentsView<*, *> = launcher.getOverviewPanel()
+
+    override fun setState(state: LauncherState) {
+        val scaleAndOffset = state.getOverviewScaleAndOffset(launcher)
+        RECENTS_SCALE_PROPERTY.set(recentsView, scaleAndOffset[0])
+        ADJACENT_PAGE_HORIZONTAL_OFFSET.set(recentsView, scaleAndOffset[1])
+        TASK_SECONDARY_TRANSLATION.set(recentsView, 0f)
+
+        CONTENT_ALPHA.set(recentsView, if (state.isRecentsViewVisible) 1f else 0f)
+        TASK_MODALNESS.set(recentsView, state.overviewModalness)
+        RECENTS_GRID_PROGRESS.set(
+            recentsView,
+            if (state.displayOverviewTasksAsGrid(launcher.deviceProfile)) 1f else 0f,
+        )
+        if (enableDesktopExplodedView()) {
+            DESK_EXPLODE_PROGRESS.set(recentsView, if (state.showExplodedDesktopView()) 1f else 0f)
+        }
+
+        TASK_THUMBNAIL_SPLASH_ALPHA.set(
+            recentsView,
+            if (state.showTaskThumbnailSplash()) 1f else 0f,
+        )
+        if (enableLargeDesktopWindowingTile()) {
+            DESKTOP_CAROUSEL_DETACH_PROGRESS.set(
+                recentsView,
+                if (state.detachDesktopCarousel()) 1f else 0f,
+            )
+        }
+
+        if (state.isRecentsViewVisible) {
+            recentsView.updateEmptyMessage()
+        } else {
+            recentsView.resetTaskVisuals()
+        }
+        setAlphas(PropertySetter.NO_ANIM_PROPERTY_SETTER, StateAnimationConfig(), state)
+        recentsView.setFullscreenProgress(state.overviewFullscreenProgress)
+        // In Overview, we may be layering app surfaces behind Launcher, so we need to notify
+        // DepthController to prevent optimizations which might occlude the layers behind
+        launcher.depthController.setHasContentBehindLauncher(state.isRecentsViewVisible)
+
+        val builder = PendingAnimation(state.getTransitionDuration(launcher, true).toLong())
+        handleSplitSelectionState(state, builder, animate = false)
+    }
+
+    override fun setStateWithAnimation(
+        toState: LauncherState,
+        config: StateAnimationConfig,
+        builder: PendingAnimation,
+    ) {
+        if (config.hasAnimationFlag(SKIP_OVERVIEW)) return
+
+        val scaleAndOffset = toState.getOverviewScaleAndOffset(launcher)
+        builder.setFloat(
+            recentsView,
+            RECENTS_SCALE_PROPERTY,
+            scaleAndOffset[0],
+            config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR),
+        )
+        builder.setFloat(
+            recentsView,
+            ADJACENT_PAGE_HORIZONTAL_OFFSET,
+            scaleAndOffset[1],
+            config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR),
+        )
+        builder.setFloat(
+            recentsView,
+            TASK_SECONDARY_TRANSLATION,
+            0f,
+            config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR),
+        )
+
+        builder.setFloat(
+            recentsView,
+            CONTENT_ALPHA,
+            if (toState.isRecentsViewVisible) 1f else 0f,
+            config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT),
+        )
+
+        builder.setFloat(
+            recentsView,
+            TASK_MODALNESS,
+            toState.overviewModalness,
+            config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR),
+        )
+
+        val fromState = launcher.stateManager.state
+        builder.setFloat(
+            recentsView,
+            TASK_THUMBNAIL_SPLASH_ALPHA,
+            if (toState.showTaskThumbnailSplash()) 1f else 0f,
+            getOverviewInterpolator(fromState, toState),
+        )
+
+        builder.setFloat(
+            recentsView,
+            RECENTS_GRID_PROGRESS,
+            if (toState.displayOverviewTasksAsGrid(launcher.deviceProfile)) 1f else 0f,
+            getOverviewInterpolator(fromState, toState),
+        )
+
+        if (enableDesktopExplodedView()) {
+            builder.setFloat(
+                recentsView,
+                DESK_EXPLODE_PROGRESS,
+                if (toState.showExplodedDesktopView()) 1f else 0f,
+                getOverviewInterpolator(fromState, toState),
+            )
+        }
+
+        if (enableLargeDesktopWindowingTile()) {
+            builder.setFloat(
+                recentsView,
+                DESKTOP_CAROUSEL_DETACH_PROGRESS,
+                if (toState.detachDesktopCarousel()) 1f else 0f,
+                getOverviewInterpolator(fromState, toState),
+            )
+        }
+
+        if (toState.isRecentsViewVisible) {
+            // While animating into recents, update the visible task data as needed
+            builder.addOnFrameCallback { recentsView.loadVisibleTaskData(FLAG_UPDATE_ALL) }
+            recentsView.updateEmptyMessage()
+        } else {
+            builder.addListener(forSuccessCallback { recentsView.resetTaskVisuals() })
+        }
+        // In Overview, we may be layering app surfaces behind Launcher, so we need to notify
+        // DepthController to prevent optimizations which might occlude the layers behind
+        builder.addListener(
+            forSuccessCallback {
+                launcher.depthController.setHasContentBehindLauncher(toState.isRecentsViewVisible)
+            }
+        )
+
+        handleSplitSelectionState(toState, builder, animate = true)
+
+        setAlphas(builder, config, toState)
+        builder.setFloat(
+            recentsView,
+            FULLSCREEN_PROGRESS,
+            toState.overviewFullscreenProgress,
+            LINEAR,
+        )
+
+        builder.addEndListener { success: Boolean ->
+            if (!success && !toState.isRecentsViewVisible) {
+                recentsView.reset()
+            }
+        }
+    }
+
+    /**
+     * Create or dismiss split screen select animations.
+     *
+     * @param builder if null then this will run the split select animations right away, otherwise
+     *   will add animations to builder.
+     */
+    private fun handleSplitSelectionState(
+        toState: LauncherState,
+        builder: PendingAnimation,
+        animate: Boolean,
+    ) {
+        val goingToOverviewFromWorkspaceContextual =
+            toState == LauncherState.OVERVIEW && launcher.isSplitSelectionActive
+        if (
+            toState != LauncherState.OVERVIEW_SPLIT_SELECT &&
+                !goingToOverviewFromWorkspaceContextual
+        ) {
+            // Not going to split
+            return
+        }
+
+        // Create transition animations to split select
+        val orientationHandler = recentsView.pagedOrientationHandler
+        val taskViewsFloat =
+            orientationHandler.getSplitSelectTaskOffset(
+                TASK_PRIMARY_SPLIT_TRANSLATION,
+                TASK_SECONDARY_SPLIT_TRANSLATION,
+                launcher.deviceProfile,
+            )
+
+        val timings = AnimUtils.getDeviceOverviewToSplitTimings(launcher.deviceProfile.isTablet)
+        if (!goingToOverviewFromWorkspaceContextual) {
+            // This animation is already done for the contextual case, don't redo it
+            recentsView.createSplitSelectInitAnimation(
+                builder,
+                toState.getTransitionDuration(launcher, true),
+            )
+        }
+        // Shift tasks vertically downward to get out of placeholder view
+        builder.setFloat(
+            recentsView,
+            taskViewsFloat.first,
+            toState.getSplitSelectTranslation(launcher),
+            timings.gridSlidePrimaryInterpolator,
+        )
+        // Zero out horizontal translation
+        builder.setFloat(
+            recentsView,
+            taskViewsFloat.second,
+            0f,
+            timings.gridSlideSecondaryInterpolator,
+        )
+
+        recentsView.handleDesktopTaskInSplitSelectState(
+            builder,
+            timings.desktopTaskFadeInterpolator,
+        )
+
+        if (!animate) {
+            builder.buildAnim().apply {
+                start()
+                end()
+            }
+        }
+    }
+
+    private fun setAlphas(
+        propertySetter: PropertySetter,
+        config: StateAnimationConfig,
+        state: LauncherState,
+    ) {
+        val clearAllButtonAlpha =
+            if (state.areElementsVisible(launcher, LauncherState.CLEAR_ALL_BUTTON)) 1f else 0f
+        propertySetter.setFloat(
+            recentsView.clearAllButton,
+            ClearAllButton.VISIBILITY_ALPHA,
+            clearAllButtonAlpha,
+            LINEAR,
+        )
+        val overviewButtonAlpha =
+            if (state.areElementsVisible(launcher, LauncherState.OVERVIEW_ACTIONS)) 1f else 0f
+        propertySetter.setFloat(
+            launcher.actionsView.visibilityAlpha,
+            AnimatedFloat.VALUE,
+            overviewButtonAlpha,
+            config.getInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, LINEAR),
+        )
+        recentsView.addDeskButton?.let {
+            propertySetter.setFloat(
+                it,
+                AddDesktopButton.VISIBILITY_ALPHA,
+                if (state.areElementsVisible(launcher, LauncherState.ADD_DESK_BUTTON)) 1f else 0f,
+                LINEAR,
+            )
+        }
+    }
+
+    private fun getOverviewInterpolator(fromState: LauncherState, toState: LauncherState) =
+        when {
+            fromState == LauncherState.QUICK_SWITCH_FROM_HOME -> ACCELERATE_DECELERATE
+            toState.isRecentsViewVisible -> INSTANT
+            else -> FINAL_FRAME
+        }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
index 2e2d7cc..1f34969 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/SystemApiWrapper.kt
@@ -144,6 +144,14 @@
     override fun isNonResizeableActivity(lai: LauncherActivityInfo) =
         lai.activityInfo.resizeMode == ActivityInfo.RESIZE_MODE_UNRESIZEABLE
 
+    override fun supportsMultiInstance(lai: LauncherActivityInfo): Boolean {
+        return try {
+            super.supportsMultiInstance(lai) || lai.supportsMultiInstance()
+        } catch (e: Exception) {
+            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
@@ -192,4 +200,9 @@
 
     override fun getApplicationInfoHash(appInfo: ApplicationInfo): String =
         (appInfo.sourceDir?.hashCode() ?: 0).toString() + " " + appInfo.longVersionCode
+
+    override fun getRoundIconRes(appInfo: ApplicationInfo) = appInfo.roundIconRes
+
+    override fun isFileDrawable(shortcutInfo: ShortcutInfo) =
+        shortcutInfo.hasIconFile() || shortcutInfo.hasIconUri()
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt b/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
index 417bb74..0d2cfbf 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DevOptionsUiHelper.kt
@@ -17,8 +17,6 @@
 package com.android.launcher3.uioverrides.flags
 
 import android.app.PendingIntent
-import android.app.blob.BlobHandle.createWithSha256
-import android.app.blob.BlobStoreManager
 import android.content.Context
 import android.content.IIntentReceiver
 import android.content.IIntentSender.Stub
@@ -29,10 +27,8 @@
 import android.net.Uri
 import android.os.Bundle
 import android.os.IBinder
-import android.os.ParcelFileDescriptor.AutoCloseOutputStream
 import android.provider.DeviceConfig
 import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
-import android.provider.Settings.Secure
 import android.text.Html
 import android.util.AttributeSet
 import android.view.inputmethod.EditorInfo
@@ -44,33 +40,16 @@
 import androidx.preference.PreferenceGroup
 import androidx.preference.PreferenceViewHolder
 import androidx.preference.SwitchPreference
-import com.android.launcher3.AutoInstallsLayout
 import com.android.launcher3.ExtendedEditText
 import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherPrefs
-import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
-import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
-import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
-import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
-import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
-import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
-import com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL
-import com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_TAG
-import com.android.launcher3.LauncherSettings.Settings.LAYOUT_PROVIDER_KEY
-import com.android.launcher3.LauncherSettings.Settings.createBlobProviderKey
 import com.android.launcher3.R
-import com.android.launcher3.model.data.FolderInfo
-import com.android.launcher3.model.data.ItemInfo
-import com.android.launcher3.model.data.LauncherAppWidgetInfo
-import com.android.launcher3.pm.UserCache
 import com.android.launcher3.proxy.ProxyActivityStarter
 import com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher
-import com.android.launcher3.shortcuts.ShortcutKey
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl
 import com.android.launcher3.util.Executors.MAIN_EXECUTOR
-import com.android.launcher3.util.Executors.MODEL_EXECUTOR
 import com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR
-import com.android.launcher3.util.LauncherLayoutBuilder
+import com.android.launcher3.util.LayoutImportExportHelper
 import com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT
 import com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_COUNT
 import com.android.launcher3.util.OnboardingPrefs.HOME_BOUNCE_SEEN
@@ -80,14 +59,12 @@
 import com.android.launcher3.util.OnboardingPrefs.TASKBAR_SEARCH_EDU_SEEN
 import com.android.launcher3.util.PluginManagerWrapper
 import com.android.launcher3.util.StartActivityParams
-import com.android.launcher3.util.UserIconInfo
 import com.android.quickstep.util.DeviceConfigHelper
 import com.android.quickstep.util.DeviceConfigHelper.Companion.NAMESPACE_LAUNCHER
 import com.android.quickstep.util.DeviceConfigHelper.DebugInfo
 import com.android.systemui.shared.plugins.PluginEnabler
 import com.android.systemui.shared.plugins.PluginPrefs
-import java.io.OutputStreamWriter
-import java.security.MessageDigest
+import java.nio.charset.StandardCharsets
 import java.util.Locale
 import java.util.concurrent.Executor
 
@@ -340,6 +317,12 @@
             )
             addPreference(
                 Preference(context).apply {
+                    title = "Launch Full Gesture Tutorial"
+                    intent = Intent(launchSandboxIntent).putExtra("use_tutorial_menu", false)
+                }
+            )
+            addPreference(
+                Preference(context).apply {
                     title = "Launch Back Tutorial"
                     intent =
                         Intent(launchSandboxIntent)
@@ -421,26 +404,12 @@
             title = "Export"
             intent =
                 createUriPickerIntent(ACTION_CREATE_DOCUMENT, MAIN_EXECUTOR) { uri ->
-                    model.enqueueModelUpdateTask { _, dataModel, _ ->
-                        val builder = LauncherLayoutBuilder()
-                        dataModel.workspaceItems.forEach { info ->
-                            val loc =
-                                when (info.container) {
-                                    CONTAINER_DESKTOP ->
-                                        builder.atWorkspace(info.cellX, info.cellY, info.screenId)
-                                    CONTAINER_HOTSEAT -> builder.atHotseat(info.screenId)
-                                    else -> return@forEach
-                                }
-                            loc.addItem(info)
-                        }
-                        dataModel.appWidgets.forEach { info ->
-                            builder.atWorkspace(info.cellX, info.cellY, info.screenId).addItem(info)
-                        }
-
+                    LayoutImportExportHelper.exportModelDbAsXml(context) { layoutXml ->
                         context.contentResolver.openOutputStream(uri).use { os ->
-                            builder.build(OutputStreamWriter(os))
+                            val bytes: ByteArray =
+                                layoutXml.toByteArray(StandardCharsets.UTF_8) // Encode to UTF-8
+                            os?.write(bytes)
                         }
-
                         MAIN_EXECUTOR.execute {
                             Toast.makeText(context, "File saved", Toast.LENGTH_LONG).show()
                         }
@@ -458,66 +427,12 @@
                         resolver.openInputStream(uri).use { stream ->
                             stream?.readAllBytes() ?: return@createUriPickerIntent
                         }
-
-                    val digest = MessageDigest.getInstance("SHA-256").digest(data)
-                    val handle = createWithSha256(digest, LAYOUT_DIGEST_LABEL, 0, LAYOUT_DIGEST_TAG)
-                    val blobManager = context.getSystemService(BlobStoreManager::class.java)!!
-
-                    blobManager.openSession(blobManager.createSession(handle)).use { session ->
-                        AutoCloseOutputStream(session.openWrite(0, -1)).use { it.write(data) }
-                        session.allowPublicAccess()
-
-                        session.commit(ORDERED_BG_EXECUTOR) {
-                            Secure.putString(
-                                resolver,
-                                LAYOUT_PROVIDER_KEY,
-                                createBlobProviderKey(digest),
-                            )
-
-                            MODEL_EXECUTOR.submit { model.modelDbController.createEmptyDB() }.get()
-                            MAIN_EXECUTOR.submit { model.forceReload() }.get()
-                            MODEL_EXECUTOR.submit {}.get()
-                            Secure.putString(resolver, LAYOUT_PROVIDER_KEY, null)
-                        }
-                    }
+                    LayoutImportExportHelper.importModelFromXml(context, data)
                 }
             category.addPreference(this)
         }
     }
 
-    private fun LauncherLayoutBuilder.ItemTarget.addItem(info: ItemInfo) {
-        val userType: String? =
-            when (UserCache.INSTANCE.get(context).getUserInfo(info.user).type) {
-                UserIconInfo.TYPE_WORK -> AutoInstallsLayout.USER_TYPE_WORK
-                UserIconInfo.TYPE_CLONED -> AutoInstallsLayout.USER_TYPE_CLONED
-                else -> null
-            }
-        when (info.itemType) {
-            ITEM_TYPE_APPLICATION ->
-                info.targetComponent?.let { c -> putApp(c.packageName, c.className, userType) }
-            ITEM_TYPE_DEEP_SHORTCUT ->
-                ShortcutKey.fromItemInfo(info).let { key ->
-                    putShortcut(key.packageName, key.id, userType)
-                }
-            ITEM_TYPE_FOLDER ->
-                (info as FolderInfo).let { folderInfo ->
-                    putFolder(folderInfo.title?.toString() ?: "").also { folderBuilder ->
-                        folderInfo.getContents().forEach { folderContent ->
-                            folderBuilder.addItem(folderContent)
-                        }
-                    }
-                }
-            ITEM_TYPE_APPWIDGET ->
-                putWidget(
-                    (info as LauncherAppWidgetInfo).providerName.packageName,
-                    info.providerName.className,
-                    info.spanX,
-                    info.spanY,
-                    userType,
-                )
-        }
-    }
-
     private fun createUriPickerIntent(
         action: String,
         executor: Executor,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index d387794..b27c6e8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -48,8 +48,7 @@
     }
 
     @Override
-    public <DEVICE_PROFILE_CONTEXT extends Context & ActivityContext>
-    int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) {
+    public int getTransitionDuration(ActivityContext context, boolean isToState) {
         return isToState
                 ? context.getDeviceProfile().allAppsOpenDuration
                 : context.getDeviceProfile().allAppsCloseDuration;
@@ -102,7 +101,7 @@
 
     @Override
     public int getTitle() {
-        return R.string.all_apps_label;
+        return R.string.all_apps_list_label;
     }
 
     @Override
@@ -154,7 +153,7 @@
         return new PageAlphaProvider(DECELERATE_2) {
             @Override
             public float getPageAlpha(int pageIndex) {
-                return launcher.getDeviceProfile().shouldShowAllAppsOnSheet()
+                return launcher.getDeviceProfile().isTablet
                         ? superPageAlphaProvider.getPageAlpha(pageIndex)
                         : 0;
             }
@@ -165,7 +164,7 @@
     public int getVisibleElements(Launcher launcher) {
         int elements = ALL_APPS_CONTENT | FLOATING_SEARCH_BAR;
         // When All Apps is presented on a bottom sheet, HOTSEAT_ICONS are visible.
-        if (launcher.getDeviceProfile().shouldShowAllAppsOnSheet()) {
+        if (launcher.getDeviceProfile().isTablet) {
             elements |= HOTSEAT_ICONS;
         }
         return elements;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index ca388c6..a5b1ee7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -77,7 +77,8 @@
         return super.getVisibleElements(launcher)
                 & ~OVERVIEW_ACTIONS
                 & ~CLEAR_ALL_BUTTON
-                & ~VERTICAL_SWIPE_INDICATOR;
+                & ~VERTICAL_SWIPE_INDICATOR
+                & ~ADD_DESK_BUTTON;
     }
 
     @Override
@@ -96,6 +97,11 @@
     }
 
     @Override
+    public boolean showExplodedDesktopView() {
+        return false;
+    }
+
+    @Override
     protected float getDepthUnchecked(Context context) {
         if (Launcher.getLauncher(context).areDesktopTasksVisible()) {
             // Don't blur the background while desktop tasks are visible
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 932d241..0c0b4fd 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -17,12 +17,12 @@
 
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
 
-import android.content.Context;
 import android.graphics.Rect;
 
 import com.android.launcher3.Flags;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
+import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.views.RecentsView;
 
 /**
@@ -39,13 +39,13 @@
     }
 
     @Override
-    public int getTransitionDuration(Context launcher, boolean isToState) {
+    public int getTransitionDuration(ActivityContext launcher, boolean isToState) {
         return 300;
     }
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        return OVERVIEW_ACTIONS | CLEAR_ALL_BUTTON;
+        return OVERVIEW_ACTIONS;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
index c48ba4f..963504f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -16,9 +16,9 @@
 package com.android.launcher3.uioverrides.states;
 
 import static com.android.app.animation.Interpolators.DECELERATE_2;
+import static com.android.launcher3.Flags.enableDesktopExplodedView;
 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
-import static com.android.wm.shell.Flags.enableSplitContextual;
 
 import android.content.Context;
 import android.graphics.Rect;
@@ -30,6 +30,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.util.BaseDepthController;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
@@ -63,10 +64,10 @@
     }
 
     @Override
-    public int getTransitionDuration(Context context, boolean isToState) {
+    public int getTransitionDuration(ActivityContext context, boolean isToState) {
         if (isToState) {
             // In gesture modes, overview comes in all the way from the side, so give it more time.
-            return DisplayController.getNavigationMode(context).hasGestures
+            return DisplayController.getNavigationMode(context.asContext()).hasGestures
                     ? OVERVIEW_SLIDE_IN_DURATION
                     : OVERVIEW_POP_IN_DURATION;
         } else {
@@ -110,7 +111,7 @@
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        int elements = CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS;
+        int elements = CLEAR_ALL_BUTTON | OVERVIEW_ACTIONS | ADD_DESK_BUTTON;
         DeviceProfile dp = launcher.getDeviceProfile();
         boolean showFloatingSearch;
         if (dp.isPhone) {
@@ -123,15 +124,15 @@
         if (showFloatingSearch) {
             elements |= FLOATING_SEARCH_BAR;
         }
-        if (enableSplitContextual() && launcher.isSplitSelectionActive()) {
-            elements &= ~CLEAR_ALL_BUTTON;
+        if (launcher.isSplitSelectionActive()) {
+            elements &= ~CLEAR_ALL_BUTTON & ~ADD_DESK_BUTTON;
         }
         return elements;
     }
 
     @Override
     public float getSplitSelectTranslation(Launcher launcher) {
-        if (!enableSplitContextual() || !launcher.isSplitSelectionActive()) {
+        if (!launcher.isSplitSelectionActive()) {
             return 0f;
         }
         RecentsView recentsView = launcher.getOverviewPanel();
@@ -171,6 +172,11 @@
     }
 
     @Override
+    public boolean showExplodedDesktopView() {
+        return enableDesktopExplodedView();
+    }
+
+    @Override
     public boolean disallowTaskbarGlobalDrag() {
         // Disable global drag in overview
         return true;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 8ad00bf..44f8bf1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -109,8 +109,11 @@
 
             // We sync the scrim fade with the taskbar animation duration to avoid any flickers for
             // taskbar icons disappearing before hotseat icons show up.
+            boolean isPinnedTaskbarAndNotInDesktopMode =
+                    isPinnedTaskbar && !DisplayController.isInDesktopMode(mContainer);
             float scrimUpperBoundFromSplit =
-                    QuickstepTransitionManager.getTaskbarToHomeDuration(isPinnedTaskbar)
+                    QuickstepTransitionManager.getTaskbarToHomeDuration(
+                            isPinnedTaskbarAndNotInDesktopMode)
                             / (float) config.duration;
             scrimUpperBoundFromSplit = Math.min(scrimUpperBoundFromSplit, 1f);
             config.setInterpolator(ANIM_OVERVIEW_ACTIONS_FADE, clampToProgress(LINEAR, 0, 0.25f));
@@ -122,7 +125,7 @@
             config.setInterpolator(ANIM_WORKSPACE_FADE, ACCELERATE);
 
             if (DisplayController.getNavigationMode(mContainer).hasGestures
-                    && overview.getTaskViewCount() > 0) {
+                    && overview.hasTaskViews()) {
                 // Overview is going offscreen, so keep it at its current scale and opacity.
                 config.setInterpolator(ANIM_OVERVIEW_SCALE, FINAL_FRAME);
                 config.setInterpolator(ANIM_OVERVIEW_FADE, FINAL_FRAME);
@@ -142,7 +145,8 @@
                 if (mContainer.getDeviceProfile().isTaskbarPresent) {
                     config.duration = Math.min(
                             config.duration,
-                            QuickstepTransitionManager.getTaskbarToHomeDuration(isPinnedTaskbar));
+                            QuickstepTransitionManager.getTaskbarToHomeDuration(
+                                    isPinnedTaskbarAndNotInDesktopMode));
                 }
                 overview.snapToPage(DEFAULT_PAGE, Math.toIntExact(config.duration));
             } else {
@@ -178,7 +182,7 @@
                 config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCELERATE);
 
                 // Scrolling in tasks, so show straight away
-                if (overview.getTaskViewCount() > 0) {
+                if (overview.hasTaskViews()) {
                     config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
                 } else {
                     config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
index 3ae221b..2631fbf 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -16,9 +16,8 @@
 
 package com.android.launcher3.uioverrides.states;
 
-import android.content.Context;
-
 import com.android.launcher3.Launcher;
+import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.views.RecentsView;
 
@@ -43,11 +42,10 @@
     }
 
     @Override
-    public int getTransitionDuration(Context context, boolean isToState) {
-        boolean isTablet = ((Launcher) context).getDeviceProfile().isTablet;
-        if (isToState && isTablet) {
+    public int getTransitionDuration(ActivityContext context, boolean isToState) {
+        if (isToState && context.getDeviceProfile().isTablet) {
             return SplitAnimationTimings.TABLET_ENTER_DURATION;
-        } else if (isToState && !isTablet) {
+        } else if (isToState) {
             return SplitAnimationTimings.PHONE_ENTER_DURATION;
         } else {
             return SplitAnimationTimings.ABORT_DURATION;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 1d9e492..a4f8b81 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -45,11 +45,11 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.TouchController;
+import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskUtils;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.OverviewToHomeAnim;
@@ -210,10 +210,7 @@
                     mLauncher.getStateManager().addStateListener(listener);
                     onSwipeInteractionCompleted(mEndState);
                 };
-                new OverviewToHomeAnim(mLauncher, onReachedHome,
-                        FeatureFlags.enableSplitContextually()
-                                ? mCancelSplitRunnable
-                                : null)
+                new OverviewToHomeAnim(mLauncher, onReachedHome, mCancelSplitRunnable)
                         .animateWithVelocity(velocity);
             } else {
                 mLauncher.getStateManager().goToState(mEndState, true,
@@ -221,7 +218,7 @@
             }
             if (mStartState != mEndState) {
                 logHomeGesture();
-                ContextualEduStatsManager.INSTANCE.get(mLauncher).updateEduStats(
+                SystemUiProxy.INSTANCE.get(mLauncher).updateContextualEduStats(
                         mSwipeDetector.isTrackpadGesture(), GestureType.HOME);
             }
             AbstractFloatingView topOpenView = AbstractFloatingView.getTopOpenView(mLauncher);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 9dec332..58b274a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -64,6 +64,7 @@
 import android.graphics.PointF;
 import android.view.MotionEvent;
 import android.view.animation.Interpolator;
+import android.window.DesktopModeFlags;
 
 import com.android.internal.jank.Cuj;
 import com.android.launcher3.LauncherState;
@@ -86,6 +87,7 @@
 import com.android.quickstep.util.WorkspaceRevealAnim;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 
 /**
  * Handles quick switching to a recent task from the home screen. To give as much flexibility to
@@ -184,6 +186,12 @@
             mIsTrackpadSwipe = isTrackpadFourFingerSwipe(ev);
             return mIsTrackpadSwipe;
         }
+        if (DesktopModeStatus.canEnterDesktopMode(mLauncher)
+                //TODO(b/345296916): replace with dev option once in teamfood
+                && DesktopModeFlags.ENABLE_QUICKSWITCH_DESKTOP_SPLIT_BUGFIX.isTrue()
+                && mRecentsView.getNonDesktopTaskViewCount() < 1) {
+            return false;
+        }
         return true;
     }
 
@@ -223,7 +231,7 @@
         updateNonOverviewAnim(QUICK_SWITCH_FROM_HOME, nonOverviewBuilder);
         mNonOverviewAnim.dispatchOnStart();
 
-        if (mRecentsView.getTaskViewCount() == 0) {
+        if (!mRecentsView.hasTaskViews()) {
             mRecentsView.setOnEmptyMessageUpdatedListener(isEmpty -> {
                 if (!isEmpty && mSwipeDetector.isDraggingState()) {
                     // We have loaded tasks, update the animators to start at the correct scale etc.
@@ -269,7 +277,7 @@
         // since we need to take potential taskbar into account.
         xAnim.setViewBackgroundColor(mLauncher.getScrimView(),
                 QUICK_SWITCH_FROM_HOME.getWorkspaceScrimColor(mLauncher), LINEAR);
-        if (mRecentsView.getTaskViewCount() == 0) {
+        if (!mRecentsView.hasTaskViews()) {
             xAnim.addFloat(mRecentsView, CONTENT_ALPHA, 0f, 1f, LINEAR);
         }
         mXOverviewAnim = xAnim.createPlaybackController();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
index 4df0f63..68940dc 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitOverviewStateTouchHelper.java
@@ -48,9 +48,9 @@
      * @return true if we should intercept the motion event
      */
     boolean canInterceptTouch(MotionEvent ev) {
-        if (mRecentsView.getTaskViewCount() > 0) {
+        if (mRecentsView.hasTaskViews()) {
             // Allow swiping up in the gap between the hotseat and overview.
-            return ev.getY() >= mRecentsView.getTaskViewAt(0).getBottom();
+            return ev.getY() >= mRecentsView.getFirstTaskView().getBottom();
         } else {
             // If there are no tasks, we only intercept if we're below the hotseat height.
             return isTouchOverHotseat(mLauncher, ev);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index b562838..a74b350 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -37,6 +37,7 @@
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.views.RecentsView;
+import com.android.systemui.contextualeducation.GestureType;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 
 /**
@@ -163,8 +164,19 @@
     @Override
     protected void onSwipeInteractionCompleted(LauncherState targetState) {
         super.onSwipeInteractionCompleted(targetState);
+        SystemUiProxy sysUIProxy = SystemUiProxy.INSTANCE.get(mLauncher);
         if (mStartState == NORMAL && targetState == OVERVIEW) {
-            SystemUiProxy.INSTANCE.get(mLauncher).onOverviewShown(true, TAG);
+            sysUIProxy.onOverviewShown(true, TAG);
+        }
+
+        if (targetState == OVERVIEW) {
+            sysUIProxy.updateContextualEduStats(
+                    mDetector.isTrackpadGesture(), GestureType.OVERVIEW);
+        } else if (targetState == ALL_APPS && !mDetector.isTrackpadGesture()) {
+            // Only update if it is touch gesture as trackpad gesture is not relevant for all apps
+            // which only provides keyboard education.
+            sysUIProxy.updateContextualEduStats(
+                    /* isTrackpadGesture= */ false, GestureType.ALL_APPS);
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 31e4e33..f582324 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -149,9 +149,9 @@
         mOverviewPanel.setFullscreenProgress(progress);
         if (progress > UPDATE_SYSUI_FLAGS_THRESHOLD) {
             int sysuiFlags = 0;
-            TaskView tv = mOverviewPanel.getTaskViewAt(0);
-            if (tv != null) {
-                sysuiFlags = tv.getTaskContainers().getFirst().getSysUiStatusNavFlags();
+            TaskView firstTaskView = mOverviewPanel.getFirstTaskView();
+            if (firstTaskView != null) {
+                sysuiFlags = firstTaskView.getSysUiStatusNavFlags();
             }
             mLauncher.getSystemUiController().updateUiState(UI_STATE_FULLSCREEN_TASK, sysuiFlags);
         } else {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
new file mode 100644
index 0000000..454a307
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewDismissTouchController.kt
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.uioverrides.touchcontrollers
+
+import android.content.Context
+import android.graphics.Rect
+import android.view.MotionEvent
+import androidx.dynamicanimation.animation.SpringAnimation
+import com.android.app.animation.Interpolators.DECELERATE
+import com.android.app.animation.Interpolators.LINEAR
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.R
+import com.android.launcher3.Utilities.EDGE_NAV_BAR
+import com.android.launcher3.Utilities.boundToRange
+import com.android.launcher3.Utilities.isRtl
+import com.android.launcher3.Utilities.mapToRange
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.MSDLPlayerWrapper
+import com.android.launcher3.util.TouchController
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY
+import com.android.quickstep.views.RecentsViewContainer
+import com.android.quickstep.views.TaskView
+import com.google.android.msdl.data.model.MSDLToken
+import kotlin.math.abs
+
+/** Touch controller for handling task view card dismiss swipes */
+class TaskViewDismissTouchController<CONTAINER>(
+    private val container: CONTAINER,
+    private val taskViewRecentsTouchContext: TaskViewRecentsTouchContext,
+) : TouchController, SingleAxisSwipeDetector.Listener where
+CONTAINER : Context,
+CONTAINER : RecentsViewContainer {
+    private val recentsView: RecentsView<*, *> = container.getOverviewPanel()
+    private val detector: SingleAxisSwipeDetector =
+        SingleAxisSwipeDetector(
+            container as Context,
+            this,
+            recentsView.pagedOrientationHandler.upDownSwipeDirection,
+        )
+    private val isRtl = isRtl(container.resources)
+    private val upDirection: Int = recentsView.pagedOrientationHandler.getUpDirection(isRtl)
+
+    private val tempTaskThumbnailBounds = Rect()
+
+    private var taskBeingDragged: TaskView? = null
+    private var springAnimation: SpringAnimation? = null
+    private var dismissLength: Int = 0
+    private var verticalFactor: Int = 0
+    private var hasDismissThresholdHapticRun = false
+    private var initialDisplacement: Float = 0f
+    private var recentsScaleAnimation: SpringAnimation? = null
+
+    private fun canInterceptTouch(ev: MotionEvent): Boolean =
+        when {
+            // Don't intercept swipes on the nav bar, as user might be trying to go home during a
+            // task dismiss animation.
+            (ev.edgeFlags and EDGE_NAV_BAR) != 0 -> {
+                false
+            }
+
+            // Floating views that a TouchController should not try to intercept touches from.
+            AbstractFloatingView.getTopOpenViewWithType(
+                container,
+                AbstractFloatingView.TYPE_TOUCH_CONTROLLER_NO_INTERCEPT,
+            ) != null -> false
+
+            // Disable swiping if the task overlay is modal.
+            taskViewRecentsTouchContext.isRecentsModal -> {
+                false
+            }
+
+            else -> taskViewRecentsTouchContext.isRecentsInteractive
+        }
+
+    override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
+        if ((ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL)) {
+            clearState()
+        }
+        if (ev.action == MotionEvent.ACTION_DOWN) {
+            if (!onActionDown(ev)) {
+                return false
+            }
+        }
+
+        onControllerTouchEvent(ev)
+        val upDirectionIsPositive = upDirection == SingleAxisSwipeDetector.DIRECTION_POSITIVE
+        val wasInitialTouchUp =
+            (upDirectionIsPositive && detector.wasInitialTouchPositive()) ||
+                (!upDirectionIsPositive && !detector.wasInitialTouchPositive())
+        return detector.isDraggingState && wasInitialTouchUp
+    }
+
+    override fun onControllerTouchEvent(ev: MotionEvent?): Boolean = detector.onTouchEvent(ev)
+
+    private fun onActionDown(ev: MotionEvent): Boolean {
+        springAnimation?.cancel()
+        recentsScaleAnimation?.cancel()
+        if (!canInterceptTouch(ev)) {
+            return false
+        }
+        taskBeingDragged =
+            recentsView.taskViews
+                .firstOrNull {
+                    recentsView.isTaskViewVisible(it) && container.dragLayer.isEventOverView(it, ev)
+                }
+                ?.also {
+                    val secondaryLayerDimension =
+                        recentsView.pagedOrientationHandler.getSecondaryDimension(
+                            container.dragLayer
+                        )
+                    // Dismiss length as bottom of task so it is fully off screen when dismissed.
+                    it.getThumbnailBounds(tempTaskThumbnailBounds, relativeToDragLayer = true)
+                    dismissLength =
+                        recentsView.pagedOrientationHandler.getTaskDismissLength(
+                            secondaryLayerDimension,
+                            tempTaskThumbnailBounds,
+                        )
+                    verticalFactor =
+                        recentsView.pagedOrientationHandler.getTaskDismissVerticalDirection()
+                }
+        detector.setDetectableScrollConditions(upDirection, /* ignoreSlop= */ false)
+        return true
+    }
+
+    override fun onDragStart(start: Boolean, startDisplacement: Float) {
+        val taskBeingDragged = taskBeingDragged ?: return
+
+        initialDisplacement =
+            taskBeingDragged.secondaryDismissTranslationProperty.get(taskBeingDragged)
+
+        // Add a tiny bit of translation Z, so that it draws on top of other views. This is relevant
+        // (e.g.) when we dismiss a task by sliding it upward: if there is a row of icons above, we
+        // want the dragged task to stay above all other views.
+        taskBeingDragged.translationZ = 0.1f
+    }
+
+    override fun onDrag(displacement: Float): Boolean {
+        val taskBeingDragged = taskBeingDragged ?: return false
+        val currentDisplacement = displacement + initialDisplacement
+        val boundedDisplacement =
+            boundToRange(abs(currentDisplacement), 0f, dismissLength.toFloat())
+        // When swiping below origin, allow slight undershoot to simulate resisting the movement.
+        val totalDisplacement =
+            if (recentsView.pagedOrientationHandler.isGoingUp(currentDisplacement, isRtl))
+                boundedDisplacement * verticalFactor
+            else
+                mapToRange(
+                    boundedDisplacement,
+                    0f,
+                    dismissLength.toFloat(),
+                    0f,
+                    container.resources.getDimension(R.dimen.task_dismiss_max_undershoot),
+                    DECELERATE,
+                ) * -verticalFactor
+        taskBeingDragged.secondaryDismissTranslationProperty.setValue(
+            taskBeingDragged,
+            totalDisplacement,
+        )
+        if (taskBeingDragged.isRunningTask && recentsView.enableDrawingLiveTile) {
+            recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+                remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
+                    totalDisplacement
+            }
+            recentsView.redrawLiveTile()
+        }
+        val dismissFraction = displacement / (dismissLength * verticalFactor).toFloat()
+        RECENTS_SCALE_PROPERTY.setValue(recentsView, getRecentsScale(dismissFraction))
+        playDismissThresholdHaptic(displacement)
+        return true
+    }
+
+    /**
+     * Play a haptic to alert the user they have passed the dismiss threshold.
+     *
+     * <p>Check within a range of the threshold value, as the drag event does not necessarily happen
+     * at the exact threshold's displacement.
+     */
+    private fun playDismissThresholdHaptic(displacement: Float) {
+        val dismissThreshold = (DISMISS_THRESHOLD_FRACTION * dismissLength * verticalFactor)
+        val inHapticRange =
+            displacement >= (dismissThreshold - DISMISS_THRESHOLD_HAPTIC_RANGE) &&
+                displacement <= (dismissThreshold + DISMISS_THRESHOLD_HAPTIC_RANGE)
+        if (!inHapticRange) {
+            hasDismissThresholdHapticRun = false
+        } else if (!hasDismissThresholdHapticRun) {
+            MSDLPlayerWrapper.INSTANCE.get(recentsView.context)
+                .playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR)
+            hasDismissThresholdHapticRun = true
+        }
+    }
+
+    override fun onDragEnd(velocity: Float) {
+        val taskBeingDragged = taskBeingDragged ?: return
+
+        val currentDisplacement =
+            taskBeingDragged.secondaryDismissTranslationProperty.get(taskBeingDragged)
+        if (currentDisplacement == 0f) {
+            clearState()
+            return
+        }
+        val isBeyondDismissThreshold =
+            abs(currentDisplacement) > abs(DISMISS_THRESHOLD_FRACTION * dismissLength)
+        val velocityIsGoingUp = recentsView.pagedOrientationHandler.isGoingUp(velocity, isRtl)
+        val isFlingingTowardsDismiss = detector.isFling(velocity) && velocityIsGoingUp
+        val isFlingingTowardsRestState = detector.isFling(velocity) && !velocityIsGoingUp
+        val isDismissing =
+            isFlingingTowardsDismiss || (isBeyondDismissThreshold && !isFlingingTowardsRestState)
+        springAnimation =
+            recentsView
+                .createTaskDismissSettlingSpringAnimation(
+                    taskBeingDragged,
+                    velocity,
+                    isDismissing,
+                    detector,
+                    dismissLength,
+                    this::clearState,
+                )
+                .apply {
+                    animateToFinalPosition(
+                        if (isDismissing) (dismissLength * verticalFactor).toFloat() else 0f
+                    )
+                }
+        recentsScaleAnimation =
+            recentsView.animateRecentsScale(RECENTS_SCALE_DEFAULT).addEndListener { _, _, _, _ ->
+                recentsScaleAnimation = null
+            }
+    }
+
+    private fun clearState() {
+        detector.finishedScrolling()
+        detector.setDetectableScrollConditions(0, false)
+        taskBeingDragged?.translationZ = 0f
+        taskBeingDragged = null
+        springAnimation = null
+    }
+
+    private fun getRecentsScale(dismissFraction: Float): Float {
+        return when {
+            // Do not scale recents when dragging below origin.
+            dismissFraction <= 0 -> {
+                RECENTS_SCALE_DEFAULT
+            }
+            // Initially scale recents as the drag begins, up to the first threshold.
+            dismissFraction < RECENTS_SCALE_FIRST_THRESHOLD_FRACTION -> {
+                mapToRange(
+                    dismissFraction,
+                    0f,
+                    RECENTS_SCALE_FIRST_THRESHOLD_FRACTION,
+                    RECENTS_SCALE_DEFAULT,
+                    RECENTS_SCALE_ON_DISMISS_CANCEL,
+                    LINEAR,
+                )
+            }
+            // Keep scale consistent until dragging to the dismiss threshold.
+            dismissFraction < RECENTS_SCALE_DISMISS_THRESHOLD_FRACTION -> {
+                RECENTS_SCALE_ON_DISMISS_CANCEL
+            }
+            // Scale beyond the dismiss threshold again, to indicate dismiss will occur on release.
+            dismissFraction < RECENTS_SCALE_SECOND_THRESHOLD_FRACTION -> {
+                mapToRange(
+                    dismissFraction,
+                    RECENTS_SCALE_DISMISS_THRESHOLD_FRACTION,
+                    RECENTS_SCALE_SECOND_THRESHOLD_FRACTION,
+                    RECENTS_SCALE_ON_DISMISS_CANCEL,
+                    RECENTS_SCALE_ON_DISMISS_SUCCESS,
+                    LINEAR,
+                )
+            }
+            // Keep scale beyond the dismiss threshold scaling consistent.
+            else -> {
+                RECENTS_SCALE_ON_DISMISS_SUCCESS
+            }
+        }
+    }
+
+    companion object {
+        private const val DISMISS_THRESHOLD_FRACTION = 0.5f
+        private const val DISMISS_THRESHOLD_HAPTIC_RANGE = 10f
+
+        private const val RECENTS_SCALE_ON_DISMISS_CANCEL = 0.9875f
+        private const val RECENTS_SCALE_ON_DISMISS_SUCCESS = 0.975f
+        private const val RECENTS_SCALE_DEFAULT = 1f
+        private const val RECENTS_SCALE_FIRST_THRESHOLD_FRACTION = 0.2f
+        private const val RECENTS_SCALE_DISMISS_THRESHOLD_FRACTION = 0.5f
+        private const val RECENTS_SCALE_SECOND_THRESHOLD_FRACTION = 0.575f
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
new file mode 100644
index 0000000..8ee552d
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewLaunchTouchController.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides.touchcontrollers
+
+import android.content.Context
+import android.graphics.Rect
+import android.view.MotionEvent
+import com.android.app.animation.Interpolators.ZOOM_IN
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.LauncherAnimUtils
+import com.android.launcher3.Utilities.EDGE_NAV_BAR
+import com.android.launcher3.Utilities.boundToRange
+import com.android.launcher3.Utilities.isRtl
+import com.android.launcher3.anim.AnimatorPlaybackController
+import com.android.launcher3.touch.BaseSwipeDetector
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.FlingBlockCheck
+import com.android.launcher3.util.TouchController
+import com.android.quickstep.views.RecentsView
+import com.android.quickstep.views.RecentsViewContainer
+import com.android.quickstep.views.TaskView
+import kotlin.math.abs
+
+/** Touch controller which handles dragging task view cards for launch. */
+class TaskViewLaunchTouchController<CONTAINER>(
+    private val container: CONTAINER,
+    private val taskViewRecentsTouchContext: TaskViewRecentsTouchContext,
+) : TouchController, SingleAxisSwipeDetector.Listener where
+CONTAINER : Context,
+CONTAINER : RecentsViewContainer {
+    private val tempRect = Rect()
+    private val flingBlockCheck = FlingBlockCheck()
+    private val recentsView: RecentsView<*, *> = container.getOverviewPanel()
+    private val detector: SingleAxisSwipeDetector =
+        SingleAxisSwipeDetector(
+            container as Context,
+            this,
+            recentsView.pagedOrientationHandler.upDownSwipeDirection,
+        )
+    private val isRtl = isRtl(container.resources)
+    private val downDirection = recentsView.pagedOrientationHandler.getDownDirection(isRtl)
+
+    private var taskBeingDragged: TaskView? = null
+    private var launchEndDisplacement: Float = 0f
+    private var playbackController: AnimatorPlaybackController? = null
+    private var verticalFactor: Int = 0
+
+    private fun canTaskLaunchTaskView(taskView: TaskView?) =
+        taskView != null &&
+            taskView === recentsView.currentPageTaskView &&
+            DisplayController.getNavigationMode(container).hasGestures &&
+            (!recentsView.showAsGrid() || taskView.isLargeTile) &&
+            recentsView.isTaskInExpectedScrollPosition(taskView)
+
+    private fun canInterceptTouch(ev: MotionEvent): Boolean =
+        when {
+            // Don't intercept swipes on the nav bar, as user might be trying to go home during a
+            // task dismiss animation.
+            (ev.edgeFlags and EDGE_NAV_BAR) != 0 -> {
+                false
+            }
+
+            // Floating views that a TouchController should not try to intercept touches from.
+            AbstractFloatingView.getTopOpenViewWithType(
+                container,
+                AbstractFloatingView.TYPE_TOUCH_CONTROLLER_NO_INTERCEPT,
+            ) != null -> {
+                false
+            }
+
+            // Disable swiping if the task overlay is modal.
+            taskViewRecentsTouchContext.isRecentsModal -> {
+                false
+            }
+
+            else -> taskViewRecentsTouchContext.isRecentsInteractive
+        }
+
+    override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
+        if (
+            (ev.action == MotionEvent.ACTION_UP || ev.action == MotionEvent.ACTION_CANCEL) &&
+                playbackController == null
+        ) {
+            clearState()
+        }
+        if (ev.action == MotionEvent.ACTION_DOWN) {
+            if (!onActionDown(ev)) {
+                clearState()
+                return false
+            }
+        }
+        onControllerTouchEvent(ev)
+        val downDirectionIsNegative = downDirection == SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+        val wasInitialTouchDown =
+            (downDirectionIsNegative && !detector.wasInitialTouchPositive()) ||
+                (!downDirectionIsNegative && detector.wasInitialTouchPositive())
+        return detector.isDraggingState && wasInitialTouchDown
+    }
+
+    override fun onControllerTouchEvent(ev: MotionEvent) = detector.onTouchEvent(ev)
+
+    private fun onActionDown(ev: MotionEvent): Boolean {
+        if (!canInterceptTouch(ev)) {
+            return false
+        }
+        taskBeingDragged =
+            recentsView.taskViews
+                .firstOrNull {
+                    recentsView.isTaskViewVisible(it) && container.dragLayer.isEventOverView(it, ev)
+                }
+                ?.also {
+                    verticalFactor =
+                        recentsView.pagedOrientationHandler.getTaskDragDisplacementFactor(isRtl)
+                }
+        if (!canTaskLaunchTaskView(taskBeingDragged)) {
+            return false
+        }
+        detector.setDetectableScrollConditions(downDirection, /* ignoreSlop= */ false)
+        return true
+    }
+
+    override fun onDragStart(start: Boolean, startDisplacement: Float) {
+        val taskBeingDragged = taskBeingDragged ?: return
+
+        val secondaryLayerDimension: Int =
+            recentsView.pagedOrientationHandler.getSecondaryDimension(container.getDragLayer())
+        val maxDuration = 2L * secondaryLayerDimension
+        recentsView.clearPendingAnimation()
+        val pendingAnimation =
+            recentsView.createTaskLaunchAnimation(taskBeingDragged, maxDuration, ZOOM_IN)
+        // Since the thumbnail is what is filling the screen, based the end displacement on it.
+        taskBeingDragged.getThumbnailBounds(tempRect, /* relativeToDragLayer= */ true)
+        launchEndDisplacement =
+            recentsView.pagedOrientationHandler
+                .getTaskLaunchLength(secondaryLayerDimension, tempRect)
+                .toFloat() * verticalFactor
+        playbackController =
+            pendingAnimation.createPlaybackController()?.apply {
+                taskViewRecentsTouchContext.onUserControlledAnimationCreated(this)
+                dispatchOnStart()
+            }
+    }
+
+    override fun onDrag(displacement: Float): Boolean {
+        playbackController?.setPlayFraction(
+            boundToRange(displacement / launchEndDisplacement, 0f, 1f)
+        )
+        return true
+    }
+
+    override fun onDragEnd(velocity: Float) {
+        val playbackController = playbackController ?: return
+
+        val isBeyondLaunchThreshold =
+            abs(playbackController.progressFraction) > abs(LAUNCH_THRESHOLD_FRACTION)
+        val velocityIsNegative = !recentsView.pagedOrientationHandler.isGoingUp(velocity, isRtl)
+        val isFlingingTowardsLaunch = detector.isFling(velocity) && velocityIsNegative
+        val isFlingingTowardsRestState = detector.isFling(velocity) && !velocityIsNegative
+        val isLaunching =
+            isFlingingTowardsLaunch || (isBeyondLaunchThreshold && !isFlingingTowardsRestState)
+
+        val progress = playbackController.progressFraction
+        var animationDuration =
+            BaseSwipeDetector.calculateDuration(
+                velocity,
+                if (isLaunching) (1 - progress) else progress,
+            )
+        if (detector.isFling(velocity) && flingBlockCheck.isBlocked && !isLaunching) {
+            animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity).toLong()
+        }
+
+        playbackController.setEndAction(this::clearState)
+        playbackController.startWithVelocity(
+            container,
+            isLaunching,
+            velocity,
+            launchEndDisplacement,
+            animationDuration,
+        )
+    }
+
+    private fun clearState() {
+        detector.finishedScrolling()
+        detector.setDetectableScrollConditions(0, false)
+        taskBeingDragged = null
+        playbackController = null
+    }
+
+    companion object {
+        private const val LAUNCH_THRESHOLD_FRACTION: Float = 0.5f
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewRecentsTouchContext.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewRecentsTouchContext.java
new file mode 100644
index 0000000..e8d31c1
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewRecentsTouchContext.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.uioverrides.touchcontrollers;
+
+import com.android.launcher3.anim.AnimatorPlaybackController;
+
+/** Interface providing context about the RecentsView state to a {@link TaskViewTouchController}. */
+public interface TaskViewRecentsTouchContext {
+    /** Returns whether Recents is interactive for touch. */
+    boolean isRecentsInteractive();
+
+    /** Returns if Recents is showing a single task in a modal way. */
+    boolean isRecentsModal();
+
+    /** Runs when a user controlled animation is created. */
+    default void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
similarity index 91%
rename from quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
rename to quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
index 202276e..f26bd13 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchControllerDeprecated.java
@@ -49,10 +49,13 @@
 
 /**
  * Touch controller for handling task view card swipes
+ *
+ * @deprecated This class will be replaced by the new {@link TaskViewTouchController}.
  */
-public abstract class TaskViewTouchController<CONTAINER extends Context & RecentsViewContainer>
-        extends AnimatorListenerAdapter implements TouchController,
-        SingleAxisSwipeDetector.Listener {
+@Deprecated
+public class TaskViewTouchControllerDeprecated<
+        CONTAINER extends Context & RecentsViewContainer> extends AnimatorListenerAdapter
+        implements TouchController, SingleAxisSwipeDetector.Listener {
 
     private static final float ANIMATION_PROGRESS_FRACTION_MIDPOINT = 0.5f;
     private static final long MIN_TASK_DISMISS_ANIMATION_DURATION = 300;
@@ -65,8 +68,9 @@
             VibrationConstants.EFFECT_TEXTURE_TICK;
 
     protected final CONTAINER mContainer;
+    private final TaskViewRecentsTouchContext mTaskViewRecentsTouchContext;
     private final SingleAxisSwipeDetector mDetector;
-    private final RecentsView mRecentsView;
+    private final RecentsView<?, ?> mRecentsView;
     private final Rect mTempRect = new Rect();
     private final boolean mIsRtl;
 
@@ -88,8 +92,10 @@
 
     private boolean mIsDismissHapticRunning = false;
 
-    public TaskViewTouchController(CONTAINER container) {
+    public TaskViewTouchControllerDeprecated(CONTAINER container,
+            TaskViewRecentsTouchContext taskViewRecentsTouchContext) {
         mContainer = container;
+        mTaskViewRecentsTouchContext = taskViewRecentsTouchContext;
         mRecentsView = container.getOverviewPanel();
         mIsRtl = Utilities.isRtl(container.getResources());
         SingleAxisSwipeDetector.Direction dir =
@@ -117,15 +123,7 @@
                 mContainer, TYPE_TOUCH_CONTROLLER_NO_INTERCEPT) != null) {
             return false;
         }
-        return isRecentsInteractive();
-    }
-
-    protected abstract boolean isRecentsInteractive();
-
-    /** Is recents view showing a single task in a modal way. */
-    protected abstract boolean isRecentsModal();
-
-    protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
+        return mTaskViewRecentsTouchContext.isRecentsInteractive();
     }
 
     @Override
@@ -157,17 +155,15 @@
             } else {
                 mTaskBeingDragged = null;
 
-                for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) {
-                    TaskView view = mRecentsView.getTaskViewAt(i);
-
-                    if (mRecentsView.isTaskViewVisible(view) && mContainer.getDragLayer()
-                            .isEventOverView(view, ev)) {
+                for (TaskView taskView : mRecentsView.getTaskViews()) {
+                    if (mRecentsView.isTaskViewVisible(taskView) && mContainer.getDragLayer()
+                            .isEventOverView(taskView, ev)) {
                         // Disable swiping up and down if the task overlay is modal.
-                        if (isRecentsModal()) {
+                        if (mTaskViewRecentsTouchContext.isRecentsModal()) {
                             mTaskBeingDragged = null;
                             break;
                         }
-                        mTaskBeingDragged = view;
+                        mTaskBeingDragged = taskView;
                         int upDirection = mRecentsView.getPagedOrientationHandler()
                                 .getUpDirection(mIsRtl);
 
@@ -179,10 +175,10 @@
                         // - We support gestures to enter overview
                         // - It's the focused task if in grid view
                         // - The task is snapped
-                        mAllowGoingDown = i == mRecentsView.getCurrentPage()
+                        mAllowGoingDown = taskView == mRecentsView.getCurrentPageTaskView()
                                 && DisplayController.getNavigationMode(mContainer).hasGestures
                                 && (!mRecentsView.showAsGrid() || mTaskBeingDragged.isLargeTile())
-                                && mRecentsView.isTaskInExpectedScrollPosition(i);
+                                && mRecentsView.isTaskInExpectedScrollPosition(taskView);
 
                         directionsToDetectScroll = mAllowGoingDown ? DIRECTION_BOTH : upDirection;
                         break;
@@ -243,7 +239,7 @@
             pa = new PendingAnimation(maxDuration);
             mRecentsView.createTaskDismissAnimation(pa, mTaskBeingDragged,
                     true /* animateTaskView */, true /* removeTask */, maxDuration,
-                    false /* dismissingForSplitSelection*/);
+                    false /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */);
 
             mEndDisplacement = -secondaryTaskDimension;
         } else {
@@ -261,7 +257,7 @@
         // Setting this interpolator doesn't affect the visual motion, but is used to determine
         // whether we successfully reached the target state in onDragEnd().
         mCurrentAnimation.getTarget().setInterpolator(currentInterpolator);
-        onUserControlledAnimationCreated(mCurrentAnimation);
+        mTaskViewRecentsTouchContext.onUserControlledAnimationCreated(mCurrentAnimation);
         mCurrentAnimation.getTarget().addListener(this);
         mCurrentAnimation.dispatchOnStart();
         mProgressMultiplier = 1 / mEndDisplacement;
diff --git a/quickstep/src/com/android/launcher3/widget/picker/WidgetCategoryFilter.kt b/quickstep/src/com/android/launcher3/widget/picker/WidgetCategoryFilter.kt
new file mode 100644
index 0000000..69feb4a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/widget/picker/WidgetCategoryFilter.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker
+
+/**
+ * A filter that can be applied on the widgetCategory attribute from appwidget-provider to identify
+ * if the widget can be displayed on a specific widget surface.
+ * - Negative value (e.g. "category_a.inv() and category_b.inv()" excludes the widgets with given
+ *   categories.
+ * - Positive value (e.g. "category_a or category_b" includes widgets with those categories.
+ * - 0 means no filter.
+ */
+class WidgetCategoryFilter(val categoryMask: Int) {
+    /** Applies the [categoryMask] to return if the [widgetCategory] matches. */
+    fun matches(widgetCategory: Int): Boolean {
+        return if (categoryMask > 0) { // inclusion filter
+            (widgetCategory and categoryMask) != 0
+        } else if (categoryMask < 0) { // exclusion filter
+            (widgetCategory and categoryMask) == widgetCategory
+        } else {
+            true // no filter
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index fbc8d6a..7574c7f 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -31,15 +31,20 @@
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
 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.enableGestureNavHorizontalTouchSlop;
 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
+import static com.android.launcher3.Flags.msdlFeedback;
 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;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_GESTURE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_ENTER_DESKTOP_MODE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_EXIT_DESKTOP_MODE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_LEFT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_QUICKSWITCH_RIGHT;
+import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
@@ -95,8 +100,10 @@
 import android.widget.Toast;
 import android.window.DesktopModeFlags;
 import android.window.PictureInPictureSurfaceTransaction;
+import android.window.TransitionInfo;
 import android.window.WindowAnimationState;
 
+import androidx.annotation.CallSuper;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
@@ -111,23 +118,24 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulContainer;
 import com.android.launcher3.taskbar.TaskbarThresholdUtils;
 import com.android.launcher3.taskbar.TaskbarUIController;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.util.WindowBounds;
 import com.android.quickstep.GestureState.GestureEndTarget;
 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
-import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.util.ActiveGestureErrorDetector;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
@@ -149,6 +157,7 @@
 import com.android.quickstep.views.RecentsViewContainer;
 import com.android.quickstep.views.TaskContainer;
 import com.android.quickstep.views.TaskView;
+import com.android.quickstep.views.TaskViewType;
 import com.android.systemui.animation.TransitionAnimator;
 import com.android.systemui.contextualeducation.GestureType;
 import com.android.systemui.shared.recents.model.Task;
@@ -164,6 +173,8 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.shared.startingsurface.SplashScreenExitAnimationUtils;
 
+import com.google.android.msdl.data.model.MSDLToken;
+
 import kotlin.Unit;
 
 import java.util.ArrayList;
@@ -190,6 +201,7 @@
     // Fraction of the scroll and transform animation in which the current task fades out
     private static final float KQS_TASK_FADE_ANIMATION_FRACTION = 0.4f;
 
+    protected final RecentsAnimationDeviceState mDeviceState;
     protected final BaseContainerInterface<STATE, RECENTS_CONTAINER> mContainerInterface;
     protected final InputConsumerProxy mInputConsumerProxy;
     protected final ContextInitListener mContextInitListener;
@@ -206,12 +218,16 @@
     protected MultiStateCallback mStateCallback;
     protected boolean mCanceled;
     private boolean mRecentsViewScrollLinked = false;
+    // The previous task view type before the user quick switches between tasks
+    private TaskViewType mPreviousTaskViewType;
 
     private final Runnable mLauncherOnDestroyCallback = () -> {
         ActiveGestureProtoLogProxy.logLauncherDestroyed();
+        mRecentsView.removeOnScrollChangedListener(mOnRecentsScrollListener);
         mRecentsView = null;
         mContainer = null;
         mStateCallback.clearState(STATE_LAUNCHER_PRESENT);
+        mRecentsAnimationStartCallbacks.clear();
     };
 
     private static int FLAG_COUNT = 0;
@@ -300,8 +316,7 @@
      */
     private static final int LOG_NO_OP_PAGE_INDEX = -1;
 
-    protected final TaskAnimationManager mTaskAnimationManager;
-    protected final RecentsWindowManager mRecentsWindowManager;
+    protected TaskAnimationManager mTaskAnimationManager;
     // Either RectFSpringAnim (if animating home) or ObjectAnimator (from mCurrentShift) otherwise
     private RunningWindowAnim[] mRunningWindowAnim;
     // Possible second animation running at the same time as mRunningWindowAnim
@@ -362,11 +377,15 @@
     @Nullable
     private RemoteAnimationTargets.ReleaseCheck mSwipePipToHomeReleaseCheck = null;
 
-    public AbsSwipeUpHandler(Context context, RecentsAnimationDeviceState deviceState,
+    private final MSDLPlayerWrapper mMSDLPlayerWrapper;
+
+    public AbsSwipeUpHandler(Context context,
             TaskAnimationManager taskAnimationManager, GestureState gestureState,
             long touchTimeMs, boolean continuingLastGesture,
-            InputConsumerController inputConsumer, RecentsWindowManager recentsWindowManager) {
-        super(context, deviceState, gestureState);
+            InputConsumerController inputConsumer,
+            MSDLPlayerWrapper msdlPlayerWrapper) {
+        super(context, gestureState);
+        mDeviceState = RecentsAnimationDeviceState.INSTANCE.get(mContext);
         mContainerInterface = gestureState.getContainerInterface();
         mContextInitListener =
                 mContainerInterface.createActivityInitListener(this::onActivityInit);
@@ -381,7 +400,6 @@
                     endLauncherTransitionController();
                 }, new InputProxyHandlerFactory(mContainerInterface, mGestureState));
         mTaskAnimationManager = taskAnimationManager;
-        mRecentsWindowManager = recentsWindowManager;
         mTouchTimeMs = touchTimeMs;
         mContinuingLastGesture = continuingLastGesture;
 
@@ -392,8 +410,10 @@
         mSplashMainWindowShiftLength = -res
                 .getDimensionPixelSize(R.dimen.starting_surface_exit_animation_window_shift_length);
 
+        mMSDLPlayerWrapper = msdlPlayerWrapper;
+
         initTransitionEndpoints(mRemoteTargetHandles[0].getTaskViewSimulator()
-                .getOrientationState().getLauncherDeviceProfile());
+                .getOrientationState().getLauncherDeviceProfile(gestureState.getDisplayId()));
         initStateCallbacks();
 
         mIsTransientTaskbar = mDp.isTaskbarPresent
@@ -583,7 +603,7 @@
         // as that will set the state as BACKGROUND_APP, overriding the animation to NORMAL.
         if (mGestureState.getEndTarget() != HOME) {
             Runnable initAnimFactory = () -> {
-                mAnimationFactory = mContainerInterface.prepareRecentsUI(mDeviceState,
+                mAnimationFactory = mContainerInterface.prepareRecentsUI(
                         mWasLauncherAlreadyVisible, this::onAnimatorPlaybackControllerCreated);
                 maybeUpdateRecentsAttachedState(false /* animate */);
                 if (mGestureState.getEndTarget() != null) {
@@ -649,12 +669,9 @@
         mGestureState.getContainerInterface().setOnDeferredActivityLaunchCallback(
                 mOnDeferredActivityLaunch);
 
-        mGestureState.runOnceAtState(STATE_END_TARGET_SET,
-                () -> {
-                    mDeviceState.getRotationTouchHelper()
-                            .onEndTargetCalculated(mGestureState.getEndTarget(),
-                                    mContainerInterface);
-                });
+        mGestureState.runOnceAtState(STATE_END_TARGET_SET, () ->
+                RotationTouchHelper.INSTANCE.get(mContext)
+                        .onEndTargetCalculated(mGestureState.getEndTarget(), mContainerInterface));
 
         notifyGestureStarted();
     }
@@ -694,7 +711,11 @@
         if (mRecentsView == null) {
             return;
         }
-        mRecentsView.onGestureAnimationStart(runningTasks, mDeviceState.getRotationTouchHelper());
+        mRecentsView.onGestureAnimationStart(runningTasks);
+        TaskView currentPageTaskView = mRecentsView.getCurrentPageTaskView();
+        if (currentPageTaskView != null) {
+            mPreviousTaskViewType = currentPageTaskView.getType();
+        }
     }
 
     private void launcherFrameDrawn() {
@@ -916,7 +937,7 @@
             TaskView runningTask = mRecentsView.getRunningTaskView();
             TaskView centermostTask = mRecentsView.getTaskViewNearestToCenterOfScreen();
             int centermostTaskFlags = centermostTask == null ? 0
-                    : centermostTask.getTaskContainers().getFirst().getSysUiStatusNavFlags();
+                    : centermostTask.getSysUiStatusNavFlags();
             boolean swipeUpThresholdPassed = windowProgress > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD;
             boolean quickswitchThresholdPassed = centermostTask != runningTask;
 
@@ -939,10 +960,10 @@
 
     @Override
     public void onRecentsAnimationStart(RecentsAnimationController controller,
-            RecentsAnimationTargets targets) {
-        super.onRecentsAnimationStart(controller, targets);
+            RecentsAnimationTargets targets, @Nullable TransitionInfo transitionInfo) {
+        super.onRecentsAnimationStart(controller, targets, transitionInfo);
         if (targets.hasDesktopTasks(mContext)) {
-            mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets);
+            mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets, transitionInfo);
         } else {
             int untrimmedAppCount = mRemoteTargetHandles.length;
             mRemoteTargetHandles = mTargetGluer.assignTargetsForSplitScreen(targets);
@@ -970,7 +991,8 @@
             // both split and non-split
             RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
                     .getOrientationState();
-            DeviceProfile dp = orientationState.getLauncherDeviceProfile();
+            DeviceProfile dp = orientationState.getLauncherDeviceProfile(
+                    mGestureState.getDisplayId());
             if (targets.minimizedHomeBounds != null && primaryTaskTarget != null) {
                 Rect overviewStackBounds = mContainerInterface
                         .getOverviewWindowBounds(targets.minimizedHomeBounds, primaryTaskTarget);
@@ -1084,7 +1106,12 @@
     public void onGestureCancelled() {
         updateDisplacement(0);
         mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
-        handleNormalGestureEnd(0, false, new PointF(), true /* isCancel */);
+        handleNormalGestureEnd(
+                /* endVelocityPxPerMs= */ 0,
+                /* isFling= */ false,
+                /* velocityPxPerMs= */ new PointF(),
+                /* isCancel= */ true,
+                /* horizontalTouchSlopPassed= */ false);
     }
 
     /**
@@ -1093,7 +1120,8 @@
      * @param velocityPxPerMs The x and y components of the velocity when the gesture ends.
      */
     @UiThread
-    public void onGestureEnded(float endVelocityPxPerMs, PointF velocityPxPerMs) {
+    public void onGestureEnded(
+            float endVelocityPxPerMs, PointF velocityPxPerMs, boolean horizontalTouchSlopPassed) {
         float flingThreshold = mContext.getResources()
                 .getDimension(R.dimen.quickstep_fling_threshold_speed);
         boolean isFling = mGestureStarted && !mIsMotionPaused
@@ -1106,7 +1134,11 @@
             mLogDirectionUpOrLeft = velocityPxPerMs.x < 0;
         }
         Runnable handleNormalGestureEndCallback = () -> handleNormalGestureEnd(
-                endVelocityPxPerMs, isFling, velocityPxPerMs, /* isCancel= */ false);
+                endVelocityPxPerMs,
+                isFling,
+                velocityPxPerMs,
+                /* isCancel= */ false,
+                horizontalTouchSlopPassed);
         if (mRecentsView != null) {
             mRecentsView.runOnPageScrollsInitialized(handleNormalGestureEndCallback);
         } else {
@@ -1170,11 +1202,13 @@
         if (endTarget != HOME) {
             InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
         } else {
+            AccessibilityManagerCompat.sendStateEventToTest(mContext, NORMAL_STATE_ORDINAL);
             InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
         }
         if (endTarget != RECENTS) {
             InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
         } else {
+            AccessibilityManagerCompat.sendStateEventToTest(mContext, OVERVIEW_STATE_ORDINAL);
             InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS);
         }
 
@@ -1237,7 +1271,11 @@
     }
 
     private GestureEndTarget calculateEndTarget(
-            PointF velocityPxPerMs, float endVelocityPxPerMs, boolean isFlingY, boolean isCancel) {
+            PointF velocityPxPerMs,
+            float endVelocityPxPerMs,
+            boolean isFlingY,
+            boolean isCancel,
+            boolean horizontalTouchSlopPassed) {
         ActiveGestureProtoLogProxy.logOnCalculateEndTarget(
                 dpiFromPx(velocityPxPerMs.x),
                 dpiFromPx(velocityPxPerMs.y),
@@ -1254,7 +1292,7 @@
         } else if (isFlingY) {
             endTarget = calculateEndTargetForFlingY(velocityPxPerMs, endVelocityPxPerMs);
         } else {
-            endTarget = calculateEndTargetForNonFling(velocityPxPerMs);
+            endTarget = calculateEndTargetForNonFling(velocityPxPerMs, horizontalTouchSlopPassed);
         }
 
         if (mDeviceState.isOverviewDisabled() && endTarget == RECENTS) {
@@ -1292,12 +1330,14 @@
         return willGoToNewTask ? NEW_TASK : HOME;
     }
 
-    private GestureEndTarget calculateEndTargetForNonFling(PointF velocity) {
+    private GestureEndTarget calculateEndTargetForNonFling(
+            PointF velocity, boolean horizontalTouchSlopPassed) {
         final boolean isScrollingToNewTask = isScrollingToNewTask();
 
         // Fully gestural mode.
         final boolean isFlingX = Math.abs(velocity.x) > mContext.getResources()
-                .getDimension(R.dimen.quickstep_fling_threshold_speed);
+                .getDimension(R.dimen.quickstep_fling_threshold_speed)
+                && (!enableGestureNavHorizontalTouchSlop() || horizontalTouchSlopPassed);
         if (isScrollingToNewTask && isFlingX) {
             // Flinging towards new task takes precedence over mIsMotionPaused (which only
             // checks y-velocity).
@@ -1337,11 +1377,15 @@
 
     @UiThread
     private void handleNormalGestureEnd(
-            float endVelocityPxPerMs, boolean isFling, PointF velocityPxPerMs, boolean isCancel) {
+            float endVelocityPxPerMs,
+            boolean isFling,
+            PointF velocityPxPerMs,
+            boolean isCancel,
+            boolean horizontalTouchSlopPassed) {
         long duration = MAX_SWIPE_DURATION;
         float currentShift = mCurrentShift.value;
         final GestureEndTarget endTarget = calculateEndTarget(
-                velocityPxPerMs, endVelocityPxPerMs, isFling, isCancel);
+                velocityPxPerMs, endVelocityPxPerMs, isFling, isCancel, horizontalTouchSlopPassed);
         // Set the state, but don't notify until the animation completes
         mGestureState.setEndTarget(endTarget, false /* isAtomic */);
         mAnimationFactory.setEndTarget(endTarget);
@@ -1350,7 +1394,7 @@
                 && mIsTransientTaskbar
                 && mContainerInterface.getTaskbarController() != null) {
             mContainerInterface.getTaskbarController()
-                    .setUserIsNotGoingHome(endTarget != GestureState.GestureEndTarget.HOME);
+                    .setUserIsNotGoingHome(endTarget != HOME);
         }
 
         float endShift = endTarget.isLauncher ? 1 : 0;
@@ -1390,10 +1434,12 @@
         }
         if (endTarget == HOME) {
             boolean isPinnedTaskbar = DisplayController.isPinnedTaskbar(mContext);
+            boolean isNotInDesktop =  !DisplayController.isInDesktopMode(mContext);
             duration = mContainer != null && mContainer.getDeviceProfile().isTaskbarPresent
-                    ? QuickstepTransitionManager.getTaskbarToHomeDuration(isPinnedTaskbar)
+                    ? QuickstepTransitionManager.getTaskbarToHomeDuration(
+                    isPinnedTaskbar && isNotInDesktop)
                     : StaggeredWorkspaceAnim.DURATION_MS;
-            ContextualEduStatsManager.INSTANCE.get(mContext).updateEduStats(
+            SystemUiProxy.INSTANCE.get(mContext).updateContextualEduStats(
                     mGestureState.isTrackpadGesture(), GestureType.HOME);
         } else if (endTarget == RECENTS) {
             if (mRecentsView != null) {
@@ -1419,7 +1465,7 @@
                 if (!mGestureState.isHandlingAtomicEvent() || isScrolling) {
                     duration = Math.max(duration, mRecentsView.getScroller().getDuration());
                 }
-                ContextualEduStatsManager.INSTANCE.get(mContext).updateEduStats(
+                SystemUiProxy.INSTANCE.get(mContext).updateContextualEduStats(
                         mGestureState.isTrackpadGesture(), GestureType.OVERVIEW);
             }
         } else if (endTarget == LAST_TASK && mRecentsView != null
@@ -1472,27 +1518,35 @@
                 startShift, endShift, duration, interpolator, endTarget, velocityPxPerMs);
     }
 
-    private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTask) {
+    private void doLogGesture(GestureEndTarget endTarget, @Nullable TaskView targetTaskView) {
         if (mDp == null || !mDp.isGestureMode) {
             // We probably never received an animation controller, skip logging.
             return;
         }
 
-        StatsLogManager.EventEnum event;
+        ArrayList<StatsLogManager.EventEnum> events = new ArrayList<>();
         switch (endTarget) {
             case HOME:
-                event = LAUNCHER_HOME_GESTURE;
+                events.add(LAUNCHER_HOME_GESTURE);
                 break;
             case RECENTS:
-                event = LAUNCHER_OVERVIEW_GESTURE;
+                events.add(LAUNCHER_OVERVIEW_GESTURE);
                 break;
             case LAST_TASK:
             case NEW_TASK:
-                event = mLogDirectionUpOrLeft ? LAUNCHER_QUICKSWITCH_LEFT
-                        : LAUNCHER_QUICKSWITCH_RIGHT;
+                events.add(mLogDirectionUpOrLeft ? LAUNCHER_QUICKSWITCH_LEFT
+                        : LAUNCHER_QUICKSWITCH_RIGHT);
+                if (targetTaskView != null && DesktopModeStatus.canEnterDesktopMode(mContext)
+                        && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH.isTrue()) {
+                    if (targetTaskView.getType() == TaskViewType.DESKTOP) {
+                        events.add(LAUNCHER_QUICKSWITCH_ENTER_DESKTOP_MODE);
+                    } else if (mPreviousTaskViewType == TaskViewType.DESKTOP) {
+                        events.add(LAUNCHER_QUICKSWITCH_EXIT_DESKTOP_MODE);
+                    }
+                }
                 break;
             default:
-                event = IGNORE;
+                events.add(IGNORE);
         }
         StatsLogger logger = StatsLogManager.newInstance(
                         mContainer != null ? mContainer.asContext() : mContext).logger()
@@ -1501,15 +1555,15 @@
                 .withInputType(mGestureState.isTrackpadGesture()
                         ? SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TRACKPAD
                         : SysUiStatsLog.LAUNCHER_UICHANGED__INPUT_TYPE__TOUCH);
-        if (targetTask != null) {
-            logger.withItemInfo(targetTask.getFirstItemInfo());
+        if (targetTaskView != null) {
+            logger.withItemInfo(targetTaskView.getItemInfo());
         }
 
         int pageIndex = endTarget == LAST_TASK || mRecentsView == null
                 ? LOG_NO_OP_PAGE_INDEX
                 : mRecentsView.getNextPage();
         logger.withRank(pageIndex);
-        logger.log(event);
+        events.forEach(logger::log);
     }
 
     protected abstract HomeAnimationFactory createHomeAnimationFactory(
@@ -1563,9 +1617,27 @@
             if (mParallelRunningAnim != null) {
                 mParallelRunningAnim.addListener(new AnimatorListenerAdapter() {
                     @Override
+                    public void onAnimationStart(Animator animation) {
+                        if (DisplayController.isInDesktopMode(mContext)
+                                && mGestureState.getEndTarget() == HOME) {
+                            // Set launcher animation started, so we don't notify from
+                            // desktop visibility controller
+                            DesktopVisibilityController.INSTANCE.get(
+                                    mContext).setLauncherAnimationRunning(true);
+                        }
+                    }
+
+                    @Override
                     public void onAnimationEnd(Animator animation) {
                         mParallelRunningAnim = null;
                         mStateCallback.setStateOnUiThread(STATE_PARALLEL_ANIM_FINISHED);
+                        // Swipe to home animation finished, notify DesktopVisibilityController
+                        // to recreate Taskbar
+                        if (DisplayController.isInDesktopMode(mContext)
+                                && mGestureState.getEndTarget() == HOME) {
+                            DesktopVisibilityController.INSTANCE.get(
+                                    mContext).onLauncherAnimationFromDesktopEnd();
+                        }
                     }
                 });
                 mParallelRunningAnim.start();
@@ -1652,7 +1724,6 @@
                 if (mHandOffAnimationToHome) {
                     handOffAnimation(velocityPxPerMs);
                 }
-
                 windowAnim[0].addAnimatorListener(new AnimationSuccessListener() {
                     @Override
                     public void onAnimationSuccess(Animator animator) {
@@ -1683,7 +1754,7 @@
 
             if (mRecentsView != null) {
                 mRecentsView.onPrepareGestureEndAnimation(null, mGestureState.getEndTarget(),
-                        getRemoteTaskViewSimulators());
+                        mRemoteTargetHandles);
             }
         } else {
             AnimatorSet animatorSet = new AnimatorSet();
@@ -1727,7 +1798,7 @@
                 mRecentsView.onPrepareGestureEndAnimation(
                         mGestureState.isHandlingAtomicEvent() ? null : animatorSet,
                         mGestureState.getEndTarget(),
-                        getRemoteTaskViewSimulators());
+                        mRemoteTargetHandles);
             }
             animatorSet.setDuration(duration).setInterpolator(interpolator);
             animatorSet.start();
@@ -1800,9 +1871,7 @@
 
         final Rect hotseatKeepClearArea = getKeepClearAreaForHotseat();
         final Rect destinationBounds = SystemUiProxy.INSTANCE.get(mContext)
-                .startSwipePipToHome(taskInfo.topActivity,
-                        taskInfo.topActivityInfo,
-                        runningTaskTarget.taskInfo.pictureInPictureParams,
+                .startSwipePipToHome(taskInfo,
                         homeRotation,
                         hotseatKeepClearArea);
         if (destinationBounds == null) {
@@ -1957,7 +2026,7 @@
                 }
                 // Make sure recents is in its final state
                 maybeUpdateRecentsAttachedState(false);
-                mContainerInterface.onSwipeUpToHomeComplete(mDeviceState);
+                mContainerInterface.onSwipeUpToHomeComplete();
             }
         });
         if (mRecentsAnimationTargets != null) {
@@ -1995,6 +2064,7 @@
     @UiThread
     private void startNewTask() {
         TaskView taskToLaunch = mRecentsView == null ? null : mRecentsView.getNextPageTaskView();
+        doLogGesture(NEW_TASK, taskToLaunch);
         startNewTask(success -> {
             if (!success) {
                 reset();
@@ -2003,7 +2073,6 @@
                 endLauncherTransitionController();
                 updateSysUiFlags(1 /* windowProgress == overview */);
             }
-            doLogGesture(NEW_TASK, taskToLaunch);
         });
     }
 
@@ -2013,7 +2082,7 @@
      * specific edge case: if we switch from A to B, and back to A before B appears, we need to
      * start A again to ensure it stays on top.
      */
-    @androidx.annotation.CallSuper
+    @CallSuper
     protected void onRestartPreviouslyAppearedTask() {
         // Finish the controller here, since we won't get onTaskAppeared() for a task that already
         // appeared.
@@ -2272,7 +2341,11 @@
     }
 
     protected void performHapticFeedback() {
-        VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+        if (msdlFeedback()) {
+            mMSDLPlayerWrapper.playToken(MSDLToken.SWIPE_THRESHOLD_INDICATOR);
+        } else {
+            VibratorWrapper.INSTANCE.get(mContext).vibrate(OVERVIEW_HAPTIC);
+        }
     }
 
     public Consumer<MotionEvent> getRecentsViewDispatcher(float navbarRotation) {
@@ -2341,9 +2414,6 @@
                 ActiveGestureLog.CompoundString nextTaskLog =
                         ActiveGestureLog.CompoundString.newEmptyString();
                 for (TaskContainer container : nextTask.getTaskContainers()) {
-                    if (container == null) {
-                        continue;
-                    }
                     nextTaskLog.append("[id: %d, pkg: %s] | ",
                             container.getTask().key.id,
                             container.getTask().key.getPackageName());
@@ -2428,7 +2498,8 @@
     }
 
     @Override
-    public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
+    public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets,
+            @Nullable TransitionInfo transitionInfo) {
         if (mRecentsAnimationController == null) {
             return;
         }
@@ -2657,9 +2728,7 @@
         }
 
         float scrollOffset = Math.abs(mRecentsView.getScrollOffset(mRecentsView.getCurrentPage()));
-        Rect carouselTaskSize = enableGridOnlyOverview()
-                ? mRecentsView.getLastComputedCarouselTaskSize()
-                : mRecentsView.getLastComputedTaskSize();
+        Rect carouselTaskSize = mRecentsView.getLastComputedTaskSize();
         int maxScrollOffset = mRecentsView.getPagedOrientationHandler().getPrimaryValue(
                 carouselTaskSize.width(), carouselTaskSize.height());
         maxScrollOffset += mRecentsView.getPageSpacing();
diff --git a/quickstep/src/com/android/quickstep/AllAppsActionManager.kt b/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
index 6fd68d5..b807a4b 100644
--- a/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
+++ b/quickstep/src/com/android/quickstep/AllAppsActionManager.kt
@@ -21,10 +21,16 @@
 import android.app.RemoteAction
 import android.content.Context
 import android.graphics.drawable.Icon
+import android.provider.Settings
+import android.provider.Settings.Secure.USER_SETUP_COMPLETE
 import android.view.accessibility.AccessibilityManager
 import com.android.launcher3.R
+import com.android.launcher3.util.SettingsCache
+import com.android.launcher3.util.SettingsCache.OnChangeListener
 import java.util.concurrent.Executor
 
+private val USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(USER_SETUP_COMPLETE)
+
 /**
  * Registers a [RemoteAction] for toggling All Apps if needed.
  *
@@ -38,6 +44,12 @@
     private val createAllAppsPendingIntent: () -> PendingIntent,
 ) {
 
+    private val onSettingsChangeListener = OnChangeListener { v -> isUserSetupComplete = v }
+
+    init {
+        SettingsCache.INSTANCE[context].register(USER_SETUP_COMPLETE_URI, onSettingsChangeListener)
+    }
+
     /** `true` if home and overview are the same Activity. */
     var isHomeAndOverviewSame = false
         set(value) {
@@ -52,12 +64,27 @@
             updateSystemAction()
         }
 
+    /** `true` if the setup UI is visible. */
+    var isSetupUiVisible = false
+        set(value) {
+            field = value
+            updateSystemAction()
+        }
+
+    private var isUserSetupComplete =
+        SettingsCache.INSTANCE[context].getValue(USER_SETUP_COMPLETE_URI, 0)
+        set(value) {
+            field = value
+            updateSystemAction()
+        }
+
     /** `true` if the action should be registered. */
     var isActionRegistered = false
         private set
 
     private fun updateSystemAction() {
-        val shouldRegisterAction = isHomeAndOverviewSame || isTaskbarPresent
+        val isInSetupFlow = isSetupUiVisible || !isUserSetupComplete
+        val shouldRegisterAction = (isHomeAndOverviewSame || isTaskbarPresent) && !isInSetupFlow
         if (isActionRegistered == shouldRegisterAction) return
         isActionRegistered = shouldRegisterAction
 
@@ -84,8 +111,10 @@
         isActionRegistered = false
         context
             .getSystemService(AccessibilityManager::class.java)
-            ?.unregisterSystemAction(
-                GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS,
-            )
+            ?.unregisterSystemAction(GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS)
+        SettingsCache.INSTANCE[context].unregister(
+            USER_SETUP_COMPLETE_URI,
+            onSettingsChangeListener,
+        )
     }
 }
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 1e755eb..549c2f8 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -86,8 +86,8 @@
         if (endTarget != null) {
             // We were on our way to this state when we got canceled, end there instead.
             startState = stateFromGestureEndTarget(endTarget);
-            DesktopVisibilityController controller = getDesktopVisibilityController();
-            if (controller != null && controller.areDesktopTasksVisible()
+            if (DesktopVisibilityController.INSTANCE.get(activity)
+                    .isInDesktopModeAndNotInOverview(activity.getDisplayId())
                     && endTarget == LAST_TASK) {
                 // When we are cancelling the transition and going back to last task, move to
                 // rest state instead when desktop tasks are visible.
diff --git a/quickstep/src/com/android/quickstep/BaseContainerInterface.java b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
index 2164bc2..c64067a 100644
--- a/quickstep/src/com/android/quickstep/BaseContainerInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseContainerInterface.java
@@ -131,7 +131,7 @@
     }
 
     public abstract BaseContainerInterface.AnimationFactory prepareRecentsUI(
-            RecentsAnimationDeviceState deviceState, boolean activityVisible,
+            boolean activityVisible,
             Consumer<AnimatorControllerWithResistance> callback);
 
     public abstract ContextInitListener createActivityInitListener(
@@ -151,11 +151,10 @@
 
     public abstract void onLaunchTaskFailed();
 
-    public abstract void onExitOverview(RotationTouchHelper deviceState,
-            Runnable exitRunnable);
+    public abstract void onExitOverview(Runnable exitRunnable);
 
     /** Called when the animation to home has fully settled. */
-    public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) {}
+    public void onSwipeUpToHomeComplete() {}
 
     /**
      * Sets a callback to be run when an activity launch happens while launcher is not yet resumed.
@@ -179,13 +178,6 @@
         mOnInitBackgroundStateUICallback = callback;
     }
 
-    @Nullable
-    public DesktopVisibilityController getDesktopVisibilityController() {
-        CONTAINER_TYPE container = getCreatedContainer();
-
-        return container == null ? null : container.getDesktopVisibilityController();
-    }
-
     /**
      * Called when the gesture ends and the animation starts towards the given target. Used to add
      * an optional additional animation with the same duration.
@@ -241,8 +233,9 @@
         if (endTarget != null) {
             // We were on our way to this state when we got canceled, end there instead.
             startState = stateFromGestureEndTarget(endTarget);
-            DesktopVisibilityController controller = getDesktopVisibilityController();
-            if (controller != null && controller.areDesktopTasksVisible()
+            final var context = recentsView.getContext();
+            if (DesktopVisibilityController.INSTANCE.get(context)
+                    .isInDesktopModeAndNotInOverview(context.getDisplayId())
                     && endTarget == LAST_TASK) {
                 // When we are cancelling the transition and going back to last task, move to
                 // rest state instead when desktop tasks are visible.
@@ -260,11 +253,7 @@
     public final void calculateTaskSize(Context context, DeviceProfile dp, Rect outRect,
             RecentsPagedOrientationHandler orientationHandler) {
         if (dp.isTablet) {
-            if (Flags.enableGridOnlyOverview()) {
-                calculateGridTaskSize(context, dp, outRect, orientationHandler);
-            } else {
-                calculateFocusTaskSize(context, dp, outRect);
-            }
+            calculateLargeTileSize(context, dp, outRect);
         } else {
             Resources res = context.getResources();
             float maxScale = res.getFloat(R.dimen.overview_max_scale);
@@ -285,24 +274,7 @@
         }
     }
 
-    /**
-     * Calculates the taskView size for carousel during app to overview animation on tablets.
-     */
-    public final void calculateCarouselTaskSize(Context context, DeviceProfile dp, Rect outRect,
-            RecentsPagedOrientationHandler orientationHandler) {
-        if (dp.isTablet && dp.isGestureMode) {
-            Resources res = context.getResources();
-            float minScale = res.getFloat(R.dimen.overview_carousel_min_scale);
-            Rect gridRect = new Rect();
-            calculateGridSize(dp, context, gridRect);
-            calculateTaskSizeInternal(context, dp, gridRect, minScale, Gravity.CENTER | Gravity.TOP,
-                    outRect);
-        } else {
-            calculateTaskSize(context, dp, outRect, orientationHandler);
-        }
-    }
-
-    private void calculateFocusTaskSize(Context context, DeviceProfile dp, Rect outRect) {
+    private void calculateLargeTileSize(Context context, DeviceProfile dp, Rect outRect) {
         Resources res = context.getResources();
         float maxScale = res.getFloat(R.dimen.overview_max_scale);
         Rect gridRect = new Rect();
@@ -390,12 +362,6 @@
         Rect insets = dp.getInsets();
         int topMargin = dp.overviewTaskThumbnailTopMarginPx;
         int bottomMargin = dp.getOverviewActionsClaimedSpace();
-        if (dp.isTaskbarPresent && Flags.enableGridOnlyOverview()) {
-            topMargin += context.getResources().getDimensionPixelSize(
-                    R.dimen.overview_top_margin_grid_only);
-            bottomMargin += context.getResources().getDimensionPixelSize(
-                    R.dimen.overview_bottom_margin_grid_only);
-        }
         int sideMargin = dp.overviewGridSideMargin;
 
         outRect.set(0, 0, dp.widthPx, dp.heightPx);
@@ -410,11 +376,7 @@
             RecentsPagedOrientationHandler orientationHandler) {
         Resources res = context.getResources();
         Rect potentialTaskRect = new Rect();
-        if (Flags.enableGridOnlyOverview()) {
-            calculateGridSize(dp, context, potentialTaskRect);
-        } else {
-            calculateFocusTaskSize(context, dp, potentialTaskRect);
-        }
+        calculateLargeTileSize(context, dp, potentialTaskRect);
 
         float rowHeight = (potentialTaskRect.height() + dp.overviewTaskThumbnailTopMarginPx
                 - dp.overviewRowSpacing) / 2f;
diff --git a/quickstep/src/com/android/quickstep/DesktopFullscreenDrawParams.kt b/quickstep/src/com/android/quickstep/DesktopFullscreenDrawParams.kt
index bafb0b2..444e77d 100644
--- a/quickstep/src/com/android/quickstep/DesktopFullscreenDrawParams.kt
+++ b/quickstep/src/com/android/quickstep/DesktopFullscreenDrawParams.kt
@@ -17,7 +17,7 @@
 package com.android.quickstep
 
 import android.content.Context
-import com.android.systemui.shared.system.QuickStepContract
+import com.android.launcher3.R
 
 // DesktopTaskView thumbnail's corner radius is independent of fullscreenProgress.
 open class DesktopFullscreenDrawParams
@@ -28,6 +28,6 @@
         // computeCornerRadius is used as cornerRadiusProvider, so
         // QuickStepContract::getWindowCornerRadius can be mocked properly.
         private fun computeCornerRadius(context: Context): Float =
-            QuickStepContract.getWindowCornerRadius(context)
+            context.resources.getDimension(R.dimen.desktop_windowing_freeform_rounded_corner_radius)
     }
 }
diff --git a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
index 94f4920..914855b 100644
--- a/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
+++ b/quickstep/src/com/android/quickstep/DesktopSystemShortcut.kt
@@ -17,6 +17,7 @@
 package com.android.quickstep
 
 import android.view.View
+import com.android.internal.jank.Cuj
 import com.android.launcher3.AbstractFloatingViewHelper
 import com.android.launcher3.R
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent
@@ -24,6 +25,8 @@
 import com.android.quickstep.views.RecentsView
 import com.android.quickstep.views.RecentsViewContainer
 import com.android.quickstep.views.TaskContainer
+import com.android.systemui.shared.system.InteractionJankMonitorWrapper
+import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
 
@@ -31,7 +34,7 @@
 class DesktopSystemShortcut(
     container: RecentsViewContainer,
     private val taskContainer: TaskContainer,
-    abstractFloatingViewHelper: AbstractFloatingViewHelper
+    abstractFloatingViewHelper: AbstractFloatingViewHelper,
 ) :
     SystemShortcut<RecentsViewContainer>(
         R.drawable.ic_desktop,
@@ -39,15 +42,17 @@
         container,
         taskContainer.itemInfo,
         taskContainer.taskView,
-        abstractFloatingViewHelper
+        abstractFloatingViewHelper,
     ) {
     override fun onClick(view: View) {
+        InteractionJankMonitorWrapper.begin(view, Cuj.CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU)
         dismissTaskMenuView()
         val recentsView = mTarget.getOverviewPanel<RecentsView<*, *>>()
         recentsView.moveTaskToDesktop(
             taskContainer,
-            DesktopModeTransitionSource.APP_FROM_OVERVIEW
+            DesktopModeTransitionSource.APP_FROM_OVERVIEW,
         ) {
+            InteractionJankMonitorWrapper.end(Cuj.CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU)
             mTarget.statsLogManager
                 .logger()
                 .withItemInfo(taskContainer.itemInfo)
@@ -64,18 +69,33 @@
             return object : TaskShortcutFactory {
                 override fun getShortcuts(
                     container: RecentsViewContainer,
-                    taskContainer: TaskContainer
+                    taskContainer: TaskContainer,
                 ): List<DesktopSystemShortcut>? {
-                    return if (!DesktopModeStatus.canEnterDesktopMode(container.asContext())) null
-                    else if (!taskContainer.task.isDockable) null
-                    else
-                        listOf(
-                            DesktopSystemShortcut(
-                                container,
-                                taskContainer,
-                                abstractFloatingViewHelper
+                    val context = container.asContext()
+                    val taskKey = taskContainer.task.key
+                    val desktopModeCompatPolicy = DesktopModeCompatPolicy(context)
+                    return when {
+                        !DesktopModeStatus.canEnterDesktopMode(context) -> null
+
+                        desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+                            taskKey.baseActivity?.packageName,
+                            taskKey.numActivities,
+                            taskKey.isTopActivityNoDisplay,
+                            taskKey.isActivityStackTransparent,
+                        ) -> null
+
+                        !taskContainer.task.isDockable -> null
+
+                        else -> {
+                            listOf(
+                                DesktopSystemShortcut(
+                                    container,
+                                    taskContainer,
+                                    abstractFloatingViewHelper,
+                                )
                             )
-                        )
+                        }
+                    }
                 }
 
                 override fun showForGroupedTask() = true
diff --git a/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt b/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
index f610014..9d7f704 100644
--- a/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
+++ b/quickstep/src/com/android/quickstep/DeviceConfigWrapper.kt
@@ -152,6 +152,9 @@
         writer.println("$prefix\tanimateLpnh=$animateLpnh")
         writer.println("$prefix\tshrinkNavHandleOnPress=$shrinkNavHandleOnPress")
         writer.println("$prefix\tlpnhTimeoutMs=$lpnhTimeoutMs")
+        writer.println("$prefix\tenableLpnhTwoStages=$enableLpnhTwoStages")
+        writer.println("$prefix\ttwoStageDurationPercentage=$twoStageDurationPercentage")
+        writer.println("$prefix\ttwoStageSlopPercentage=$twoStageSlopPercentage")
         writer.println("$prefix\tenableLongPressNavHandle=$enableLongPressNavHandle")
         writer.println("$prefix\tenableSearchHapticHint=$enableSearchHapticHint")
         writer.println("$prefix\tenableSearchHapticCommit=$enableSearchHapticCommit")
diff --git a/quickstep/src/com/android/quickstep/DisplayModel.kt b/quickstep/src/com/android/quickstep/DisplayModel.kt
new file mode 100644
index 0000000..ac94375
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/DisplayModel.kt
@@ -0,0 +1,119 @@
+/*
+ * 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 android.content.Context
+import android.hardware.display.DisplayManager
+import android.util.Log
+import android.util.SparseArray
+import android.view.Display
+import androidx.core.util.valueIterator
+import com.android.launcher3.util.Executors
+import com.android.quickstep.DisplayModel.DisplayResource
+import java.io.PrintWriter
+
+/** data model for managing resources with lifecycles that match that of the connected display */
+abstract class DisplayModel<RESOURCE_TYPE : DisplayResource>(val context: Context) {
+
+    companion object {
+        private const val TAG = "DisplayModel"
+        private const val DEBUG = false
+    }
+
+    private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+    protected val displayResourceArray = SparseArray<RESOURCE_TYPE>()
+
+    private val displayListener: DisplayManager.DisplayListener =
+        (object : DisplayManager.DisplayListener {
+            override fun onDisplayAdded(displayId: Int) {
+                if (DEBUG) Log.d(TAG, "onDisplayAdded: displayId=$displayId")
+                storeDisplayResource(displayId)
+            }
+
+            override fun onDisplayRemoved(displayId: Int) {
+                if (DEBUG) Log.d(TAG, "onDisplayRemoved: displayId=$displayId")
+                deleteDisplayResource(displayId)
+            }
+
+            override fun onDisplayChanged(displayId: Int) {
+                if (DEBUG) Log.d(TAG, "onDisplayChanged: displayId=$displayId")
+            }
+        })
+
+    protected abstract fun createDisplayResource(display: Display): RESOURCE_TYPE
+
+    protected fun registerDisplayListener() {
+        displayManager.registerDisplayListener(displayListener, Executors.MAIN_EXECUTOR.handler)
+        // In the scenario where displays were added before this display listener was
+        // registered, we should store the DisplayResources for those displays directly.
+        displayManager.displays
+            .filter { getDisplayResource(it.displayId) == null }
+            .forEach { storeDisplayResource(it.displayId) }
+    }
+
+    fun destroy() {
+        displayResourceArray.valueIterator().forEach { displayResource ->
+            displayResource.cleanup()
+        }
+        displayResourceArray.clear()
+        displayManager.unregisterDisplayListener(displayListener)
+    }
+
+    fun getDisplayResource(displayId: Int): RESOURCE_TYPE? {
+        if (DEBUG) Log.d(TAG, "get: displayId=$displayId")
+        return displayResourceArray[displayId]
+    }
+
+    fun deleteDisplayResource(displayId: Int) {
+        if (DEBUG) Log.d(TAG, "delete: displayId=$displayId")
+        getDisplayResource(displayId)?.cleanup()
+        displayResourceArray.remove(displayId)
+    }
+
+    fun storeDisplayResource(displayId: Int) {
+        if (DEBUG) Log.d(TAG, "store: displayId=$displayId")
+        getDisplayResource(displayId)?.let {
+            return
+        }
+        val display = displayManager.getDisplay(displayId)
+        if (display == null) {
+            if (DEBUG)
+                Log.w(
+                    TAG,
+                    "storeDisplayResource: could not create display for displayId=$displayId",
+                    Exception(),
+                )
+            return
+        }
+        displayResourceArray[displayId] = createDisplayResource(display)
+    }
+
+    fun dump(prefix: String, writer: PrintWriter) {
+        writer.println("${prefix}${this::class.simpleName}: display resources=[")
+
+        displayResourceArray.valueIterator().forEach { displayResource ->
+            displayResource.dump("${prefix}\t", writer)
+        }
+        writer.println("${prefix}]")
+    }
+
+    abstract class DisplayResource {
+        abstract fun cleanup()
+
+        abstract fun dump(prefix: String, writer: PrintWriter)
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/ExternalDisplaySystemShortcut.kt b/quickstep/src/com/android/quickstep/ExternalDisplaySystemShortcut.kt
index 46c4f36..f97cf9c 100644
--- a/quickstep/src/com/android/quickstep/ExternalDisplaySystemShortcut.kt
+++ b/quickstep/src/com/android/quickstep/ExternalDisplaySystemShortcut.kt
@@ -25,6 +25,7 @@
 import com.android.quickstep.views.RecentsViewContainer
 import com.android.quickstep.views.TaskContainer
 import com.android.window.flags.Flags
+import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 
 /** A menu item that allows the user to move the current app into external display. */
@@ -66,18 +67,31 @@
                     container: RecentsViewContainer,
                     taskContainer: TaskContainer,
                 ): List<ExternalDisplaySystemShortcut>? {
-                    return if (
-                        DesktopModeStatus.canEnterDesktopMode(container.asContext()) &&
-                            Flags.moveToExternalDisplayShortcut()
-                    )
-                        listOf(
-                            ExternalDisplaySystemShortcut(
-                                container,
-                                abstractFloatingViewHelper,
-                                taskContainer,
+                    val context = container.asContext()
+                    val taskKey = taskContainer.task.key
+                    val desktopModeCompatPolicy = DesktopModeCompatPolicy(context)
+                    return when {
+                        !DesktopModeStatus.canEnterDesktopMode(context) -> null
+
+                        !Flags.moveToExternalDisplayShortcut() -> null
+
+                        desktopModeCompatPolicy.isTopActivityExemptFromDesktopWindowing(
+                            taskKey.baseActivity?.packageName,
+                            taskKey.numActivities,
+                            taskKey.isTopActivityNoDisplay,
+                            taskKey.isActivityStackTransparent,
+                        ) -> null
+
+                        else -> {
+                            listOf(
+                                ExternalDisplaySystemShortcut(
+                                    container,
+                                    abstractFloatingViewHelper,
+                                    taskContainer,
+                                )
                             )
-                        )
-                    else null
+                        }
+                    }
                 }
 
                 override fun showForGroupedTask() = true
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index b787399..d8e0296 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -79,9 +79,9 @@
 
     /** 6 */
     @Override
-    public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
+    public AnimationFactory prepareRecentsUI(
             boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback) {
-        notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
+        notifyRecentsOfOrientation();
         DefaultAnimationFactory factory = new DefaultAnimationFactory(callback);
         factory.initBackgroundStateUI();
         return factory;
@@ -142,12 +142,12 @@
     }
 
     @Override
-    public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
+    public void onExitOverview(Runnable exitRunnable) {
         final StateManager<RecentsState, RecentsActivity> stateManager =
                 getCreatedContainer().getStateManager();
         if (stateManager.getState() == HOME) {
             exitRunnable.run();
-            notifyRecentsOfOrientation(deviceState);
+            notifyRecentsOfOrientation();
             return;
         }
 
@@ -158,7 +158,7 @@
                         // Are we going from Recents to Workspace?
                         if (toState == HOME) {
                             exitRunnable.run();
-                            notifyRecentsOfOrientation(deviceState);
+                            notifyRecentsOfOrientation();
                             stateManager.removeStateListener(this);
                         }
                     }
@@ -197,11 +197,9 @@
         }
     }
 
-    private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) {
+    private void notifyRecentsOfOrientation() {
         // reset layout on swipe to home
-        RecentsView recentsView = getCreatedContainer().getOverviewPanel();
-        recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
-                rotationTouchHelper.getDisplayRotation());
+        getCreatedContainer().getOverviewPanel().reapplyActiveRotation();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 9b56fd4..7d8a53d 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -50,6 +50,7 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
+import android.window.TransitionInfo;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -62,6 +63,7 @@
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.util.RectFSpringAnim;
@@ -100,11 +102,12 @@
 
     private boolean mAppCanEnterPip;
 
-    public FallbackSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
+    public FallbackSwipeHandler(Context context,
             TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
-            boolean continuingLastGesture, InputConsumerController inputConsumer) {
-        super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
-                continuingLastGesture, inputConsumer, null);
+            boolean continuingLastGesture, InputConsumerController inputConsumer,
+            MSDLPlayerWrapper msdlPlayerWrapper) {
+        super(context, taskAnimationManager, gestureState, touchTimeMs,
+                continuingLastGesture, inputConsumer, msdlPlayerWrapper);
 
         mRunningOverHome = mGestureState.getRunningTask() != null
                 && mGestureState.getRunningTask().isHomeTask();
@@ -170,14 +173,15 @@
     }
 
     @Override
-    public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets) {
+    public void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTargets,
+            @Nullable TransitionInfo transitionInfo) {
         if (mActiveAnimationFactory != null && mActiveAnimationFactory.handleHomeTaskAppeared(
                 appearedTaskTargets)) {
             mActiveAnimationFactory = null;
             return;
         }
 
-        super.onTasksAppeared(appearedTaskTargets);
+        super.onTasksAppeared(appearedTaskTargets, transitionInfo);
     }
 
     @Override
@@ -214,8 +218,7 @@
         if (mRunningOverHome) {
             if (DisplayController.getNavigationMode(mContext).hasGestures) {
                 mRecentsView.onGestureAnimationStartOnHome(
-                        mGestureState.getRunningTask().getPlaceholderTasks(),
-                        mDeviceState.getRotationTouchHelper());
+                        mGestureState.getRunningTask().getPlaceholderTasks());
             }
         } else {
             super.notifyGestureAnimationStartToRecents();
diff --git a/quickstep/src/com/android/quickstep/FallbackWindowInterface.java b/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
index 832c093..35630ef 100644
--- a/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackWindowInterface.java
@@ -51,33 +51,13 @@
  */
 public final class FallbackWindowInterface extends BaseWindowInterface{
 
-    private static FallbackWindowInterface INSTANCE;
-
     private final RecentsWindowManager mRecentsWindowManager;
 
-    /**
-     * This is only null before init() or after destroy()
-     */
-    @Nullable
-    public static FallbackWindowInterface getInstance(){
-        return INSTANCE;
-    }
-
-    public static void init(RecentsWindowManager recentsWindowManager) {
-        if (INSTANCE == null) {
-            INSTANCE = new FallbackWindowInterface(recentsWindowManager);
-        }
-    }
-
-    private FallbackWindowInterface(RecentsWindowManager recentsWindowManager) {
+    public FallbackWindowInterface(RecentsWindowManager recentsWindowManager) {
         super(DEFAULT, BACKGROUND_APP);
         mRecentsWindowManager = recentsWindowManager;
     }
 
-    public void destroy() {
-        INSTANCE = null;
-    }
-
     /** 2 */
     @Override
     public int getSwipeUpDestinationAndLength(DeviceProfile dp, Context context, Rect outRect,
@@ -100,10 +80,9 @@
 
     /** 6 */
     @Override
-    public BaseWindowInterface.AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState
-            deviceState, boolean activityVisible,
+    public BaseWindowInterface.AnimationFactory prepareRecentsUI(boolean activityVisible,
             Consumer<AnimatorControllerWithResistance> callback) {
-        notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
+        notifyRecentsOfOrientation();
         BaseWindowInterface.DefaultAnimationFactory factory =
                 new BaseWindowInterface.DefaultAnimationFactory(callback);
         factory.initBackgroundStateUI();
@@ -173,12 +152,12 @@
     }
 
     @Override
-    public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
+    public void onExitOverview(Runnable exitRunnable) {
         final StateManager<RecentsState, RecentsWindowManager> stateManager =
                 getCreatedContainer().getStateManager();
         if (stateManager.getState() == HOME) {
             exitRunnable.run();
-            notifyRecentsOfOrientation(deviceState);
+            notifyRecentsOfOrientation();
             return;
         }
 
@@ -189,7 +168,7 @@
                         // Are we going from Recents to Workspace?
                         if (toState == HOME) {
                             exitRunnable.run();
-                            notifyRecentsOfOrientation(deviceState);
+                            notifyRecentsOfOrientation();
                             stateManager.removeStateListener(this);
                         }
                     }
@@ -228,11 +207,9 @@
         }
     }
 
-    private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) {
+    private void notifyRecentsOfOrientation() {
         // reset layout on swipe to home
-        RecentsView recentsView = getCreatedContainer().getOverviewPanel();
-        recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
-                rotationTouchHelper.getDisplayRotation());
+        ((RecentsView) getCreatedContainer().getOverviewPanel()).reapplyActiveRotation();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/FocusState.kt b/quickstep/src/com/android/quickstep/FocusState.kt
index ba3991f..7c6aa5b 100644
--- a/quickstep/src/com/android/quickstep/FocusState.kt
+++ b/quickstep/src/com/android/quickstep/FocusState.kt
@@ -27,7 +27,10 @@
 class FocusState {
 
     var focusedDisplayId = DEFAULT_DISPLAY
-        private set
+        private set(value) {
+            field = value
+            listeners.forEach { it.onFocusedDisplayChanged(value) }
+        }
 
     private var listeners = mutableSetOf<FocusChangeListener>()
 
@@ -40,9 +43,7 @@
             transitions?.setFocusTransitionListener(
                 object : Stub() {
                     override fun onFocusedDisplayChanged(displayId: Int) {
-                        Executors.MAIN_EXECUTOR.execute {
-                            listeners.forEach { it.onFocusedDisplayChanged(displayId) }
-                        }
+                        Executors.MAIN_EXECUTOR.execute { focusedDisplayId = displayId }
                     }
                 }
             )
diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java
index cfbcf0a..c4ba2d5 100644
--- a/quickstep/src/com/android/quickstep/GestureState.java
+++ b/quickstep/src/com/android/quickstep/GestureState.java
@@ -28,25 +28,25 @@
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_HOME;
 import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.SET_END_TARGET_NEW_TASK;
 
-import android.app.TaskInfo;
 import android.content.Intent;
 import android.os.SystemClock;
+import android.view.Display;
 import android.view.MotionEvent;
 import android.view.RemoteAnimationTarget;
+import android.window.TransitionInfo;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.Flags;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulContainer;
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
+import com.android.quickstep.fallback.window.RecentsWindowFlags;
 import com.android.quickstep.util.ActiveGestureErrorDetector;
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
 import com.android.quickstep.views.RecentsViewContainer;
 import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.wm.shell.shared.GroupedTaskInfo;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -159,6 +159,7 @@
     private final BaseContainerInterface mContainerInterface;
     private final MultiStateCallback mStateCallback;
     private final int mGestureId;
+    private final int mDisplayId;
 
     public enum TrackpadGestureType {
         NONE,
@@ -191,16 +192,18 @@
     private boolean mHandlingAtomicEvent;
     private boolean mIsInExtendedSlopRegion;
 
-    public GestureState(OverviewComponentObserver componentObserver, int gestureId) {
+    public GestureState(OverviewComponentObserver componentObserver, int displayId, int gestureId) {
+        mDisplayId = displayId;
         mHomeIntent = componentObserver.getHomeIntent();
         mOverviewIntent = componentObserver.getOverviewIntent();
-        mContainerInterface = componentObserver.getContainerInterface();
+        mContainerInterface = componentObserver.getContainerInterface(displayId);
         mStateCallback = new MultiStateCallback(
                 STATE_NAMES.toArray(new String[0]), GestureState::getTrackedEventForState);
         mGestureId = gestureId;
     }
 
     public GestureState(GestureState other) {
+        mDisplayId = other.mDisplayId;
         mHomeIntent = other.mHomeIntent;
         mOverviewIntent = other.mOverviewIntent;
         mContainerInterface = other.mContainerInterface;
@@ -215,6 +218,7 @@
 
     public GestureState() {
         // Do nothing, only used for initializing the gesture state prior to user unlock
+        mDisplayId = Display.DEFAULT_DISPLAY;
         mHomeIntent = new Intent();
         mOverviewIntent = new Intent();
         mContainerInterface = null;
@@ -286,6 +290,13 @@
     }
 
     /**
+     * @return the id for the display this particular gesture was performed on.
+     */
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    /**
      * Sets if the gesture is is from the trackpad, if so, whether 3-finger, or 4-finger
      */
     public void setTrackpadGestureType(TrackpadGestureType trackpadGestureType) {
@@ -312,8 +323,7 @@
      */
     public boolean useSyntheticRecentsTransition() {
         return mRunningTask.isHomeTask()
-                && (Flags.enableFallbackOverviewInWindow()
-                        || Flags.enableLauncherOverviewInWindow());
+                && RecentsWindowFlags.Companion.getEnableOverviewInWindow();
     }
 
     /**
@@ -333,13 +343,7 @@
             return new int[]{INVALID_TASK_ID, INVALID_TASK_ID};
         } else {
             if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-                if (mRunningTask.getVisibleTasks().isEmpty()) {
-                    return new int[0];
-                }
-                GroupedTaskInfo topRunningTask = mRunningTask.getVisibleTasks().getFirst();
-                List<TaskInfo> groupedTasks = topRunningTask.getTaskInfoList();
-                return groupedTasks.stream().mapToInt(
-                        groupedTask -> groupedTask.taskId).toArray();
+                return mRunningTask.topGroupedTaskIds();
             } else {
                 int cachedTasksSize = mRunningTask.mAllCachedTasks.size();
                 int count = Math.min(cachedTasksSize, getMultipleTasks ? 2 : 1);
@@ -491,7 +495,7 @@
 
     @Override
     public void onRecentsAnimationStart(RecentsAnimationController controller,
-            RecentsAnimationTargets targets) {
+            RecentsAnimationTargets targets, TransitionInfo info) {
         mStateCallback.setState(STATE_RECENTS_ANIMATION_STARTED);
     }
 
@@ -552,6 +556,7 @@
 
     public void dump(String prefix, PrintWriter pw) {
         pw.println(prefix + "GestureState:");
+        pw.println(prefix + "\tdisplayID=" + mDisplayId);
         pw.println(prefix + "\tgestureID=" + mGestureId);
         pw.println(prefix + "\trunningTask=" + mRunningTask);
         pw.println(prefix + "\tendTarget=" + mEndTarget);
diff --git a/quickstep/src/com/android/quickstep/HighResLoadingState.kt b/quickstep/src/com/android/quickstep/HighResLoadingState.kt
new file mode 100644
index 0000000..8a21c4f
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/HighResLoadingState.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import android.content.res.Resources
+import com.android.quickstep.recents.data.HighResLoadingStateNotifier
+
+/** Determines when high res or low res thumbnails should be loaded. */
+class HighResLoadingState : HighResLoadingStateNotifier {
+    // If the device does not support low-res thumbnails, only attempt to load high-res thumbnails
+    private val forceHighResThumbnails = !supportsLowResThumbnails()
+    var visible: Boolean = false
+        set(value) {
+            field = value
+            updateState()
+        }
+
+    var flingingFast = false
+        set(value) {
+            field = value
+            updateState()
+        }
+
+    var isEnabled: Boolean = false
+        private set
+
+    private val callbacks = ArrayList<HighResLoadingStateChangedCallback>()
+
+    interface HighResLoadingStateChangedCallback {
+        fun onHighResLoadingStateChanged(enabled: Boolean)
+    }
+
+    override fun addCallback(callback: HighResLoadingStateChangedCallback) {
+        callbacks.add(callback)
+    }
+
+    override fun removeCallback(callback: HighResLoadingStateChangedCallback) {
+        callbacks.remove(callback)
+    }
+
+    private fun updateState() {
+        val prevState = isEnabled
+        isEnabled = forceHighResThumbnails || (visible && !flingingFast)
+        if (prevState != isEnabled) {
+            for (callback in callbacks.asReversed()) {
+                callback.onHighResLoadingStateChanged(isEnabled)
+            }
+        }
+    }
+
+    /**
+     * Returns Whether device supports low-res thumbnails. Low-res files are an optimization for
+     * faster load times of snapshots. Devices can optionally disable low-res files so that they
+     * only store snapshots at high-res scale. The actual scale can be configured in frameworks/base
+     * config overlay.
+     */
+    private fun supportsLowResThumbnails(): Boolean {
+        val res = Resources.getSystem()
+        val resId = res.getIdentifier("config_lowResTaskSnapshotScale", "dimen", "android")
+        if (resId != 0) {
+            return 0 < res.getFloat(resId)
+        }
+        return true
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/HomeVisibilityState.kt b/quickstep/src/com/android/quickstep/HomeVisibilityState.kt
index 241e16d..1345e0b 100644
--- a/quickstep/src/com/android/quickstep/HomeVisibilityState.kt
+++ b/quickstep/src/com/android/quickstep/HomeVisibilityState.kt
@@ -18,6 +18,7 @@
 
 import android.os.RemoteException
 import android.util.Log
+import com.android.launcher3.Utilities
 import com.android.launcher3.config.FeatureFlags
 import com.android.launcher3.util.Executors
 import com.android.wm.shell.shared.IHomeTransitionListener.Stub
@@ -41,10 +42,13 @@
             transitions?.setHomeTransitionListener(
                 object : Stub() {
                     override fun onHomeVisibilityChanged(isVisible: Boolean) {
-                        Executors.MAIN_EXECUTOR.execute {
-                            isHomeVisible = isVisible
-                            listeners.forEach { it.onHomeVisibilityChanged(isVisible) }
-                        }
+                        Utilities.postAsyncCallback(
+                            Executors.MAIN_EXECUTOR.handler,
+                            {
+                                isHomeVisible = isVisible
+                                listeners.forEach { it.onHomeVisibilityChanged(isVisible) }
+                            },
+                        )
                     }
                 }
             )
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 0185737..081ed9d 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -17,6 +17,7 @@
 
 import android.annotation.TargetApi;
 import android.os.Build;
+import android.view.Display;
 import android.view.InputEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -66,6 +67,10 @@
 
     int getType();
 
+    default int getDisplayId() {
+        return Display.DEFAULT_DISPLAY;
+    }
+
     /**
      * Returns true if the user has crossed the threshold for it to be an explicit action.
      */
diff --git a/quickstep/src/com/android/quickstep/InputConsumerUtils.kt b/quickstep/src/com/android/quickstep/InputConsumerUtils.kt
index bea3150..cd3ac12 100644
--- a/quickstep/src/com/android/quickstep/InputConsumerUtils.kt
+++ b/quickstep/src/com/android/quickstep/InputConsumerUtils.kt
@@ -56,8 +56,7 @@
 
     @JvmStatic
     fun <S : BaseState<S>, T> newConsumer(
-        baseContext: Context,
-        tisContext: Context,
+        context: Context,
         resetGestureInputConsumer: ResetGestureInputConsumer?,
         overviewComponentObserver: OverviewComponentObserver,
         deviceState: RecentsAnimationDeviceState,
@@ -77,7 +76,12 @@
         val bubbleControllers = tac?.bubbleControllers
         if (bubbleControllers != null && BubbleBarInputConsumer.isEventOnBubbles(tac, event)) {
             val consumer: InputConsumer =
-                BubbleBarInputConsumer(tisContext, bubbleControllers, inputMonitorCompat)
+                BubbleBarInputConsumer(
+                    context,
+                    gestureState.displayId,
+                    bubbleControllers,
+                    inputMonitorCompat,
+                )
             logInputConsumerSelectionReason(
                 consumer,
                 newCompoundString("event is on bubbles, creating new input consumer"),
@@ -88,7 +92,7 @@
         if (progressProxy != null) {
             val consumer: InputConsumer =
                 ProgressDelegateInputConsumer(
-                    tisContext,
+                    context,
                     taskAnimationManager,
                     gestureState,
                     inputMonitorCompat,
@@ -109,7 +113,7 @@
             if (gestureState.isTrackpadGesture) deviceState.canStartTrackpadGesture()
             else deviceState.canStartSystemGesture()
 
-        if (!get(tisContext).isUserUnlocked) {
+        if (!get(context).isUserUnlocked) {
             val reasonString = newCompoundString("device locked")
             val consumer =
                 if (canStartSystemGesture) {
@@ -117,7 +121,7 @@
                     // launched while device is locked even after exiting direct boot mode (e.g.
                     // camera).
                     createDeviceLockedInputConsumer(
-                        tisContext,
+                        context,
                         resetGestureInputConsumer,
                         deviceState,
                         gestureState,
@@ -148,7 +152,7 @@
                 )
             base =
                 newBaseConsumer<S, T>(
-                    tisContext,
+                    context,
                     resetGestureInputConsumer,
                     overviewComponentObserver,
                     deviceState,
@@ -186,7 +190,7 @@
                 )
                 base =
                     tryCreateAssistantInputConsumer(
-                        tisContext,
+                        context,
                         deviceState,
                         inputMonitorCompat,
                         base,
@@ -213,7 +217,7 @@
                     )
                     base =
                         TaskbarUnstashInputConsumer(
-                            tisContext,
+                            context,
                             base,
                             inputMonitorCompat,
                             tac,
@@ -237,7 +241,7 @@
                 }
             }
 
-            val navHandle = tac?.navHandle ?: SystemUiProxy.INSTANCE[tisContext]
+            val navHandle = tac?.navHandle ?: SystemUiProxy.INSTANCE[context]
             if (
                 canStartSystemGesture &&
                     !previousGestureState.isRecentsAnimationRunning &&
@@ -256,7 +260,7 @@
                 reasonString.append("using NavHandleLongPressInputConsumer")
                 base =
                     NavHandleLongPressInputConsumer(
-                        tisContext,
+                        context,
                         base,
                         inputMonitorCompat,
                         deviceState,
@@ -286,7 +290,13 @@
                             "%ssystem dialog is showing, using SysUiOverlayInputConsumer",
                             SUBSTRING_PREFIX,
                         )
-                base = SysUiOverlayInputConsumer(baseContext, deviceState, inputMonitorCompat)
+                base =
+                    SysUiOverlayInputConsumer(
+                        context,
+                        gestureState.displayId,
+                        deviceState,
+                        inputMonitorCompat,
+                    )
             }
 
             if (
@@ -300,7 +310,13 @@
                             "%sTrackpad 3-finger gesture, using TrackpadStatusBarInputConsumer",
                             SUBSTRING_PREFIX,
                         )
-                base = TrackpadStatusBarInputConsumer(baseContext, base, inputMonitorCompat)
+                base =
+                    TrackpadStatusBarInputConsumer(
+                        context,
+                        gestureState.displayId,
+                        base,
+                        inputMonitorCompat,
+                    )
             }
 
             if (deviceState.isScreenPinningActive) {
@@ -312,7 +328,7 @@
                         )
                 // Note: we only allow accessibility to wrap this, and it replaces the previous
                 // base input consumer (which should be NO_OP anyway since topTaskLocked == true).
-                base = ScreenPinnedInputConsumer(tisContext, gestureState)
+                base = ScreenPinnedInputConsumer(context, gestureState)
             }
 
             if (deviceState.canTriggerOneHandedAction(event)) {
@@ -323,7 +339,14 @@
                     reasonPrefix,
                     SUBSTRING_PREFIX,
                 )
-                base = OneHandedModeInputConsumer(tisContext, deviceState, base, inputMonitorCompat)
+                base =
+                    OneHandedModeInputConsumer(
+                        context,
+                        gestureState.displayId,
+                        deviceState,
+                        base,
+                        inputMonitorCompat,
+                    )
             }
 
             if (deviceState.isAccessibilityMenuAvailable) {
@@ -335,9 +358,9 @@
                 )
                 base =
                     AccessibilityInputConsumer(
-                        tisContext,
+                        context,
+                        gestureState.displayId,
                         deviceState,
-                        gestureState,
                         base,
                         inputMonitorCompat,
                     )
@@ -362,7 +385,14 @@
                     reasonPrefix,
                     SUBSTRING_PREFIX,
                 )
-                base = OneHandedModeInputConsumer(tisContext, deviceState, base, inputMonitorCompat)
+                base =
+                    OneHandedModeInputConsumer(
+                        context,
+                        gestureState.displayId,
+                        deviceState,
+                        base,
+                        inputMonitorCompat,
+                    )
             }
         }
         logInputConsumerSelectionReason(base, reasonString)
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index d193fee..ac0aa76 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -80,7 +80,7 @@
     }
 
     @Override
-    public void onSwipeUpToHomeComplete(RecentsAnimationDeviceState deviceState) {
+    public void onSwipeUpToHomeComplete() {
         QuickstepLauncher launcher = getCreatedContainer();
         if (launcher == null) {
             return;
@@ -93,7 +93,7 @@
         MAIN_EXECUTOR.getHandler().post(launcher.getStateManager()::reapplyState);
 
         launcher.getRootView().setForceHideBackArrow(false);
-        notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
+        notifyRecentsOfOrientation();
     }
 
     @Override
@@ -106,9 +106,9 @@
     }
 
     @Override
-    public AnimationFactory prepareRecentsUI(RecentsAnimationDeviceState deviceState,
+    public AnimationFactory prepareRecentsUI(
             boolean activityVisible, Consumer<AnimatorControllerWithResistance> callback) {
-        notifyRecentsOfOrientation(deviceState.getRotationTouchHelper());
+        notifyRecentsOfOrientation();
         DefaultAnimationFactory factory = new DefaultAnimationFactory(callback) {
             @Override
             protected void createBackgroundToOverviewAnim(QuickstepLauncher activity,
@@ -227,7 +227,7 @@
 
 
     @Override
-    public void onExitOverview(RotationTouchHelper deviceState, Runnable exitRunnable) {
+    public void onExitOverview(Runnable exitRunnable) {
         final StateManager<LauncherState, Launcher> stateManager =
                 getCreatedContainer().getStateManager();
         stateManager.addStateListener(
@@ -237,18 +237,16 @@
                         // Are we going from Recents to Workspace?
                         if (toState == LauncherState.NORMAL) {
                             exitRunnable.run();
-                            notifyRecentsOfOrientation(deviceState);
+                            notifyRecentsOfOrientation();
                             stateManager.removeStateListener(this);
                         }
                     }
                 });
     }
 
-    private void notifyRecentsOfOrientation(RotationTouchHelper rotationTouchHelper) {
+    private void notifyRecentsOfOrientation() {
         // reset layout on swipe to home
-        RecentsView recentsView = getCreatedContainer().getOverviewPanel();
-        recentsView.setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
-                rotationTouchHelper.getDisplayRotation());
+        ((RecentsView) getCreatedContainer().getOverviewPanel()).reapplyActiveRotation();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 4bd9ffb..783ec2c 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -16,6 +16,7 @@
 
 package com.android.quickstep;
 
+import static android.util.MathUtils.lerp;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
@@ -51,11 +52,15 @@
 import android.window.BackProgressAnimator;
 import android.window.IBackAnimationHandoffHandler;
 import android.window.IOnBackInvokedCallback;
+
+import com.android.app.animation.Animations;
 import com.android.app.animation.Interpolators;
 import com.android.internal.policy.SystemBarUtils;
 import com.android.internal.view.AppearanceRegion;
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.Flags;
+import com.android.launcher3.LauncherState;
 import com.android.launcher3.QuickstepTransitionManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -65,6 +70,7 @@
 import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
 import com.android.quickstep.util.BackAnimState;
+import com.android.quickstep.util.ScalingWorkspaceRevealAnim;
 import com.android.systemui.shared.system.QuickStepContract;
 
 import java.lang.ref.WeakReference;
@@ -87,9 +93,12 @@
  */
 public class LauncherBackAnimationController {
     private static final int SCRIM_FADE_DURATION = 233;
-    private static final float MIN_WINDOW_SCALE = 0.85f;
+    private static final float MIN_WINDOW_SCALE =
+            Flags.predictiveBackToHomePolish() ? 0.75f : 0.85f;
     private static final float MAX_SCRIM_ALPHA_DARK = 0.8f;
     private static final float MAX_SCRIM_ALPHA_LIGHT = 0.2f;
+    private static final int MAX_BLUR_RADIUS = 20;
+    private static final int MIN_BLUR_RADIUS_PRE_COMMIT = 10;
 
     private final QuickstepTransitionManager mQuickstepTransitionManager;
     private final Matrix mTransformMatrix = new Matrix();
@@ -119,6 +128,7 @@
     private ValueAnimator mScrimAlphaAnimator;
     private float mScrimAlpha;
     private boolean mOverridingStatusBarFlags;
+    private int mLastBlurRadius = 0;
 
     private final ComponentCallbacks mComponentCallbacks = new ComponentCallbacks() {
         @Override
@@ -268,6 +278,10 @@
 
     private void onCancelFinished() {
         customizeStatusBarAppearance(false);
+        if (Flags.predictiveBackToHomePolish() && !mLauncher.getWorkspace().isOverlayShown()
+                && !mLauncher.isInState(LauncherState.ALL_APPS)) {
+            setLauncherScale(ScalingWorkspaceRevealAnim.MAX_SIZE);
+        }
         finishAnimation();
     }
 
@@ -314,6 +328,17 @@
                 new RemoteAnimationTarget[]{ mBackTarget });
         setLauncherTargetViewVisible(false);
         mCurrentRect.set(mStartRect);
+        if (Flags.predictiveBackToHomePolish() && !mLauncher.getWorkspace().isOverlayShown()
+                && !mLauncher.isInState(LauncherState.ALL_APPS)) {
+            Animations.cancelOngoingAnimation(mLauncher.getWorkspace());
+            Animations.cancelOngoingAnimation(mLauncher.getHotseat());
+            if (Flags.predictiveBackToHomeBlur()) {
+                mLauncher.getDepthController().pauseBlursOnWindows(true);
+            }
+            mLauncher.getDepthController().stateDepth.setValue(
+                    LauncherState.BACKGROUND_APP.getDepth(mLauncher));
+            setLauncherScale(ScalingWorkspaceRevealAnim.MIN_SIZE);
+        }
         if (mScrimLayer == null) {
             addScrimLayer();
         }
@@ -328,6 +353,13 @@
         }
     }
 
+    private void setLauncherScale(float scale) {
+        mLauncher.getWorkspace().setScaleX(scale);
+        mLauncher.getWorkspace().setScaleY(scale);
+        mLauncher.getHotseat().setScaleX(scale);
+        mLauncher.getHotseat().setScaleY(scale);
+    }
+
     void addScrimLayer() {
         SurfaceControl parent = mLauncherTarget != null ? mLauncherTarget.leash : null;
         if (parent == null || !parent.isValid()) {
@@ -346,6 +378,7 @@
         final float[] colorComponents = new float[] { 0f, 0f, 0f };
         mScrimAlpha = (isDarkTheme)
                 ? MAX_SCRIM_ALPHA_DARK : MAX_SCRIM_ALPHA_LIGHT;
+        setBlur(MAX_BLUR_RADIUS);
         mTransaction
                 .setColor(mScrimLayer, colorComponents)
                 .setAlpha(mScrimLayer, mScrimAlpha)
@@ -372,6 +405,9 @@
         if (mScrimLayer == null) {
             // Scrim hasn't been attached yet. Let's attach it.
             addScrimLayer();
+        } else {
+            mLastBlurRadius = (int) lerp(MAX_BLUR_RADIUS, MIN_BLUR_RADIUS_PRE_COMMIT, progress);
+            setBlur(mLastBlurRadius);
         }
         float screenWidth = mStartRect.width();
         float screenHeight = mStartRect.height();
@@ -403,6 +439,12 @@
         customizeStatusBarAppearance(top > mStatusBarHeight / 2);
     }
 
+    private void setBlur(int blurRadius) {
+        if (Flags.predictiveBackToHomeBlur()) {
+            mTransaction.setBackgroundBlurRadius(mScrimLayer, blurRadius);
+        }
+    }
+
     /** Transform the target window to match the target rect. */
     private void applyTransform(RectF targetRect, float cornerRadius) {
         final float scale = targetRect.width() / mStartRect.width();
@@ -472,7 +514,13 @@
 
     private void finishAnimation() {
         mLauncher.setPredictiveBackToHomeInProgress(false);
+        if (mBackTarget != null && mBackTarget.leash.isValid()) {
+            mBackTarget.leash.release();
+        }
         mBackTarget = null;
+        if (mLauncherTarget != null && mLauncherTarget.leash.isValid()) {
+            mLauncherTarget.leash.release();
+        }
         mLauncherTarget = null;
         mBackInProgress = false;
         mBackProgress = 0;
@@ -500,6 +548,12 @@
         if (mScrimLayer != null) {
             removeScrimLayer();
         }
+        if (Flags.predictiveBackToHomePolish() && Flags.predictiveBackToHomeBlur()
+                && !mLauncher.getWorkspace().isOverlayShown()
+                && !mLauncher.isInState(LauncherState.ALL_APPS)) {
+            mLauncher.getDepthController().pauseBlursOnWindows(false);
+        }
+        mLastBlurRadius = 0;
     }
 
     private void startTransitionAnimations(BackAnimState backAnim) {
@@ -513,6 +567,7 @@
             float value = (Float) animation.getAnimatedValue();
             if (mScrimLayer != null && mScrimLayer.isValid()) {
                 mTransaction.setAlpha(mScrimLayer, value * mScrimAlpha);
+                setBlur((int) lerp(mLastBlurRadius, 0, 1f - value));
                 applyTransaction();
             }
         });
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 6087dc2..4c56f35 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -39,8 +39,10 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.StableViewInfo;
 import com.android.launcher3.views.ClipIconView;
 import com.android.launcher3.views.FloatingIconView;
@@ -54,6 +56,7 @@
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.animation.TransitionAnimator;
+import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.InputConsumerController;
 
 import java.util.Collections;
@@ -65,11 +68,11 @@
 public class LauncherSwipeHandlerV2 extends AbsSwipeUpHandler<
         QuickstepLauncher, RecentsView<QuickstepLauncher, LauncherState>, LauncherState> {
 
-    public LauncherSwipeHandlerV2(Context context, RecentsAnimationDeviceState deviceState,
-            TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
-            boolean continuingLastGesture, InputConsumerController inputConsumer) {
-        super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
-                continuingLastGesture, inputConsumer, null);
+    public LauncherSwipeHandlerV2(Context context, TaskAnimationManager taskAnimationManager,
+            GestureState gestureState, long touchTimeMs, boolean continuingLastGesture,
+            InputConsumerController inputConsumer, MSDLPlayerWrapper msdlPlayerWrapper) {
+        super(context, taskAnimationManager, gestureState, touchTimeMs,
+                continuingLastGesture, inputConsumer, msdlPlayerWrapper);
     }
 
 
@@ -103,8 +106,8 @@
         boolean canUseWorkspaceView = workspaceView != null
                 && workspaceView.isAttachedToWindow()
                 && workspaceView.getHeight() > 0
-                && (mContainer.getDesktopVisibilityController() == null
-                || !mContainer.getDesktopVisibilityController().areDesktopTasksVisible());
+                && !DesktopVisibilityController.INSTANCE.get(mContainer)
+                        .isInDesktopModeAndNotInOverview(mContainer.getDisplayId());
 
         mContainer.getRootView().setForceHideBackArrow(true);
 
@@ -298,15 +301,17 @@
             // Disable if swiping to PIP
             return null;
         }
-        if (sourceTaskView == null || sourceTaskView.getFirstTask().key.getComponent() == null) {
+        Task firstTask;
+        if (sourceTaskView == null || ((firstTask = sourceTaskView.getFirstTask()) == null)
+                || firstTask.key.getComponent() == null) {
             // Disable if it's an invalid task
             return null;
         }
 
-        return mContainer.getFirstMatchForAppClose(StableViewInfo.fromLaunchCookies(launchCookies),
+        return mContainer.getFirstHomeElementForAppClose(
+                StableViewInfo.fromLaunchCookies(launchCookies),
                 sourceTaskView.getFirstTask().key.getComponent().getPackageName(),
-                UserHandle.of(sourceTaskView.getFirstTask().key.userId),
-                false /* supportsAllAppsState */);
+                UserHandle.of(sourceTaskView.getFirstTask().key.userId));
     }
 
     @Override
@@ -329,7 +334,7 @@
         protected void playScalingRevealAnimation() {
             if (mContainer != null) {
                 new ScalingWorkspaceRevealAnim(mContainer, mSiblingAnimation,
-                        getWindowTargetRect()).start();
+                        getWindowTargetRect(), true /* playAlphaReveal */).start();
             }
         }
 
@@ -379,7 +384,7 @@
             if (mContainer != null) {
                 new ScalingWorkspaceRevealAnim(
                         mContainer, null /* siblingAnimation */,
-                        null /* windowTargetRect */).start();
+                        null /* windowTargetRect */, true /* playAlphaReveal */).start();
             }
         }
     }
diff --git a/quickstep/src/com/android/quickstep/OWNERS b/quickstep/src/com/android/quickstep/OWNERS
new file mode 100644
index 0000000..868e0ab
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/OWNERS
@@ -0,0 +1,9 @@
+# System Navigation team
+brianji@google.com
+jonmiranda@google.com
+jagrutdesai@google.com
+randypfohl@google.com
+saumyaprakash@google.com
+sukeshram@google.com
+twickham@google.com
+victortulias@google.com
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
index c09bf3e..bf87291 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.kt
@@ -22,11 +22,14 @@
 import android.os.SystemClock
 import android.os.Trace
 import android.util.Log
+import android.view.Display.DEFAULT_DISPLAY
 import android.view.View
+import android.window.TransitionInfo
 import androidx.annotation.BinderThread
 import androidx.annotation.UiThread
 import androidx.annotation.VisibleForTesting
 import com.android.internal.jank.Cuj
+import com.android.launcher3.Flags.enableAltTabKqsOnConnectedDisplays
 import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
 import com.android.launcher3.Flags.enableOverviewCommandHelperTimeout
 import com.android.launcher3.PagedView
@@ -35,6 +38,8 @@
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_3_BUTTON
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_QUICK_SWITCH
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_OVERVIEW_SHOW_OVERVIEW_FROM_KEYBOARD_SHORTCUT
+import com.android.launcher3.taskbar.TaskbarManager
+import com.android.launcher3.taskbar.TaskbarUIController
 import com.android.launcher3.util.Executors
 import com.android.launcher3.util.RunnableList
 import com.android.launcher3.util.coroutines.DispatcherProvider
@@ -45,6 +50,8 @@
 import com.android.quickstep.OverviewCommandHelper.CommandType.KEYBOARD_INPUT
 import com.android.quickstep.OverviewCommandHelper.CommandType.SHOW
 import com.android.quickstep.OverviewCommandHelper.CommandType.TOGGLE
+import com.android.quickstep.fallback.window.RecentsDisplayModel
+import com.android.quickstep.fallback.window.RecentsWindowFlags.Companion.enableOverviewInWindow
 import com.android.quickstep.util.ActiveGestureLog
 import com.android.quickstep.util.ActiveGestureProtoLogProxy
 import com.android.quickstep.views.RecentsView
@@ -69,6 +76,9 @@
     private val overviewComponentObserver: OverviewComponentObserver,
     private val taskAnimationManager: TaskAnimationManager,
     private val dispatcherProvider: DispatcherProvider = ProductionDispatchers,
+    private val recentsDisplayModel: RecentsDisplayModel,
+    private val focusState: FocusState,
+    private val taskbarManager: TaskbarManager,
 ) {
     private val coroutineScope = CoroutineScope(SupervisorJob() + dispatcherProvider.background)
 
@@ -81,9 +91,11 @@
      */
     private var keyboardTaskFocusIndex = -1
 
+    // TODO (b/397942185): get per-display interface
     private val containerInterface: BaseContainerInterface<*, *>
-        get() = overviewComponentObserver.containerInterface
+        get() = overviewComponentObserver.getContainerInterface(DEFAULT_DISPLAY)
 
+    // TODO (b/397942185): get per-display RecentsView
     private val visibleRecentsView: RecentsView<*, *>?
         get() = containerInterface.getVisibleRecentsView<RecentsView<*, *>>()
 
@@ -233,7 +245,7 @@
         // When running task view is null we return last large taskView - typically focusView when
         // grid only is not enabled else last desktop task view.
         return if (recentsView.runningTaskView == null) {
-            recentsView.lastLargeTaskView ?: recentsView.getTaskViewAt(0)
+            recentsView.lastLargeTaskView ?: recentsView.getFirstTaskView()
         } else {
             if (
                 enableLargeDesktopWindowingTile() &&
@@ -283,20 +295,52 @@
         val recentsView: RecentsView<*, *>? = recentsViewContainer?.getOverviewPanel()
         val deviceProfile = recentsViewContainer?.getDeviceProfile()
         val uiController = containerInterface.getTaskbarController()
-        val allowQuickSwitch =
-            uiController != null &&
-                deviceProfile != null &&
-                (deviceProfile.isTablet || deviceProfile.isTwoPanels)
+
+        val focusedDisplayId = focusState.focusedDisplayId
+        val focusedDisplayUIController: TaskbarUIController? =
+            if (enableOverviewInWindow) {
+                Log.d(
+                    TAG,
+                    "Querying RecentsDisplayModel for TaskbarUIController for display: $focusedDisplayId",
+                )
+                recentsDisplayModel.getRecentsWindowManager(focusedDisplayId)?.taskbarUIController
+            } else {
+                Log.d(
+                    TAG,
+                    "Querying TaskbarManager for TaskbarUIController for display: $focusedDisplayId",
+                )
+                // TODO(b/395061396): Remove this path when overview in widow is enabled.
+                taskbarManager.getUIControllerForDisplay(focusedDisplayId)
+            }
+        Log.d(
+            TAG,
+            "TaskbarUIController for display $focusedDisplayId was" +
+                "${if (focusedDisplayUIController == null) " not" else ""} found",
+        )
 
         when (command.type) {
             HIDE -> {
-                if (!allowQuickSwitch) return true
-                keyboardTaskFocusIndex = uiController!!.launchFocusedTask()
+                if (uiController == null || deviceProfile?.isTablet == false) return true
+                keyboardTaskFocusIndex =
+                    if (
+                        enableAltTabKqsOnConnectedDisplays() && focusedDisplayUIController != null
+                    ) {
+                        focusedDisplayUIController.launchFocusedTask()
+                    } else {
+                        uiController.launchFocusedTask()
+                    }
+
                 if (keyboardTaskFocusIndex == -1) return true
             }
             KEYBOARD_INPUT ->
-                if (allowQuickSwitch) {
-                    uiController!!.openQuickSwitchView()
+                if (uiController != null && deviceProfile?.isTablet == true) {
+                    if (
+                        enableAltTabKqsOnConnectedDisplays() && focusedDisplayUIController != null
+                    ) {
+                        focusedDisplayUIController.openQuickSwitchView()
+                    } else {
+                        uiController.openQuickSwitchView()
+                    }
                     return true
                 } else {
                     keyboardTaskFocusIndex = 0
@@ -319,7 +363,11 @@
             TOGGLE -> {}
         }
 
-        recentsView?.setKeyboardTaskFocusIndex(keyboardTaskFocusIndex)
+        recentsView?.setKeyboardTaskFocusIndex(
+            recentsView.indexOfChild(recentsView.taskViews.elementAtOrNull(keyboardTaskFocusIndex))
+                ?: -1
+        )
+
         // Handle recents view focus when launching from home
         val animatorListener: Animator.AnimatorListener =
             object : AnimatorListenerAdapter() {
@@ -343,13 +391,15 @@
             return false
         }
 
-        val activity = containerInterface.getCreatedContainer()
-        if (activity != null) {
-            InteractionJankMonitorWrapper.begin(activity.rootView, Cuj.CUJ_LAUNCHER_QUICK_SWITCH)
+        if (!enableOverviewInWindow) {
+            containerInterface.getCreatedContainer()?.rootView?.let { view ->
+                InteractionJankMonitorWrapper.begin(view, Cuj.CUJ_LAUNCHER_QUICK_SWITCH)
+            }
         }
 
         val gestureState =
             touchInteractionService.createGestureState(
+                focusedDisplayId,
                 GestureState.DEFAULT_STATE,
                 GestureState.TrackpadGestureType.NONE,
             )
@@ -369,13 +419,24 @@
                 override fun onRecentsAnimationStart(
                     controller: RecentsAnimationController,
                     targets: RecentsAnimationTargets,
+                    transitionInfo: TransitionInfo?,
                 ) {
                     Log.d(TAG, "recents animation started: $command")
+                    if (enableOverviewInWindow) {
+                        containerInterface.getCreatedContainer()?.rootView?.let { view ->
+                            InteractionJankMonitorWrapper.begin(view, Cuj.CUJ_LAUNCHER_QUICK_SWITCH)
+                        }
+                    }
+
                     updateRecentsViewFocus(command)
                     logShowOverviewFrom(command.type)
                     containerInterface.runOnInitBackgroundStateUI {
                         Log.d(TAG, "recents animation started - onInitBackgroundStateUI: $command")
-                        interactionHandler.onGestureEnded(0f, PointF())
+                        interactionHandler.onGestureEnded(
+                            0f,
+                            PointF(),
+                            /* horizontalTouchSlopPassed= */ false,
+                        )
                     }
                     command.removeListener(this)
                 }
@@ -470,7 +531,7 @@
         // Stops requesting focused after first view gets focused.
         recentsView.getTaskViewAt(keyboardTaskFocusIndex).requestFocus() ||
             recentsView.nextTaskView.requestFocus() ||
-            recentsView.getTaskViewAt(0).requestFocus() ||
+            recentsView.firstTaskView.requestFocus() ||
             recentsView.requestFocus()
     }
 
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index e3da9f5..7d3a1da 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -21,8 +21,10 @@
 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.launcher3.Flags.enableOverviewOnConnectedDisplays;
 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.quickstep.fallback.window.RecentsWindowFlags.enableLauncherOverviewInWindow;
 import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
 
 import android.content.ActivityNotFoundException;
@@ -40,7 +42,6 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
-import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 import com.android.launcher3.dagger.ApplicationContext;
 import com.android.launcher3.dagger.LauncherAppComponent;
@@ -48,6 +49,8 @@
 import com.android.launcher3.util.DaggerSingletonObject;
 import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
+import com.android.quickstep.fallback.window.RecentsWindowFlags;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
 import com.android.systemui.shared.system.PackageManagerWrapper;
 
@@ -71,12 +74,10 @@
             new DaggerSingletonObject<>(LauncherAppComponent::getOverviewComponentObserver);
 
     // We register broadcast receivers on main thread to avoid missing updates.
-    private final SimpleBroadcastReceiver mUserPreferenceChangeReceiver =
-            new SimpleBroadcastReceiver(MAIN_EXECUTOR, this::updateOverviewTargets);
-    private final SimpleBroadcastReceiver mOtherHomeAppUpdateReceiver =
-            new SimpleBroadcastReceiver(MAIN_EXECUTOR, this::updateOverviewTargets);
+    private final SimpleBroadcastReceiver mUserPreferenceChangeReceiver;
+    private final SimpleBroadcastReceiver mOtherHomeAppUpdateReceiver;
 
-    private final Context mContext;
+    private final RecentsDisplayModel mRecentsDisplayModel;
 
     private final Intent mCurrentHomeIntent;
     private final Intent mMyHomeIntent;
@@ -88,7 +89,7 @@
             new CopyOnWriteArrayList<>();
 
     private String mUpdateRegisteredPackage;
-    private BaseContainerInterface mContainerInterface;
+    private BaseContainerInterface mDefaultDisplayContainerInterface;
     private Intent mOverviewIntent;
     private boolean mIsHomeAndOverviewSame;
     private boolean mIsDefaultHome;
@@ -97,10 +98,15 @@
     @Inject
     public OverviewComponentObserver(
             @ApplicationContext Context context,
+            RecentsDisplayModel recentsDisplayModel,
             DaggerSingletonTracker lifecycleTracker) {
-        mContext = context;
+        mUserPreferenceChangeReceiver =
+                new SimpleBroadcastReceiver(context, MAIN_EXECUTOR, this::updateOverviewTargets);
+        mOtherHomeAppUpdateReceiver =
+                new SimpleBroadcastReceiver(context, MAIN_EXECUTOR, this::updateOverviewTargets);
+        mRecentsDisplayModel = recentsDisplayModel;
         mCurrentHomeIntent = createHomeIntent();
-        mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(mContext.getPackageName());
+        mMyHomeIntent = new Intent(mCurrentHomeIntent).setPackage(context.getPackageName());
         ResolveInfo info = context.getPackageManager().resolveActivity(mMyHomeIntent, 0);
         ComponentName myHomeComponent =
                 new ComponentName(context.getPackageName(), info.activityInfo.name);
@@ -108,7 +114,7 @@
         mConfigChangesMap.append(myHomeComponent.hashCode(), info.activityInfo.configChanges);
         mSetupWizardPkg = context.getString(R.string.setup_wizard_pkg);
 
-        ComponentName fallbackComponent = new ComponentName(mContext, RecentsActivity.class);
+        ComponentName fallbackComponent = new ComponentName(context, RecentsActivity.class);
         mFallbackIntent = new Intent(Intent.ACTION_MAIN)
                 .addCategory(Intent.CATEGORY_DEFAULT)
                 .setComponent(fallbackComponent)
@@ -120,7 +126,7 @@
             mConfigChangesMap.append(fallbackComponent.hashCode(), fallbackInfo.configChanges);
         } catch (PackageManager.NameNotFoundException ignored) { /* Impossible */ }
 
-        mUserPreferenceChangeReceiver.register(mContext, ACTION_PREFERRED_ACTIVITY_CHANGED);
+        mUserPreferenceChangeReceiver.register(ACTION_PREFERRED_ACTIVITY_CHANGED);
         updateOverviewTargets();
 
         lifecycleTracker.addCloseable(this::onDestroy);
@@ -171,11 +177,11 @@
         // Set assistant visibility to 0 from launcher's perspective, ensures any elements that
         // launcher made invisible become visible again before the new activity control helper
         // becomes active.
-        if (mContainerInterface != null) {
-            mContainerInterface.onAssistantVisibilityChanged(0.f);
+        if (mDefaultDisplayContainerInterface != null) {
+            mDefaultDisplayContainerInterface.onAssistantVisibilityChanged(0.f);
         }
 
-        if (SEPARATE_RECENTS_ACTIVITY.get() || Flags.enableLauncherOverviewInWindow()) {
+        if (SEPARATE_RECENTS_ACTIVITY.get() || enableLauncherOverviewInWindow.isTrue()) {
             mIsDefaultHome = false;
             if (defaultHome == null) {
                 defaultHome = mMyHomeIntent.getComponent();
@@ -189,7 +195,7 @@
 
         if (!mIsHomeDisabled && (defaultHome == null || mIsDefaultHome)) {
             // User default home is same as out home app. Use Overview integrated in Launcher.
-            mContainerInterface = LauncherActivityInterface.INSTANCE;
+            mDefaultDisplayContainerInterface = LauncherActivityInterface.INSTANCE;
             mIsHomeAndOverviewSame = true;
             mOverviewIntent = mMyHomeIntent;
             mCurrentHomeIntent.setComponent(mMyHomeIntent.getComponent());
@@ -198,11 +204,11 @@
             unregisterOtherHomeAppUpdateReceiver();
         } else {
             // The default home app is a different launcher. Use the fallback Overview instead.
-
-            if (Flags.enableLauncherOverviewInWindow() || Flags.enableFallbackOverviewInWindow()) {
-                mContainerInterface = FallbackWindowInterface.getInstance();
+            if (RecentsWindowFlags.Companion.getEnableOverviewInWindow()) {
+                mDefaultDisplayContainerInterface =
+                        mRecentsDisplayModel.getFallbackWindowInterface(DEFAULT_DISPLAY);
             } else {
-                mContainerInterface = FallbackActivityInterface.INSTANCE;
+                mDefaultDisplayContainerInterface = FallbackActivityInterface.INSTANCE;
             }
             mIsHomeAndOverviewSame = false;
             mOverviewIntent = mFallbackIntent;
@@ -219,7 +225,7 @@
 
                 mUpdateRegisteredPackage = defaultHome.getPackageName();
                 mOtherHomeAppUpdateReceiver.registerPkgActions(
-                        mContext, mUpdateRegisteredPackage, ACTION_PACKAGE_ADDED,
+                        mUpdateRegisteredPackage, ACTION_PACKAGE_ADDED,
                         ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REMOVED);
             }
         }
@@ -230,13 +236,13 @@
      * Clean up any registered receivers.
      */
     private void onDestroy() {
-        mUserPreferenceChangeReceiver.unregisterReceiverSafely(mContext);
+        mUserPreferenceChangeReceiver.unregisterReceiverSafely();
         unregisterOtherHomeAppUpdateReceiver();
     }
 
     private void unregisterOtherHomeAppUpdateReceiver() {
         if (mUpdateRegisteredPackage != null) {
-            mOtherHomeAppUpdateReceiver.unregisterReceiverSafely(mContext);
+            mOtherHomeAppUpdateReceiver.unregisterReceiverSafely();
             mUpdateRegisteredPackage = null;
         }
     }
@@ -263,7 +269,7 @@
      *
      * @return the overview intent
      */
-    Intent getOverviewIntentIgnoreSysUiState() {
+    public Intent getOverviewIntentIgnoreSysUiState() {
         return mIsDefaultHome ? mMyHomeIntent : mOverviewIntent;
     }
 
@@ -291,12 +297,16 @@
     }
 
     /**
-     * Get the current control helper for managing interactions to the overview container.
+     * Get the current control helper for managing interactions to the overview container for
+     * the given displayId.
      *
-     * @return the current control helper
+     * @param displayId The display id
+     * @return the control helper for the given display
      */
-    public BaseContainerInterface<?,?> getContainerInterface() {
-        return mContainerInterface;
+    public BaseContainerInterface<?, ?> getContainerInterface(int displayId) {
+        return (enableOverviewOnConnectedDisplays() && displayId != DEFAULT_DISPLAY)
+                ? mRecentsDisplayModel.getFallbackWindowInterface(displayId)
+                : mDefaultDisplayContainerInterface;
     }
 
     public void dump(PrintWriter pw) {
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index f91f696..e9f7024 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -1,5 +1,7 @@
 package com.android.quickstep;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.android.launcher3.taskbar.TaskbarThresholdUtils.getFromNavThreshold;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
@@ -19,6 +21,7 @@
 import com.android.quickstep.util.TISBindHelper;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.RecentsViewContainer;
+import com.android.systemui.shared.recents.model.Task;
 
 import java.util.ArrayList;
 import java.util.concurrent.CountDownLatch;
@@ -44,11 +47,9 @@
                 CountDownLatch latch = new CountDownLatch(1);
                 RecentsModel.INSTANCE.get(mContext).getTasks((taskGroups) -> {
                     for (GroupTask group : taskGroups) {
-                        taskBaseIntentComponents.add(
-                                group.task1.key.baseIntent.getComponent().flattenToString());
-                        if (group.task2 != null) {
+                        for (Task t : group.getTasks()) {
                             taskBaseIntentComponents.add(
-                                    group.task2.key.baseIntent.getComponent().flattenToString());
+                                    t.key.baseIntent.getComponent().flattenToString());
                         }
                     }
                     latch.countDown();
@@ -178,7 +179,7 @@
 
             case TestProtocol.REQUEST_RECREATE_TASKBAR:
                 // Allow null-pointer to catch illegal states.
-                runOnTISBinder(tisBinder -> tisBinder.getTaskbarManager().recreateTaskbar());
+                runOnTISBinder(tisBinder -> tisBinder.getTaskbarManager().recreateTaskbars());
                 return response;
             case TestProtocol.REQUEST_TASKBAR_IME_DOCKED:
                 return getTISBinderUIProperty(Bundle::putBoolean, tisBinder ->
@@ -205,17 +206,20 @@
     @Override
     protected WindowInsets getWindowInsets() {
         RecentsViewContainer container = getRecentsViewContainer();
-        return container == null ? null : container.getRootView().getRootWindowInsets();
+        WindowInsets insets = container == null
+                ? null : container.getRootView().getRootWindowInsets();
+        return insets == null ? super.getWindowInsets() : insets;
     }
 
     private RecentsViewContainer getRecentsViewContainer() {
+        // TODO (b/400647896): support per-display container in e2e tests
         return OverviewComponentObserver.INSTANCE.get(mContext)
-                .getContainerInterface().getCreatedContainer();
+                .getContainerInterface(DEFAULT_DISPLAY).getCreatedContainer();
     }
 
     @Override
     protected boolean isLauncherInitialized() {
-        return super.isLauncherInitialized() && TouchInteractionService.isInitialized();
+        return super.isLauncherInitialized() && SystemUiProxy.INSTANCE.get(mContext).isActive();
     }
 
     private void enableBlockingTimeout(
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 85e2b6e..cc5b2da 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -18,9 +18,11 @@
 
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 
+import static com.android.launcher3.Flags.enableSeparateExternalDisplayTasks;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
-import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM;
+import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_DESK;
+import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.KeyguardManager;
@@ -30,24 +32,39 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.util.SparseBooleanArray;
+import android.window.DesktopExperienceFlags;
 
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.quickstep.util.DesktopTask;
+import com.android.quickstep.util.ExternalDisplaysKt;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
+import com.android.quickstep.util.SplitTask;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.wm.shell.Flags;
 import com.android.wm.shell.recents.IRecentTasksListener;
 import com.android.wm.shell.shared.GroupedTaskInfo;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 
+import kotlin.collections.ArraysKt;
+import kotlin.collections.CollectionsKt;
+import kotlin.collections.MapsKt;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -55,7 +72,10 @@
 /**
  * Manages the recent task list from the system, caching it as necessary.
  */
-public class RecentTasksList {
+// TODO: b/401602554 - Consider letting [DesktopTasksController] notify [RecentTasksController] of
+//  desk changes to trigger [IRecentTasksListener.onRecentTasksChanged()], instead of implementing
+//  [DesktopVisibilityListener].
+public class RecentTasksList implements WindowManagerProxy.DesktopVisibilityListener {
 
     private static final TaskLoadResult INVALID_RESULT = new TaskLoadResult(-1, false, 0);
 
@@ -63,6 +83,7 @@
     private final KeyguardManager mKeyguardManager;
     private final LooperExecutor mMainThreadExecutor;
     private final SystemUiProxy mSysUiProxy;
+    private final DesktopVisibilityController mDesktopVisibilityController;
 
     // The list change id, increments as the task list changes in the system
     private int mChangeId;
@@ -80,13 +101,16 @@
 
     public RecentTasksList(Context context, LooperExecutor mainThreadExecutor,
             KeyguardManager keyguardManager, SystemUiProxy sysUiProxy,
-            TopTaskTracker topTaskTracker) {
+            TopTaskTracker topTaskTracker,
+            DesktopVisibilityController desktopVisibilityController,
+            DaggerSingletonTracker tracker) {
         mContext = context;
         mMainThreadExecutor = mainThreadExecutor;
         mKeyguardManager = keyguardManager;
         mChangeId = 1;
         mSysUiProxy = sysUiProxy;
-        sysUiProxy.registerRecentTasksListener(new IRecentTasksListener.Stub() {
+        mDesktopVisibilityController = desktopVisibilityController;
+        final IRecentTasksListener recentTasksListener = new IRecentTasksListener.Stub() {
             @Override
             public void onRecentTasksChanged() throws RemoteException {
                 mMainThreadExecutor.execute(RecentTasksList.this::onRecentTasksChanged);
@@ -116,7 +140,8 @@
             @Override
             public void onTaskMovedToFront(GroupedTaskInfo taskToFront) {
                 mMainThreadExecutor.execute(() -> {
-                    topTaskTracker.handleTaskMovedToFront(taskToFront.getTaskInfo1());
+                    topTaskTracker.handleTaskMovedToFront(
+                            taskToFront.getBaseGroupedTask().getTaskInfo1());
                 });
             }
 
@@ -131,7 +156,19 @@
                     topTaskTracker.onVisibleTasksChanged(visibleTasks);
                 });
             }
-        });
+        };
+
+        mSysUiProxy.registerRecentTasksListener(recentTasksListener);
+        tracker.addCloseable(
+                () -> mSysUiProxy.unregisterRecentTasksListener(recentTasksListener));
+
+        if (DesktopModeStatus.enableMultipleDesktops(mContext)) {
+            mDesktopVisibilityController.registerDesktopVisibilityListener(
+                    this);
+            tracker.addCloseable(
+                    () -> mDesktopVisibilityController.unregisterDesktopVisibilityListener(this));
+        }
+
         // We may receive onRunningTaskAppeared events later for tasks which have already been
         // included in the list returned by mSysUiProxy.getRunningTasks(), or may receive
         // onRunningTaskVanished for tasks not included in the returned list. These cases will be
@@ -257,7 +294,7 @@
         mRecentTasksChangedListener = null;
     }
 
-    private void initRunningTasks(ArrayList<RunningTaskInfo> runningTasks) {
+    private void initRunningTasks(List<RunningTaskInfo> runningTasks) {
         // Tasks are retrieved in order of most recently launched/used to least recently launched.
         mRunningTasks = new ArrayList<>(runningTasks);
         Collections.reverse(mRunningTasks);
@@ -270,6 +307,27 @@
         return mRunningTasks;
     }
 
+    @Override
+    public void onDeskAdded(int displayId, int deskId) {
+        onRecentTasksChanged();
+    }
+
+    @Override
+    public void onDeskRemoved(int displayId, int deskId) {
+        onRecentTasksChanged();
+    }
+
+    @Override
+    public void onActiveDeskChanged(int displayId, int newActiveDesk, int oldActiveDesk) {
+        // Should desk activation changes lead to the invalidation of the loaded tasks? The cases
+        // are:
+        // - Switching from one active desk to another.
+        // - Switching from out of a desk session into an active desk.
+        // - Switching from an active desk to a non-desk session.
+        // These changes don't affect the list of desks, nor their contents, so let's ignore them
+        // for now.
+    }
+
     private void onRunningTaskAppeared(RunningTaskInfo taskInfo) {
         // Make sure this task is not already in the list
         for (RunningTaskInfo existingTask : mRunningTasks) {
@@ -338,83 +396,129 @@
 
         TaskLoadResult allTasks = new TaskLoadResult(requestId, loadKeysOnly, rawTasks.size());
 
-        int numVisibleTasks = 0;
+        boolean isFirstVisibleTaskFound = false;
         for (GroupedTaskInfo rawTask : rawTasks) {
-            if (rawTask.getType() == TYPE_FREEFORM) {
-                // TYPE_FREEFORM tasks is only created when desktop mode can be entered,
-                // leftover TYPE_FREEFORM tasks created when flag was on should be ignored.
+            if (rawTask.isBaseType(TYPE_DESK)) {
+                // TYPE_DESK tasks is only created when desktop mode can be entered,
+                // leftover TYPE_DESK tasks created when flag was on should be ignored.
                 if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
-                    GroupTask desktopTask = createDesktopTask(rawTask);
-                    if (desktopTask != null) {
-                        allTasks.add(desktopTask);
+                    List<DesktopTask> desktopTasks = createDesktopTasks(
+                            rawTask.getBaseGroupedTask());
+                    allTasks.addAll(desktopTasks);
+
+                    // If any task in desktop group task is visible, set isFirstVisibleTaskFound to
+                    // true. This way if there is a transparent task in the list later on, it does
+                    // not get its own tile in Overview.
+                    if (rawTask.getBaseGroupedTask().getTaskInfoList().stream().anyMatch(
+                            taskInfo -> taskInfo.isVisible)) {
+                        isFirstVisibleTaskFound = true;
                     }
                 }
                 continue;
             }
-            TaskInfo taskInfo1 = rawTask.getTaskInfo1();
-            TaskInfo taskInfo2 = rawTask.getTaskInfo2();
-            Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
-            Task task1 = loadKeysOnly
-                    ? new Task(task1Key)
-                    : Task.from(task1Key, taskInfo1,
-                            tmpLockedUsers.get(task1Key.userId) /* isLocked */);
-            Task task2 = null;
-            if (taskInfo2 != null) {
-                // Is split task
-                Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
-                task2 = loadKeysOnly
-                        ? new Task(task2Key)
-                        : Task.from(task2Key, taskInfo2,
-                                tmpLockedUsers.get(task2Key.userId) /* isLocked */);
+
+            if (Flags.enableShellTopTaskTracking()) {
+                final TaskInfo taskInfo1 = rawTask.getBaseGroupedTask().getTaskInfo1();
+                final Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
+                final Task task1 = Task.from(task1Key, taskInfo1,
+                        tmpLockedUsers.get(task1Key.userId) /* isLocked */);
+
+                if (rawTask.isBaseType(TYPE_SPLIT)) {
+                    final TaskInfo taskInfo2 = rawTask.getBaseGroupedTask().getTaskInfo2();
+                    final Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
+                    final Task task2 = Task.from(task2Key, taskInfo2,
+                            tmpLockedUsers.get(task2Key.userId) /* isLocked */);
+                    final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
+                            convertShellSplitBoundsToLauncher(
+                                    rawTask.getBaseGroupedTask().getSplitBounds());
+                    allTasks.add(new SplitTask(task1, task2, launcherSplitBounds));
+                } else {
+                    allTasks.add(new SingleTask(task1));
+                }
             } else {
-                // Is fullscreen task
-                if (numVisibleTasks > 0) {
-                    boolean isExcluded = (taskInfo1.baseIntent.getFlags()
-                            & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
-                    if (taskInfo1.isTopActivityTransparent && isExcluded) {
-                        // If there are already visible tasks, then ignore the excluded tasks and
-                        // don't add them to the returned list
-                        continue;
+                TaskInfo taskInfo1 = rawTask.getTaskInfo1();
+                TaskInfo taskInfo2 = rawTask.getTaskInfo2();
+                Task.TaskKey task1Key = new Task.TaskKey(taskInfo1);
+                Task task1 = loadKeysOnly
+                        ? new Task(task1Key)
+                        : Task.from(task1Key, taskInfo1,
+                                tmpLockedUsers.get(task1Key.userId) /* isLocked */);
+                Task task2 = null;
+                if (taskInfo2 != null) {
+                    // Is split task
+                    Task.TaskKey task2Key = new Task.TaskKey(taskInfo2);
+                    task2 = loadKeysOnly
+                            ? new Task(task2Key)
+                            : Task.from(task2Key, taskInfo2,
+                                    tmpLockedUsers.get(task2Key.userId) /* isLocked */);
+                } else {
+                    // Is fullscreen task
+                    if (isFirstVisibleTaskFound) {
+                        boolean isExcluded = (taskInfo1.baseIntent.getFlags()
+                                & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
+                        if (taskInfo1.isTopActivityTransparent && isExcluded) {
+                            // If there are already visible tasks, then ignore the excluded tasks
+                            // and don't add them to the returned list
+                            continue;
+                        }
                     }
                 }
+                if (taskInfo1.isVisible) {
+                    isFirstVisibleTaskFound = true;
+                }
+                if (task2 != null) {
+                    Objects.requireNonNull(rawTask.getSplitBounds());
+                    final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
+                            convertShellSplitBoundsToLauncher(rawTask.getSplitBounds());
+                    allTasks.add(new SplitTask(task1, task2, launcherSplitBounds));
+                } else {
+                    allTasks.add(new SingleTask(task1));
+                }
             }
-            if (taskInfo1.isVisible) {
-                numVisibleTasks++;
-            }
-            final SplitConfigurationOptions.SplitBounds launcherSplitBounds =
-                    convertShellSplitBoundsToLauncher(rawTask.getSplitBounds());
-            allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
         }
 
         return allTasks;
     }
 
-    private @Nullable DesktopTask createDesktopTask(GroupedTaskInfo recentTaskInfo) {
-        ArrayList<Task> tasks = new ArrayList<>(recentTaskInfo.getTaskInfoList().size());
-        int[] minimizedTaskIds = recentTaskInfo.getMinimizedTaskIds();
-        if (minimizedTaskIds.length == recentTaskInfo.getTaskInfoList().size()) {
-            // All Tasks are minimized -> don't create a DesktopTask
-            return null;
-        }
-        for (TaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) {
-            Task.TaskKey key = new Task.TaskKey(taskInfo);
-            Task task = Task.from(key, taskInfo, false);
-            task.positionInParent = taskInfo.positionInParent;
-            task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds();
-            task.isVisible = taskInfo.isVisible;
-            task.isMinimized =
-                    Arrays.stream(minimizedTaskIds).anyMatch(taskId -> taskId == taskInfo.taskId);
-            tasks.add(task);
-        }
-        return new DesktopTask(tasks);
+    private Task createTask(TaskInfo taskInfo, Set<Integer> minimizedTaskIds) {
+        Task.TaskKey key = new Task.TaskKey(taskInfo);
+        Task task = Task.from(key, taskInfo, false);
+        task.positionInParent = taskInfo.positionInParent;
+        task.appBounds = taskInfo.configuration.windowConfiguration.getAppBounds();
+        task.isVisible = taskInfo.isVisible;
+        task.isMinimized = minimizedTaskIds.contains(taskInfo.taskId);
+        return task;
     }
 
-    private ArrayList<GroupTask> copyOf(ArrayList<GroupTask> tasks) {
-        ArrayList<GroupTask> newTasks = new ArrayList<>();
-        for (int i = 0; i < tasks.size(); i++) {
-            newTasks.add(tasks.get(i).copy());
+    private List<DesktopTask> createDesktopTasks(GroupedTaskInfo recentTaskInfo) {
+        int[] minimizedTaskIdArray = recentTaskInfo.getMinimizedTaskIds();
+        Set<Integer> minimizedTaskIds = minimizedTaskIdArray != null
+                ? CollectionsKt.toSet(ArraysKt.asIterable(minimizedTaskIdArray))
+                : Collections.emptySet();
+        if (enableSeparateExternalDisplayTasks()
+                && !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) {
+            // This code is not needed when the multiple desktop feature is enabled, since Shell
+            // will send a single `GroupedTaskInfo` for each desk with a unique `deskId` across
+            // all displays.
+            Map<Integer, List<Task>> perDisplayTasks = new HashMap<>();
+            for (TaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) {
+                Task task = createTask(taskInfo, minimizedTaskIds);
+                List<Task> tasks = perDisplayTasks.computeIfAbsent(
+                        ExternalDisplaysKt.getDisplayId(task),
+                        k -> new ArrayList<>());
+                tasks.add(task);
+            }
+            // When the multiple desktop feature is disabled, there can only be up to a single desk
+            // on each display, The desk ID doesn't matter and should not be used.
+            return MapsKt.map(perDisplayTasks,
+                    it -> new DesktopTask(DesktopVisibilityController.INACTIVE_DESK_ID,
+                            it.getValue()));
+        } else {
+            final int deskId = recentTaskInfo.getDeskId();
+            List<Task> tasks = CollectionsKt.map(recentTaskInfo.getTaskInfoList(),
+                    it -> createTask(it, minimizedTaskIds));
+            return List.of(new DesktopTask(deskId, tasks));
         }
-        return newTasks;
     }
 
     public void dump(String prefix, PrintWriter writer) {
@@ -422,14 +526,12 @@
         writer.println(prefix + "  mChangeId=" + mChangeId);
         writer.println(prefix + "  mResultsUi=[id=" + mResultsUi.mRequestId + ", tasks=");
         for (GroupTask task : mResultsUi) {
-            Task task1 = task.task1;
-            Task task2 = task.task2;
-            ComponentName cn1 = task1.getTopComponent();
-            ComponentName cn2 = task2 != null ? task2.getTopComponent() : null;
-            writer.println(prefix + "    t1: (id=" + task1.key.id
-                    + "; package=" + (cn1 != null ? cn1.getPackageName() + ")" : "no package)")
-                    + " t2: (id=" + (task2 != null ? task2.key.id : "-1")
-                    + "; package=" + (cn2 != null ? cn2.getPackageName() + ")" : "no package)"));
+            int count = 0;
+            for (Task t : task.getTasks()) {
+                ComponentName cn = t.getTopComponent();
+                writer.println(prefix + "    t" + (++count) + ": (id=" + t.key.id
+                        + "; package=" + (cn != null ? cn.getPackageName() + ")" : "no package)"));
+            }
         }
         writer.println(prefix + "  ]");
         int currentUserId = Process.myUserHandle().getIdentifier();
@@ -441,14 +543,7 @@
         }
         writer.println(prefix + "  rawTasks=[");
         for (GroupedTaskInfo task : rawTasks) {
-            TaskInfo taskInfo1 = task.getTaskInfo1();
-            TaskInfo taskInfo2 = task.getTaskInfo2();
-            ComponentName cn1 = taskInfo1.topActivity;
-            ComponentName cn2 = taskInfo2 != null ? taskInfo2.topActivity : null;
-            writer.println(prefix + "    t1: (id=" + taskInfo1.taskId
-                    + "; package=" + (cn1 != null ? cn1.getPackageName() + ")" : "no package)")
-                    + " t2: (id=" + (taskInfo2 != null ? taskInfo2.taskId : "-1")
-                    + "; package=" + (cn2 != null ? cn2.getPackageName() + ")" : "no package)"));
+            writer.println(prefix + task);
         }
         writer.println(prefix + "  ]");
     }
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 834cf44..3d12fdf 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -26,6 +26,7 @@
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
 import static com.android.launcher3.testing.shared.TestProtocol.LAUNCHER_ACTIVITY_STOPPED_MESSAGE;
 import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL;
+import static com.android.launcher3.util.WallpaperThemeManager.setWallpaperDependentTheme;
 import static com.android.quickstep.OverviewComponentObserver.startHomeIntentSafely;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
@@ -65,7 +66,6 @@
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.desktop.DesktopRecentsTransitionController;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
@@ -248,7 +248,6 @@
 
     @Override
     public void returnToHomescreen() {
-        super.returnToHomescreen();
         // TODO(b/137318995) This should go home, but doing so removes freeform windows
     }
 
@@ -262,6 +261,7 @@
         }
     }
 
+    @NonNull
     @Override
     public ActivityOptionsWrapper getActivityLaunchOptions(final View v, @Nullable ItemInfo item) {
         if (!(v instanceof TaskView)) {
@@ -319,7 +319,7 @@
     /**
      * Composes the animations for a launch from the recents list if possible.
      */
-    private AnimatorSet  composeRecentsLaunchAnimator(
+    private AnimatorSet composeRecentsLaunchAnimator(
             @NonNull RecentsView recentsView,
             @NonNull TaskView taskView,
             RemoteAnimationTarget[] appTargets,
@@ -329,7 +329,8 @@
         boolean activityClosing = taskIsATargetWithMode(appTargets, getTaskId(), MODE_CLOSING);
         PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
         createRecentsWindowAnimator(recentsView, taskView, !activityClosing, appTargets,
-                wallpaperTargets, nonAppTargets, null /* depthController */, pa);
+                wallpaperTargets, nonAppTargets, /* depthController= */ null ,
+                /* transitionInfo= */ null, pa);
         target.play(pa.buildAnim());
 
         // Found a visible recents task that matches the opening app, lets launch the app from there
@@ -372,6 +373,7 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        setWallpaperDependentTheme(this);
 
         mStateManager = new StateManager<>(this, RecentsState.BG_LAUNCHER);
 
@@ -432,7 +434,6 @@
      */
     private void initDeviceProfile() {
         mDeviceProfile = createDeviceProfile();
-        onDeviceProfileInitiated();
     }
 
     @Override
@@ -453,6 +454,10 @@
 
     @Override
     protected void onDestroy() {
+        RecentsView recentsView = getOverviewPanel();
+        if (recentsView != null) {
+            recentsView.destroy();
+        }
         super.onDestroy();
         ACTIVITY_TRACKER.onContextDestroyed(this);
         mActivityLaunchAnimationRunner = null;
@@ -550,10 +555,4 @@
     public boolean isRecentsViewVisible() {
         return getStateManager().getState().isRecentsViewVisible();
     }
-
-    @Nullable
-    @Override
-    public DesktopVisibilityController getDesktopVisibilityController() {
-        return mTISBindHelper.getDesktopVisibilityController();
-    }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 8fc1a78..ecde37b 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -21,19 +21,22 @@
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.wm.shell.shared.TransitionUtil.TYPE_SPLIT_SCREEN_DIM_LAYER;
 
+import android.annotation.Nullable;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.util.ArraySet;
 import android.view.RemoteAnimationTarget;
+import android.window.TransitionInfo;
 
 import androidx.annotation.BinderThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
 
-import com.android.launcher3.Flags;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.Preconditions;
+import com.android.quickstep.fallback.window.RecentsWindowFlags;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -93,7 +96,7 @@
             RemoteAnimationTarget[] appTargets, Rect homeContentInsets,
             Rect minimizedHomeBounds, Bundle extras) {
         onAnimationStart(controller, appTargets, new RemoteAnimationTarget[0],
-                homeContentInsets, minimizedHomeBounds, extras);
+                homeContentInsets, minimizedHomeBounds, extras, /* transitionInfo= */ null);
     }
 
     // Called only in R+ platform
@@ -101,7 +104,8 @@
     public final void onAnimationStart(RecentsAnimationControllerCompat animationController,
             RemoteAnimationTarget[] appTargets,
             RemoteAnimationTarget[] wallpaperTargets,
-            Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras) {
+            Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras,
+            @Nullable TransitionInfo transitionInfo) {
         long appCount = Arrays.stream(appTargets)
                 .filter(app -> app.mode == MODE_CLOSING)
                 .count();
@@ -109,8 +113,8 @@
         boolean isOpeningHome = Arrays.stream(appTargets).filter(app -> app.mode == MODE_OPENING
                         && app.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME)
                 .count() > 0;
-        if (appCount == 0 && (!(Flags.enableFallbackOverviewInWindow()
-                || Flags.enableLauncherOverviewInWindow()) || isOpeningHome)) {
+        if (appCount == 0 && (!RecentsWindowFlags.Companion.getEnableOverviewInWindow()
+                || isOpeningHome)) {
             ActiveGestureProtoLogProxy.logOnRecentsAnimationStartCancelled();
             // Edge case, if there are no closing app targets, then Launcher has nothing to handle
             notifyAnimationCanceled();
@@ -141,7 +145,7 @@
             Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
                 ActiveGestureProtoLogProxy.logOnRecentsAnimationStart(targets.apps.length);
                 for (RecentsAnimationListener listener : getListeners()) {
-                    listener.onRecentsAnimationStart(mController, targets);
+                    listener.onRecentsAnimationStart(mController, targets, transitionInfo);
                 }
             });
         }
@@ -160,11 +164,12 @@
 
     @BinderThread
     @Override
-    public void onTasksAppeared(RemoteAnimationTarget[] apps) {
+    public void onTasksAppeared(
+            RemoteAnimationTarget[] apps, @Nullable TransitionInfo transitionInfo) {
         Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
             ActiveGestureProtoLogProxy.logRecentsAnimationCallbacksOnTasksAppeared();
             for (RecentsAnimationListener listener : getListeners()) {
-                listener.onTasksAppeared(apps);
+                listener.onTasksAppeared(apps, transitionInfo);
             }
         });
     }
@@ -186,7 +191,8 @@
             ArrayList<RemoteAnimationTarget> apps, ArrayList<RemoteAnimationTarget> nonApps) {
         for (int i = 0; i < appTargets.length; i++) {
             RemoteAnimationTarget target = appTargets[i];
-            if (target.windowType == TYPE_DOCK_DIVIDER) {
+            if (target.windowType == TYPE_DOCK_DIVIDER
+                    || target.windowType == TYPE_SPLIT_SCREEN_DIM_LAYER) {
                 nonApps.add(target);
             } else {
                 apps.add(target);
@@ -205,7 +211,7 @@
      */
     public interface RecentsAnimationListener {
         default void onRecentsAnimationStart(RecentsAnimationController controller,
-                RecentsAnimationTargets targets) {}
+                RecentsAnimationTargets targets, @Nullable TransitionInfo transitionInfo) {}
 
         /**
          * Callback from the system when the recents animation is canceled. {@param thumbnailData}
@@ -222,6 +228,7 @@
         /**
          * Callback made when a task started from the recents is ready for an app transition.
          */
-        default void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTarget) {}
+        default void onTasksAppeared(@NonNull RemoteAnimationTarget[] appearedTaskTarget,
+                @Nullable TransitionInfo transitionInfo) {}
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 145773d..865cc47 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -36,6 +36,7 @@
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
 import com.android.systemui.animation.TransitionAnimator;
 import com.android.systemui.shared.recents.model.ThumbnailData;
+import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
 import com.android.wm.shell.recents.IRecentsAnimationController;
@@ -71,7 +72,7 @@
      * currently being animated.
      */
     public ThumbnailData screenshotTask(int taskId) {
-        return mController.screenshotTask(taskId);
+        return ActivityManagerWrapper.getInstance().takeTaskThumbnail(taskId);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index e296449..6710096 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -37,7 +37,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
@@ -64,6 +64,11 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
@@ -83,21 +88,27 @@
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.inject.Inject;
 
 /**
  * Manages the state of the system during a swipe up gesture.
  */
+@LauncherAppSingleton
 public class RecentsAnimationDeviceState implements DisplayInfoChangeListener, ExclusionListener {
 
-    private static final String TAG = "RecentsAnimationDeviceState";
-
     static final String SUPPORT_ONE_HANDED_MODE = "ro.support_one_handed_mode";
 
     // TODO: Move to quickstep contract
     private static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 3f;
     private static final float QUICKSTEP_TOUCH_SLOP_RATIO_GESTURAL = 1.414f;
 
+    public static DaggerSingletonObject<RecentsAnimationDeviceState> INSTANCE =
+            new DaggerSingletonObject<>(LauncherAppComponent::getRecentsAnimationDeviceState);
+
     private final Context mContext;
     private final DisplayController mDisplayController;
 
@@ -110,9 +121,8 @@
     private final boolean mCanImeRenderGesturalNavButtons =
             InputMethodService.canImeRenderGesturalNavButtons();
 
-    private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
-
     private @SystemUiStateFlags long mSystemUiStateFlags = QuickStepContract.SYSUI_STATE_AWAKE;
+    private final Map<Integer, Long> mSysUIStateFlagsPerDisplay = new ConcurrentHashMap<>();
     private NavigationMode mMode = THREE_BUTTONS;
     private NavBarPosition mNavBarPosition;
 
@@ -130,55 +140,38 @@
     private @NonNull Region mExclusionRegion = GestureExclusionManager.EMPTY_REGION;
     private boolean mExclusionListenerRegistered;
 
-    public RecentsAnimationDeviceState(Context context) {
-        this(context, false, GestureExclusionManager.INSTANCE);
-    }
-
-    public RecentsAnimationDeviceState(Context context, boolean isInstanceForTouches) {
-        this(context, isInstanceForTouches, GestureExclusionManager.INSTANCE);
-    }
-
     @VisibleForTesting
-    RecentsAnimationDeviceState(Context context, GestureExclusionManager exclusionManager) {
-        this(context, false, exclusionManager);
-    }
-
-    /**
-     * @param isInstanceForTouches {@code true} if this is the persistent instance being used for
-     *                                   gesture touch handling
-     */
+    @Inject
     RecentsAnimationDeviceState(
-            Context context, boolean isInstanceForTouches,
-            GestureExclusionManager exclusionManager) {
+            @ApplicationContext Context context,
+            GestureExclusionManager exclusionManager,
+            DisplayController displayController,
+            ContextualSearchStateManager contextualSearchStateManager,
+            RotationTouchHelper rotationTouchHelper,
+            SettingsCache settingsCache,
+            DaggerSingletonTracker lifeCycle) {
         mContext = context;
-        mDisplayController = DisplayController.INSTANCE.get(context);
+        mDisplayController = displayController;
         mExclusionManager = exclusionManager;
-        mContextualSearchStateManager = ContextualSearchStateManager.INSTANCE.get(context);
+        mContextualSearchStateManager = contextualSearchStateManager;
+        mRotationTouchHelper = rotationTouchHelper;
         mIsOneHandedModeSupported = SystemProperties.getBoolean(SUPPORT_ONE_HANDED_MODE, false);
-        mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
-        if (isInstanceForTouches) {
-            // rotationTouchHelper doesn't get initialized after being destroyed, so only destroy
-            // if primary TouchInteractionService instance needs to be destroyed.
-            mRotationTouchHelper.init();
-            runOnDestroy(mRotationTouchHelper::destroy);
-        }
 
         // Register for exclusion updates
-        runOnDestroy(() -> unregisterExclusionListener());
+        lifeCycle.addCloseable(this::unregisterExclusionListener);
 
         // Register for display changes changes
         mDisplayController.addChangeListener(this);
         onDisplayInfoChanged(context, mDisplayController.getInfo(), CHANGE_ALL);
-        runOnDestroy(() -> mDisplayController.removeChangeListener(this));
+        lifeCycle.addCloseable(() -> mDisplayController.removeChangeListener(this));
 
-        SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
         if (mIsOneHandedModeSupported) {
             Uri oneHandedUri = Settings.Secure.getUriFor(ONE_HANDED_ENABLED);
             SettingsCache.OnChangeListener onChangeListener =
                     enabled -> mIsOneHandedModeEnabled = enabled;
             settingsCache.register(oneHandedUri, onChangeListener);
             mIsOneHandedModeEnabled = settingsCache.getValue(oneHandedUri);
-            runOnDestroy(() -> settingsCache.unregister(oneHandedUri, onChangeListener));
+            lifeCycle.addCloseable(() -> settingsCache.unregister(oneHandedUri, onChangeListener));
         } else {
             mIsOneHandedModeEnabled = false;
         }
@@ -189,14 +182,16 @@
                 enabled -> mIsSwipeToNotificationEnabled = enabled;
         settingsCache.register(swipeBottomNotificationUri, onChangeListener);
         mIsSwipeToNotificationEnabled = settingsCache.getValue(swipeBottomNotificationUri);
-        runOnDestroy(() -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener));
+        lifeCycle.addCloseable(
+                () -> settingsCache.unregister(swipeBottomNotificationUri, onChangeListener));
 
         Uri setupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE);
         mIsUserSetupComplete = settingsCache.getValue(setupCompleteUri, 0);
         if (!mIsUserSetupComplete) {
             SettingsCache.OnChangeListener userSetupChangeListener = e -> mIsUserSetupComplete = e;
             settingsCache.register(setupCompleteUri, userSetupChangeListener);
-            runOnDestroy(() -> settingsCache.unregister(setupCompleteUri, userSetupChangeListener));
+            lifeCycle.addCloseable(
+                    () -> settingsCache.unregister(setupCompleteUri, userSetupChangeListener));
         }
 
         try {
@@ -217,28 +212,19 @@
             }
         };
         TaskStackChangeListeners.getInstance().registerTaskStackListener(mPipListener);
-        runOnDestroy(() ->
+        lifeCycle.addCloseable(() ->
                 TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mPipListener));
     }
 
-    private void runOnDestroy(Runnable action) {
-        mOnDestroyActions.add(action);
-    }
-
-    /**
-     * Cleans up all the registered listeners and receivers.
-     */
-    public void destroy() {
-        for (Runnable r : mOnDestroyActions) {
-            r.run();
-        }
-    }
-
     /**
      * Adds a listener for the nav mode change, guaranteed to be called after the device state's
      * mode has changed.
+     *
+     * @return Added {@link DisplayInfoChangeListener} so that caller is
+     * responsible for removing the listener from {@link DisplayController} to avoid memory leak.
      */
-    public void addNavigationModeChangedCallback(Runnable callback) {
+    public DisplayController.DisplayInfoChangeListener addNavigationModeChangedCallback(
+            Runnable callback) {
         DisplayController.DisplayInfoChangeListener listener = (context, info, flags) -> {
             if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
                 callback.run();
@@ -246,7 +232,16 @@
         };
         mDisplayController.addChangeListener(listener);
         callback.run();
-        runOnDestroy(() -> mDisplayController.removeChangeListener(listener));
+        return listener;
+    }
+
+    /**
+     * Remove the {DisplayController.DisplayInfoChangeListener} added from
+     * {@link #addNavigationModeChangedCallback} when {@link TouchInteractionService} is destroyed.
+     */
+    public void removeDisplayInfoChangeListener(
+            DisplayController.DisplayInfoChangeListener listener) {
+        mDisplayController.removeChangeListener(listener);
     }
 
     @Override
@@ -330,13 +325,6 @@
     }
 
     /**
-     * @return the display id for the display that Launcher is running on.
-     */
-    public int getDisplayId() {
-        return DEFAULT_DISPLAY;
-    }
-
-    /**
      * @return whether the user has completed setup wizard
      */
     public boolean isUserSetupComplete() {
@@ -362,22 +350,51 @@
     }
 
     /**
-     * Updates the system ui state flags from SystemUI.
+     * Updates the system ui state flags from SystemUI for a specific display.
+     *
+     * @param stateFlags the current {@link SystemUiStateFlags} for the display.
+     * @param displayId  the display's ID.
      */
-    public void setSystemUiFlags(@SystemUiStateFlags long stateFlags) {
-        mSystemUiStateFlags = stateFlags;
+    public void setSysUIStateFlagsForDisplay(@SystemUiStateFlags long stateFlags,
+            int displayId) {
+        mSysUIStateFlagsPerDisplay.put(displayId, stateFlags);
     }
 
     /**
-     * @return the system ui state flags.
+     * Clears the system ui state flags for a specific display. This is called when the display is
+     * destroyed.
+     *
+     * @param displayId the display's ID.
+     */
+    public void clearSysUIStateFlagsForDisplay(int displayId) {
+        mSysUIStateFlagsPerDisplay.remove(displayId);
+    }
+
+    /**
+     * @return the system ui state flags for the default display.
      */
     // TODO(141886704): See if we can remove this
     @SystemUiStateFlags
-    public long getSystemUiStateFlags() {
-        return mSystemUiStateFlags;
+    public long getSysuiStateFlag() {
+        return getSystemUiStateFlags(DEFAULT_DISPLAY);
     }
 
     /**
+     * @return the system ui state flags for a given display ID.
+     */
+    @SystemUiStateFlags
+    public long getSystemUiStateFlags(int displayId) {
+        return mSysUIStateFlagsPerDisplay.getOrDefault(displayId,
+                QuickStepContract.SYSUI_STATE_AWAKE);
+    }
+
+    /**
+     * @return the display ids that have sysui state.
+     */
+    public Set<Integer> getDisplaysWithSysUIState() {
+        return mSysUIStateFlagsPerDisplay.keySet();
+    }
+    /**
      * Sets the flag that indicates whether a predictive back-to-home animation is in progress
      */
     public void setPredictiveBackToHomeInProgress(boolean isInProgress) {
@@ -395,8 +412,8 @@
      * @return whether SystemUI is in a state where we can start a system gesture.
      */
     public boolean canStartSystemGesture() {
-        boolean canStartWithNavHidden = (mSystemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
-                || (mSystemUiStateFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0
+        boolean canStartWithNavHidden = (getSysuiStateFlag() & SYSUI_STATE_NAV_BAR_HIDDEN) == 0
+                || (getSysuiStateFlag() & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0
                 || mRotationTouchHelper.isTaskListFrozen();
         return canStartWithNavHidden && canStartAnyGesture();
     }
@@ -408,7 +425,7 @@
      */
     public boolean canStartTrackpadGesture() {
         boolean trackpadGesturesEnabled =
-                (mSystemUiStateFlags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) == 0;
+                (getSysuiStateFlag() & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) == 0;
         return trackpadGesturesEnabled && canStartAnyGesture();
     }
 
@@ -416,8 +433,8 @@
      * 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;
+        boolean homeOrOverviewEnabled = (getSysuiStateFlag() & SYSUI_STATE_HOME_DISABLED) == 0
+                || (getSysuiStateFlag() & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
         long gestureDisablingStates = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
                         | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
                         | SYSUI_STATE_QUICK_SETTINGS_EXPANDED
@@ -425,7 +442,7 @@
                         | SYSUI_STATE_DEVICE_DREAMING
                         | SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION
                         | SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
-        return (gestureDisablingStates & mSystemUiStateFlags) == 0 && homeOrOverviewEnabled;
+        return (gestureDisablingStates & getSysuiStateFlag()) == 0 && homeOrOverviewEnabled;
     }
 
     /**
@@ -433,35 +450,35 @@
      *         (like camera or maps)
      */
     public boolean isKeyguardShowingOccluded() {
-        return (mSystemUiStateFlags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0;
+        return (getSysuiStateFlag() & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0;
     }
 
     /**
      * @return whether screen pinning is enabled and active
      */
     public boolean isScreenPinningActive() {
-        return (mSystemUiStateFlags & SYSUI_STATE_SCREEN_PINNING) != 0;
+        return (getSysuiStateFlag() & SYSUI_STATE_SCREEN_PINNING) != 0;
     }
 
     /**
      * @return whether assistant gesture is constraint
      */
     public boolean isAssistantGestureIsConstrained() {
-        return (mSystemUiStateFlags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0;
+        return (getSysuiStateFlag() & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0;
     }
 
     /**
      * @return whether the bubble stack is expanded
      */
     public boolean isBubblesExpanded() {
-        return (mSystemUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
+        return (getSysuiStateFlag() & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
     }
 
     /**
      * @return whether the global actions dialog is showing
      */
     public boolean isSystemUiDialogShowing() {
-        return (mSystemUiStateFlags & SYSUI_STATE_DIALOG_SHOWING) != 0;
+        return (getSysuiStateFlag() & SYSUI_STATE_DIALOG_SHOWING) != 0;
     }
 
     /**
@@ -475,35 +492,35 @@
      * @return whether the accessibility menu is available.
      */
     public boolean isAccessibilityMenuAvailable() {
-        return (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
+        return (getSysuiStateFlag() & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
     }
 
     /**
      * @return whether the accessibility menu shortcut is available.
      */
     public boolean isAccessibilityMenuShortcutAvailable() {
-        return (mSystemUiStateFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
+        return (getSysuiStateFlag() & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
     }
 
     /**
      * @return whether home is disabled (either by SUW/SysUI/device policy)
      */
     public boolean isHomeDisabled() {
-        return (mSystemUiStateFlags & SYSUI_STATE_HOME_DISABLED) != 0;
+        return (getSysuiStateFlag() & SYSUI_STATE_HOME_DISABLED) != 0;
     }
 
     /**
      * @return whether overview is disabled (either by SUW/SysUI/device policy)
      */
     public boolean isOverviewDisabled() {
-        return (mSystemUiStateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
+        return (getSysuiStateFlag() & SYSUI_STATE_OVERVIEW_DISABLED) != 0;
     }
 
     /**
      * @return whether one-handed mode is enabled and active
      */
     public boolean isOneHandedModeActive() {
-        return (mSystemUiStateFlags & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
+        return (getSysuiStateFlag() & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
     }
 
     /**
@@ -566,7 +583,7 @@
      */
     public boolean canTriggerAssistantAction(MotionEvent ev) {
         return mAssistantAvailable
-                && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
+                && !QuickStepContract.isAssistantGestureDisabled(getSysuiStateFlag())
                 && mRotationTouchHelper.touchInAssistantRegion(ev)
                 && !isTrackpadScroll(ev)
                 && !isLockToAppActive();
@@ -603,14 +620,10 @@
         return mPipIsActive;
     }
 
-    public RotationTouchHelper getRotationTouchHelper() {
-        return mRotationTouchHelper;
-    }
-
     /** Returns whether IME is rendering nav buttons, and IME is currently showing. */
     public boolean isImeRenderingNavButtons() {
         return mCanImeRenderGesturalNavButtons && mMode == NO_BUTTON
-                && ((mSystemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0);
+                && ((getSysuiStateFlag() & SYSUI_STATE_IME_VISIBLE) != 0);
     }
 
     /**
@@ -642,24 +655,37 @@
         return touchSlop * touchSlop;
     }
 
+    /** Returns a string representation of the system ui state flags for the default display. */
     public String getSystemUiStateString() {
-        return  QuickStepContract.getSystemUiStateString(mSystemUiStateFlags);
+        return  getSystemUiStateString(getSysuiStateFlag());
+    }
+
+    /** Returns a string representation of the system ui state flags. */
+    public String getSystemUiStateString(long flags) {
+        return  QuickStepContract.getSystemUiStateString(flags);
     }
 
     public void dump(PrintWriter pw) {
         pw.println("DeviceState:");
         pw.println("  canStartSystemGesture=" + canStartSystemGesture());
-        pw.println("  systemUiFlags=" + mSystemUiStateFlags);
+        pw.println("  systemUiFlagsForDefaultDisplay=" + getSysuiStateFlag());
         pw.println("  systemUiFlagsDesc=" + getSystemUiStateString());
         pw.println("  assistantAvailable=" + mAssistantAvailable);
         pw.println("  assistantDisabled="
-                + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
+                + QuickStepContract.isAssistantGestureDisabled(getSysuiStateFlag()));
         pw.println("  isOneHandedModeEnabled=" + mIsOneHandedModeEnabled);
         pw.println("  isSwipeToNotificationEnabled=" + mIsSwipeToNotificationEnabled);
         pw.println("  deferredGestureRegion=" + mDeferredGestureRegion.getBounds());
         pw.println("  exclusionRegion=" + mExclusionRegion.getBounds());
         pw.println("  pipIsActive=" + mPipIsActive);
         pw.println("  predictiveBackToHomeInProgress=" + mIsPredictiveBackToHomeInProgress);
+        for (int displayId : mSysUIStateFlagsPerDisplay.keySet()) {
+            pw.println("  systemUiFlagsForDisplay" + displayId + "=" + getSystemUiStateFlags(
+                    displayId));
+            pw.println("  systemUiFlagsForDisplay" + displayId + "Desc=" + getSystemUiStateString(
+                    getSystemUiStateFlags(displayId)));
+        }
+        pw.println("  RotationTouchHelper:");
         mRotationTouchHelper.dump(pw);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
index cf7e499..0deb1ca 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationTargets.java
@@ -59,6 +59,10 @@
         if (!DesktopModeStatus.canEnterDesktopMode(context)) {
             return false;
         }
+        // TODO: b/400866688 - Check if we need to update this such that for an empty desk, we
+        //  receive a list of apps that contain only the Launcher and the `DesktopWallpaperActivity`
+        //  and both are fullscreen windowing mode. A desk can also have transparent modals and
+        //  immersive apps which may not have a "freeform" windowing mode.
         for (RemoteAnimationTarget target : apps) {
             if (target.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
                 return true;
diff --git a/quickstep/src/com/android/quickstep/RecentsFilterState.java b/quickstep/src/com/android/quickstep/RecentsFilterState.java
index ff6951d..1808a97 100644
--- a/quickstep/src/com/android/quickstep/RecentsFilterState.java
+++ b/quickstep/src/com/android/quickstep/RecentsFilterState.java
@@ -18,7 +18,10 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.quickstep.util.DesksUtils;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.views.TaskViewType;
+import com.android.systemui.shared.recents.model.Task;
 
 import java.util.HashMap;
 import java.util.List;
@@ -37,7 +40,7 @@
     public static final int MIN_FILTERING_TASK_COUNT = 2;
 
     // default filter that returns true for any input
-    public static final Predicate<GroupTask> DEFAULT_FILTER = (groupTask -> true);
+    public static final Predicate<GroupTask> EMPTY_FILTER = (groupTask -> true);
 
     // the package name to filter recent tasks by
     @Nullable
@@ -115,16 +118,43 @@
      * Returns a predicate for filtering out GroupTasks by package name.
      *
      * @param packageName package name to filter GroupTasks by
-     *                    if null, Predicate always returns true.
+     *                    if null, Predicate filters out desktop tasks with no non-minimized tasks,
+     *                    unless the multiple desks feature is enabled, which allows empty desks.
      */
     public static Predicate<GroupTask> getFilter(@Nullable String packageName) {
         if (packageName == null) {
-            return DEFAULT_FILTER;
+            return getDesktopTaskFilter();
         }
 
-        return (groupTask) -> (groupTask.task2 != null
-                && groupTask.task2.key.getPackageName().equals(packageName))
-                || groupTask.task1.key.getPackageName().equals(packageName);
+        return (groupTask) -> (groupTask.containsPackage(packageName)
+                && shouldKeepGroupTask(groupTask));
+    }
+
+    /**
+     * Returns a predicate that filters out desk tasks that contain no non-minimized desktop tasks,
+     * unless the multiple desks feature is enabled, which allows empty desks.
+     */
+    public static Predicate<GroupTask> getDesktopTaskFilter() {
+        return (groupTask -> shouldKeepGroupTask(groupTask));
+    }
+
+    /**
+     * Returns true if the given `groupTask` should be kept, and false if it should be filtered out.
+     * Desks will be filtered out if they are empty unless the multiple desks feature is enabled.
+     *
+     * @param groupTask The group task to check.
+     */
+    private static boolean shouldKeepGroupTask(GroupTask groupTask) {
+        if (groupTask.taskViewType != TaskViewType.DESKTOP) {
+            return true;
+        }
+
+        if (DesksUtils.areMultiDesksFlagsEnabled()) {
+            return true;
+        }
+
+        return groupTask.getTasks().stream()
+                .anyMatch(task -> !task.isMinimized);
     }
 
     /**
@@ -136,17 +166,9 @@
         Map<String, Integer> instanceCountMap = new HashMap<>();
 
         for (GroupTask groupTask : groupTasks) {
-            final String firstTaskPkgName = groupTask.task1.key.getPackageName();
-            final String secondTaskPkgName =
-                    groupTask.task2 == null ? null : groupTask.task2.key.getPackageName();
-
-            // increment the instance count for the first task's base activity package name
-            incrementOrAddIfNotExists(instanceCountMap, firstTaskPkgName);
-
-            // check if second task is non existent
-            if (secondTaskPkgName != null) {
-                // increment the instance count for the second task's base activity package name
-                incrementOrAddIfNotExists(instanceCountMap, secondTaskPkgName);
+            for (Task t : groupTask.getTasks()) {
+                final String taskPkgName = t.key.getPackageName();
+                incrementOrAddIfNotExists(instanceCountMap, taskPkgName);
             }
         }
 
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index d073580..e1adf3d 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 
+import android.annotation.SuppressLint;
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
@@ -37,11 +38,19 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.graphics.ThemeManager;
+import com.android.launcher3.graphics.ThemeManager.ThemeChangeListener;
 import com.android.launcher3.icons.IconProvider;
-import com.android.launcher3.icons.IconProvider.IconChangeListener;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Executors.SimpleThreadFactory;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.LockedUserState;
 import com.android.launcher3.util.SafeCloseable;
+import com.android.quickstep.dagger.QuickstepBaseAppComponent;
 import com.android.quickstep.recents.data.RecentTasksDataSource;
 import com.android.quickstep.recents.data.TaskVisualsChangeNotifier;
 import com.android.quickstep.util.DesktopTask;
@@ -53,6 +62,8 @@
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -62,17 +73,20 @@
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 
+import javax.inject.Inject;
+
 /**
  * Singleton class to load and manage recents model.
  */
 @TargetApi(Build.VERSION_CODES.O)
-public class RecentsModel implements RecentTasksDataSource, IconChangeListener,
-        TaskStackChangeListener, TaskVisualsChangeListener, TaskVisualsChangeNotifier,
-        SafeCloseable {
+@LauncherAppSingleton
+public class RecentsModel implements RecentTasksDataSource, TaskStackChangeListener,
+        TaskVisualsChangeListener, TaskVisualsChangeNotifier,
+        ThemeChangeListener {
 
     // We do not need any synchronization for this variable as its only written on UI thread.
-    public static final MainThreadInitializedObject<RecentsModel> INSTANCE =
-            new MainThreadInitializedObject<>(RecentsModel::new);
+    public static final DaggerSingletonObject<RecentsModel> INSTANCE =
+            new DaggerSingletonObject<>(QuickstepBaseAppComponent::getRecentsModel);
 
     private static final Executor RECENTS_MODEL_EXECUTOR = Executors.newSingleThreadExecutor(
             new SimpleThreadFactory("TaskThumbnailIconCache-", THREAD_PRIORITY_BACKGROUND));
@@ -80,43 +94,70 @@
     private final ConcurrentLinkedQueue<TaskVisualsChangeListener> mThumbnailChangeListeners =
             new ConcurrentLinkedQueue<>();
     private final Context mContext;
-
     private final RecentTasksList mTaskList;
     private final TaskIconCache mIconCache;
     private final TaskThumbnailCache mThumbnailCache;
-    private final ComponentCallbacks mCallbacks;
 
-    private final TaskStackChangeListeners mTaskStackChangeListeners;
-
-    private RecentsModel(Context context) {
-        this(context, new IconProvider(context));
+    @Inject
+     public RecentsModel(@ApplicationContext Context context,
+            SystemUiProxy systemUiProxy,
+            TopTaskTracker topTaskTracker,
+            DisplayController displayController,
+            LockedUserState lockedUserState,
+            Lazy<ThemeManager> themeManagerLazy,
+            DesktopVisibilityController desktopVisibilityController,
+            DaggerSingletonTracker tracker
+            ) {
+        // Lazily inject the ThemeManager and access themeManager once the device is
+        // unlocked. See b/393248495 for details.
+        this(context, new IconProvider(context), systemUiProxy, topTaskTracker,
+                displayController, lockedUserState, themeManagerLazy, desktopVisibilityController,
+                tracker);
     }
 
-    private RecentsModel(Context context, IconProvider iconProvider) {
+    @SuppressLint("VisibleForTests")
+    private RecentsModel(@ApplicationContext Context context,
+            IconProvider iconProvider,
+            SystemUiProxy systemUiProxy,
+            TopTaskTracker topTaskTracker,
+            DisplayController displayController,
+            LockedUserState lockedUserState,
+            Lazy<ThemeManager> themeManagerLazy,
+            DesktopVisibilityController desktopVisibilityController,
+            DaggerSingletonTracker tracker) {
         this(context,
                 new RecentTasksList(
                         context,
                         MAIN_EXECUTOR,
                         context.getSystemService(KeyguardManager.class),
-                        SystemUiProxy.INSTANCE.get(context),
-                        TopTaskTracker.INSTANCE.get(context)),
-                new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider),
+                        systemUiProxy,
+                        topTaskTracker, desktopVisibilityController, tracker),
+                new TaskIconCache(context, RECENTS_MODEL_EXECUTOR, iconProvider, displayController),
                 new TaskThumbnailCache(context, RECENTS_MODEL_EXECUTOR),
                 iconProvider,
-                TaskStackChangeListeners.getInstance());
+                TaskStackChangeListeners.getInstance(),
+                lockedUserState,
+                themeManagerLazy,
+                tracker);
     }
 
     @VisibleForTesting
-    RecentsModel(Context context, RecentTasksList taskList, TaskIconCache iconCache,
-            TaskThumbnailCache thumbnailCache, IconProvider iconProvider,
-            TaskStackChangeListeners taskStackChangeListeners) {
+    RecentsModel(@ApplicationContext Context context,
+            RecentTasksList taskList,
+            TaskIconCache iconCache,
+            TaskThumbnailCache thumbnailCache,
+            IconProvider iconProvider,
+            TaskStackChangeListeners taskStackChangeListeners,
+            LockedUserState lockedUserState,
+            Lazy<ThemeManager> themeManagerLazy,
+            DaggerSingletonTracker tracker) {
         mContext = context;
         mTaskList = taskList;
         mIconCache = iconCache;
         mIconCache.registerTaskVisualsChangeListener(this);
         mThumbnailCache = thumbnailCache;
         if (isCachePreloadingEnabled()) {
-            mCallbacks = new ComponentCallbacks() {
+            ComponentCallbacks componentCallbacks = new ComponentCallbacks() {
                 @Override
                 public void onConfigurationChanged(Configuration configuration) {
                     updateCacheSizeAndPreloadIfNeeded();
@@ -126,14 +167,27 @@
                 public void onLowMemory() {
                 }
             };
-            context.registerComponentCallbacks(mCallbacks);
-        } else {
-            mCallbacks = null;
+            context.registerComponentCallbacks(componentCallbacks);
+            tracker.addCloseable(() -> context.unregisterComponentCallbacks(componentCallbacks));
         }
 
-        mTaskStackChangeListeners = taskStackChangeListeners;
-        mTaskStackChangeListeners.registerTaskStackListener(this);
-        iconProvider.registerIconChangeListener(this, MAIN_EXECUTOR.getHandler());
+        taskStackChangeListeners.registerTaskStackListener(this);
+        SafeCloseable iconChangeCloseable = iconProvider.registerIconChangeListener(
+                this::onAppIconChanged, MAIN_EXECUTOR.getHandler());
+
+        Runnable unlockCallback = () -> themeManagerLazy.get().addChangeListener(this);
+        lockedUserState.runOnUserUnlocked(unlockCallback);
+
+        tracker.addCloseable(() -> {
+            taskStackChangeListeners.unregisterTaskStackListener(this);
+            iconChangeCloseable.close();
+            mIconCache.removeTaskVisualsChangeListener();
+            if (lockedUserState.isUserUnlocked()) {
+                themeManagerLazy.get().removeChangeListener(this);
+            } else {
+                lockedUserState.removeOnUserUnlockedRunnable(unlockCallback);
+            }
+        });
     }
 
     public TaskIconCache getIconCache() {
@@ -146,7 +200,7 @@
 
     /**
      * Fetches the list of recent tasks. Tasks are ordered by recency, with the latest active tasks
-     * at the end of the list.
+     * at the end of the list. Filters out desktop tasks that contain no non-minimized tasks.
      *
      * @param callback The callback to receive the task plan once its complete or null. This is
      *                always called on the UI thread.
@@ -155,7 +209,7 @@
     @Override
     public int getTasks(@Nullable Consumer<List<GroupTask>> callback) {
         return mTaskList.getTasks(false /* loadKeysOnly */, callback,
-                RecentsFilterState.DEFAULT_FILTER);
+                RecentsFilterState.getDesktopTaskFilter());
     }
 
     /**
@@ -231,8 +285,8 @@
                     // time the user next enters overview
                     continue;
                 }
-                mThumbnailCache.updateThumbnailInCache(group.task1, /* lowResolution= */ true);
-                mThumbnailCache.updateThumbnailInCache(group.task2, /* lowResolution= */ true);
+                group.getTasks().forEach(
+                        t -> mThumbnailCache.updateThumbnailInCache(t, /* lowResolution= */ true));
             }
         });
     }
@@ -268,8 +322,7 @@
         }
     }
 
-    @Override
-    public void onAppIconChanged(String packageName, UserHandle user) {
+    private void onAppIconChanged(String packageName, UserHandle user) {
         mIconCache.invalidateCacheEntries(packageName, user);
         for (TaskVisualsChangeListener listener : mThumbnailChangeListeners) {
             listener.onTaskIconChanged(packageName, user);
@@ -284,7 +337,7 @@
     }
 
     @Override
-    public void onSystemIconStateChanged(String iconState) {
+    public void onThemeChanged() {
         mIconCache.clearCache();
     }
 
@@ -367,8 +420,8 @@
 
         mTaskList.getTaskKeys(mThumbnailCache.getCacheSize(), taskGroups -> {
             for (GroupTask group : taskGroups) {
-                mThumbnailCache.updateThumbnailInCache(group.task1, /* lowResolution= */ false);
-                mThumbnailCache.updateThumbnailInCache(group.task2, /* lowResolution= */ false);
+                group.getTasks().forEach(
+                        t -> mThumbnailCache.updateThumbnailInCache(t, /* lowResolution= */ false));
             }
         });
     }
@@ -387,15 +440,6 @@
         }
     }
 
-    @Override
-    public void close() {
-        if (mCallbacks != null) {
-            mContext.unregisterComponentCallbacks(mCallbacks);
-        }
-        mIconCache.removeTaskVisualsChangeListener();
-        mTaskStackChangeListeners.unregisterTaskStackListener(this);
-    }
-
     private boolean isCachePreloadingEnabled() {
         return enableGridOnlyOverview() || enableRefactorTaskThumbnail();
     }
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 91d0776..f96bbcb 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -24,6 +24,7 @@
 import android.graphics.Rect;
 import android.util.Log;
 import android.view.RemoteAnimationTarget;
+import android.window.TransitionInfo;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -67,16 +68,13 @@
      * running tasks
      */
     public RemoteTargetGluer(Context context, BaseContainerInterface sizingStrategy) {
-        DesktopVisibilityController desktopVisibilityController =
-                sizingStrategy.getDesktopVisibilityController();
-        if (desktopVisibilityController != null) {
-            int visibleTasksCount = desktopVisibilityController.getVisibleDesktopTasksCount();
-            if (visibleTasksCount > 0) {
-                // Allocate +1 to account for a new task added to the desktop mode
-                int numHandles = visibleTasksCount + 1;
-                init(context, sizingStrategy, numHandles, true /* forDesktop */);
-                return;
-            }
+        int visibleTasksCount = DesktopVisibilityController.INSTANCE.get(context)
+                .getVisibleDesktopTasksCount();
+        if (visibleTasksCount > 0) {
+            // Allocate +1 to account for a new task added to the desktop mode
+            int numHandles = visibleTasksCount + 1;
+            init(context, sizingStrategy, numHandles, true /* forDesktop */);
+            return;
         }
 
         // Assume 2 handles needed for split, scale down as needed later on when we actually
@@ -144,7 +142,9 @@
         if (mSplitBounds == null) {
             SplitBounds shellSplitBounds = targets.extras.getParcelable(KEY_EXTRA_SPLIT_BOUNDS,
                     SplitBounds.class);
-            mSplitBounds = convertShellSplitBoundsToLauncher(shellSplitBounds);
+            if (shellSplitBounds != null) {
+                mSplitBounds = convertShellSplitBoundsToLauncher(shellSplitBounds);
+            }
         }
 
         boolean containsSplitTargets = mSplitBounds != null;
@@ -216,7 +216,8 @@
      * Similar to {@link #assignTargets(RemoteAnimationTargets)}, except this creates distinct
      * transform params per app in {@code targets.apps} list.
      */
-    public RemoteTargetHandle[] assignTargetsForDesktop(RemoteAnimationTargets targets) {
+    public RemoteTargetHandle[] assignTargetsForDesktop(
+            RemoteAnimationTargets targets, @Nullable TransitionInfo transitionInfo) {
         resizeRemoteTargetHandles(targets);
 
         for (int i = 0; i < mRemoteTargetHandles.length; i++) {
@@ -225,6 +226,7 @@
                     .filter(target -> target.taskId != primaryTaskTarget.taskId).toList();
             mRemoteTargetHandles[i].mTransformParams.setTargetSet(
                     createRemoteAnimationTargetsForTarget(targets, excludeTargets));
+            mRemoteTargetHandles[i].mTransformParams.setTransitionInfo(transitionInfo);
             mRemoteTargetHandles[i].mTaskViewSimulator.setPreview(primaryTaskTarget, null);
         }
         return mRemoteTargetHandles;
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index 909cc35..ae6cfa0 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -33,13 +33,16 @@
 import android.view.MotionEvent;
 import android.view.OrientationEventListener;
 
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
 import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.NavigationMode;
-import com.android.launcher3.util.SafeCloseable;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.systemui.shared.Flags;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -47,26 +50,27 @@
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
+
+import javax.inject.Inject;
 
 /**
  * Helper class for transforming touch events
  */
-public class RotationTouchHelper implements DisplayInfoChangeListener, SafeCloseable {
+@LauncherAppSingleton
+public class RotationTouchHelper implements DisplayInfoChangeListener {
 
-    public static final MainThreadInitializedObject<RotationTouchHelper> INSTANCE =
-            new MainThreadInitializedObject<>(RotationTouchHelper::new);
+    public static final DaggerSingletonObject<RotationTouchHelper> INSTANCE =
+            new DaggerSingletonObject<>(LauncherAppComponent::getRotationTouchHelper);
 
-    private OrientationTouchTransformer mOrientationTouchTransformer;
-    private DisplayController mDisplayController;
-    private int mDisplayId;
+    private final OrientationTouchTransformer mOrientationTouchTransformer;
+    private final DisplayController mDisplayController;
+    private final SystemUiProxy mSystemUiProxy;
+    private final int mDisplayId;
     private int mDisplayRotation;
 
-    private final ArrayList<Runnable> mOnDestroyActions = new ArrayList<>();
-
     private NavigationMode mMode = THREE_BUTTONS;
 
-    private TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() {
+    private final TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() {
         @Override
         public void onRecentTaskListFrozenChanged(boolean frozen) {
             mTaskListFrozen = frozen;
@@ -93,7 +97,7 @@
         }
     };
 
-    private Runnable mExitOverviewRunnable = new Runnable() {
+    private final Runnable mExitOverviewRunnable = new Runnable() {
         @Override
         public void run() {
             mInOverview = false;
@@ -107,7 +111,7 @@
      * rotates rotates the device to match that orientation, this triggers calls to sysui to adjust
      * the navbar.
      */
-    private OrientationEventListener mOrientationListener;
+    private final OrientationEventListener mOrientationListener;
     private int mSensorRotation = ROTATION_0;
     /**
      * This is the configuration of the foreground app or the app that will be in the foreground
@@ -120,7 +124,6 @@
      * would indicate the user's intention to rotate the foreground app.
      */
     private boolean mPrioritizeDeviceRotation = false;
-    private Runnable mOnDestroyFrozenTaskRunnable;
     /**
      * Set to true when user swipes to recents. In recents, we ignore the state of the recents
      * task list being frozen or not to allow the user to keep interacting with nav bar rotation
@@ -131,35 +134,26 @@
     private boolean mTaskListFrozen;
     private final Context mContext;
 
-    /**
-     * Keeps track of whether destroy has been called for this instance. Mainly used for TAPL tests
-     * where multiple instances of RotationTouchHelper are being created. b/177316094
-     */
-    private boolean mNeedsInit = true;
-
-    private RotationTouchHelper(Context context) {
+    @Inject
+    RotationTouchHelper(@ApplicationContext Context context,
+            DisplayController displayController,
+            SystemUiProxy systemUiProxy,
+            DaggerSingletonTracker lifeCycle) {
         mContext = context;
-        if (mNeedsInit) {
-            init();
-        }
-    }
-
-    public void init() {
-        if (!mNeedsInit) {
-            return;
-        }
-        mDisplayController = DisplayController.INSTANCE.get(mContext);
-        Resources resources = mContext.getResources();
+        mDisplayController = displayController;
+        mSystemUiProxy = systemUiProxy;
+        // TODO (b/398195845): this needs updating so non-default displays do not rotate with the
+        //  default display.
         mDisplayId = DEFAULT_DISPLAY;
 
+        Resources resources = mContext.getResources();
         mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
                 () -> QuickStepContract.getWindowCornerRadius(mContext));
 
-        // Register for navigation mode changes
-        mDisplayController.addChangeListener(this);
-        DisplayController.Info info = mDisplayController.getInfo();
-        onDisplayInfoChangedInternal(info, CHANGE_ALL, hasGestures(info.getNavigationMode()));
-        runOnDestroy(() -> mDisplayController.removeChangeListener(this));
+        // Register for navigation mode and rotation changes
+        mDisplayController.addChangeListenerForDisplay(this, mDisplayId);
+        DisplayController.Info info = mDisplayController.getInfoForDisplay(mDisplayId);
+        onDisplayInfoChanged(context, info, CHANGE_ALL);
 
         mOrientationListener = new OrientationEventListener(mContext) {
             @Override
@@ -180,40 +174,13 @@
                 }
             }
         };
-        runOnDestroy(() -> mOrientationListener.disable());
-        mNeedsInit = false;
-    }
 
-    private void setupOrientationSwipeHandler() {
-        TaskStackChangeListeners.getInstance().registerTaskStackListener(mFrozenTaskListener);
-        mOnDestroyFrozenTaskRunnable = () -> TaskStackChangeListeners.getInstance()
-                .unregisterTaskStackListener(mFrozenTaskListener);
-        runOnDestroy(mOnDestroyFrozenTaskRunnable);
-    }
-
-    private void destroyOrientationSwipeHandlerCallback() {
-        TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mFrozenTaskListener);
-        mOnDestroyActions.remove(mOnDestroyFrozenTaskRunnable);
-    }
-
-    private void runOnDestroy(Runnable action) {
-        mOnDestroyActions.add(action);
-    }
-
-    @Override
-    public void close() {
-        destroy();
-    }
-
-    /**
-     * Cleans up all the registered listeners and receivers.
-     */
-    public void destroy() {
-        for (Runnable r : mOnDestroyActions) {
-            r.run();
-        }
-        mNeedsInit = true;
-        mOnDestroyActions.clear();
+        lifeCycle.addCloseable(() -> {
+            mDisplayController.removeChangeListenerForDisplay(this, mDisplayId);
+            mOrientationListener.disable();
+            TaskStackChangeListeners.getInstance()
+                    .unregisterTaskStackListener(mFrozenTaskListener);
+        });
     }
 
     public boolean isTaskListFrozen() {
@@ -236,7 +203,8 @@
             return;
         }
 
-        mOrientationTouchTransformer.createOrAddTouchRegion(mDisplayController.getInfo(),
+        mOrientationTouchTransformer.createOrAddTouchRegion(
+                mDisplayController.getInfoForDisplay(mDisplayId),
                 "RTH.updateGestureTouchRegions");
     }
 
@@ -264,10 +232,6 @@
 
     @Override
     public void onDisplayInfoChanged(Context context, Info info, int flags) {
-        onDisplayInfoChangedInternal(info, flags, false);
-    }
-
-    private void onDisplayInfoChangedInternal(Info info, int flags, boolean forceRegister) {
         if ((flags & (CHANGE_ROTATION | CHANGE_ACTIVE_SCREEN | CHANGE_NAVIGATION_MODE
                 | CHANGE_SUPPORTED_BOUNDS)) != 0) {
             mDisplayRotation = info.rotation;
@@ -297,15 +261,16 @@
 
         if ((flags & CHANGE_NAVIGATION_MODE) != 0) {
             NavigationMode newMode = info.getNavigationMode();
-            mOrientationTouchTransformer.setNavigationMode(newMode, mDisplayController.getInfo(),
+            mOrientationTouchTransformer.setNavigationMode(newMode,
+                    mDisplayController.getInfoForDisplay(mDisplayId),
                     mContext.getResources());
 
-            if (forceRegister || (!hasGestures(mMode) && hasGestures(newMode))) {
-                setupOrientationSwipeHandler();
-            } else if (hasGestures(mMode) && !hasGestures(newMode)) {
-                destroyOrientationSwipeHandlerCallback();
+            TaskStackChangeListeners.getInstance()
+                    .unregisterTaskStackListener(mFrozenTaskListener);
+            if (hasGestures(newMode)) {
+                TaskStackChangeListeners.getInstance()
+                        .registerTaskStackListener(mFrozenTaskListener);
             }
-
             mMode = newMode;
         }
     }
@@ -319,7 +284,8 @@
      */
     void setGesturalHeight(int newGesturalHeight) {
         mOrientationTouchTransformer.setGesturalHeight(
-                newGesturalHeight, mDisplayController.getInfo(), mContext.getResources());
+                newGesturalHeight, mDisplayController.getInfoForDisplay(mDisplayId),
+                mContext.getResources());
     }
 
     /**
@@ -335,7 +301,8 @@
     }
 
     private void enableMultipleRegions(boolean enable) {
-        mOrientationTouchTransformer.enableMultipleRegions(enable, mDisplayController.getInfo());
+        mOrientationTouchTransformer.enableMultipleRegions(enable,
+                mDisplayController.getInfoForDisplay(mDisplayId));
         notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getQuickStepStartingRotation());
         if (enable && !mInOverview && !TestProtocol.sDisableSensorRotation) {
             // Clear any previous state from sensor manager
@@ -363,7 +330,7 @@
                 // If we're in landscape w/o ever quickswitching, show the navbar in landscape
                 enableMultipleRegions(true);
             }
-            containerInterface.onExitOverview(this, mExitOverviewRunnable);
+            containerInterface.onExitOverview(mExitOverviewRunnable);
         } else if (endTarget == GestureState.GestureEndTarget.HOME
                 || endTarget == GestureState.GestureEndTarget.ALL_APPS) {
             enableMultipleRegions(false);
@@ -390,8 +357,7 @@
     }
 
     private void notifySysuiOfCurrentRotation(int rotation) {
-        UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext)
-                .notifyPrioritizedRotation(rotation));
+        UI_HELPER_EXECUTOR.execute(() -> mSystemUiProxy.notifyPrioritizedRotation(rotation));
     }
 
     /**
@@ -399,7 +365,8 @@
      * notifies system UI of the primary rotation the user is interacting with
      */
     private void toggleSecondaryNavBarsForRotation() {
-        mOrientationTouchTransformer.setSingleActiveRegion(mDisplayController.getInfo());
+        mOrientationTouchTransformer.setSingleActiveRegion(
+                mDisplayController.getInfoForDisplay(mDisplayId));
         notifySysuiOfCurrentRotation(mOrientationTouchTransformer.getCurrentActiveRotation());
     }
 
diff --git a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
index 5264643..de7fb89 100644
--- a/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/SimpleOrientationTouchTransformer.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
 import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
 import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
@@ -22,35 +24,40 @@
 import android.content.Context;
 import android.view.MotionEvent;
 
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.SafeCloseable;
+import com.android.quickstep.dagger.QuickstepBaseAppComponent;
 
+import javax.inject.Inject;
+
+@LauncherAppSingleton
 public class SimpleOrientationTouchTransformer implements
-        DisplayController.DisplayInfoChangeListener, SafeCloseable {
+        DisplayController.DisplayInfoChangeListener {
 
-    public static final MainThreadInitializedObject<SimpleOrientationTouchTransformer> INSTANCE =
-            new MainThreadInitializedObject<>(SimpleOrientationTouchTransformer::new);
+    public static final DaggerSingletonObject<SimpleOrientationTouchTransformer> INSTANCE =
+            new DaggerSingletonObject<>(
+                    QuickstepBaseAppComponent::getSimpleOrientationTouchTransformer);
 
-    private final Context mContext;
     private OrientationRectF mOrientationRectF;
     private OrientationRectF mTouchingOrientationRectF;
     private int mViewRotation;
+    private final int mDisplayId;
 
-    public SimpleOrientationTouchTransformer(Context context) {
-        this(context, DisplayController.INSTANCE.get(context));
-    }
+    @Inject
+    public SimpleOrientationTouchTransformer(@ApplicationContext Context context,
+            DisplayController displayController,
+            DaggerSingletonTracker tracker) {
+        // TODO (b/398195845): make sure non-default displays don't get affected by default display
+        // changes.
+        mDisplayId = DEFAULT_DISPLAY;
+        displayController.addChangeListenerForDisplay(this, mDisplayId);
+        tracker.addCloseable(
+                () -> displayController.removeChangeListenerForDisplay(this, mDisplayId));
 
-    @androidx.annotation.VisibleForTesting
-    public SimpleOrientationTouchTransformer(Context context, DisplayController displayController) {
-        mContext = context;
-        displayController.addChangeListener(this);
-        onDisplayInfoChanged(context, displayController.getInfo(), CHANGE_ALL);
-    }
-
-    @Override
-    public void close() {
-        DisplayController.INSTANCE.get(mContext).removeChangeListener(this);
+        onDisplayInfoChanged(context, displayController.getInfoForDisplay(mDisplayId), CHANGE_ALL);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index f813d9a..233f0a9 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -58,7 +58,7 @@
 import java.util.function.Consumer;
 
 public abstract class SwipeUpAnimationLogic implements
-        RecentsAnimationCallbacks.RecentsAnimationListener{
+        RecentsAnimationCallbacks.RecentsAnimationListener {
 
     protected static final Rect TEMP_RECT = new Rect();
     protected final RemoteTargetGluer mTargetGluer;
@@ -66,7 +66,6 @@
     protected DeviceProfile mDp;
 
     protected final Context mContext;
-    protected final RecentsAnimationDeviceState mDeviceState;
     protected final GestureState mGestureState;
 
     protected RemoteTargetHandle[] mRemoteTargetHandles;
@@ -85,20 +84,19 @@
 
     protected boolean mIsSwipeForSplit;
 
-    public SwipeUpAnimationLogic(Context context, RecentsAnimationDeviceState deviceState,
-            GestureState gestureState) {
+    public SwipeUpAnimationLogic(Context context, GestureState gestureState) {
         mContext = context;
-        mDeviceState = deviceState;
         mGestureState = gestureState;
         updateIsGestureForSplit(TopTaskTracker.INSTANCE.get(context)
                 .getRunningSplitTaskIds().length);
 
         mTargetGluer = new RemoteTargetGluer(mContext, mGestureState.getContainerInterface());
         mRemoteTargetHandles = mTargetGluer.getRemoteTargetHandles();
+        RotationTouchHelper rotationTouchHelper = RotationTouchHelper.INSTANCE.get(context);
         runActionOnRemoteHandles(remoteTargetHandle ->
                 remoteTargetHandle.getTaskViewSimulator().getOrientationState().update(
-                        mDeviceState.getRotationTouchHelper().getCurrentActiveRotation(),
-                        mDeviceState.getRotationTouchHelper().getDisplayRotation()
+                        rotationTouchHelper.getCurrentActiveRotation(),
+                        rotationTouchHelper.getDisplayRotation()
                 ));
     }
 
@@ -114,7 +112,7 @@
             PendingAnimation pendingAnimation = new PendingAnimation(mTransitionDragLength * 2);
             TaskViewSimulator taskViewSimulator = remoteHandle.getTaskViewSimulator();
             taskViewSimulator.setDp(dp);
-            taskViewSimulator.addAppToOverviewAnim(pendingAnimation, LINEAR);
+            taskViewSimulator.addAppToCarouselAnim(pendingAnimation, LINEAR);
             AnimatorPlaybackController playbackController =
                     pendingAnimation.createPlaybackController();
 
@@ -505,6 +503,11 @@
                 }
             }
 
+            if (Float.isNaN(scale)) {
+                Log.e(TAG, "Scale is NaN: starting dimensions=[" + startWidth + ", " + startHeight
+                        + "], current dimensions=[" + currentWidth + ", " + currentHeight + "]");
+            }
+
             mTargetTaskView.setScaleX(scale);
             mTargetTaskView.setScaleY(scale);
             mTargetTaskView.setTranslationX(
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
deleted file mode 100644
index 6a25ecb..0000000
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ /dev/null
@@ -1,1660 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
-
-import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import static com.android.quickstep.util.LogUtils.splitFailureMessage;
-
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.PendingIntent;
-import android.app.PictureInPictureParams;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ShortcutInfo;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.Log;
-import android.view.IRemoteAnimationRunner;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.window.DesktopModeFlags;
-import android.window.IOnBackInvokedCallback;
-import android.window.RemoteTransition;
-import android.window.TaskSnapshot;
-import android.window.TransitionFilter;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-import androidx.annotation.WorkerThread;
-
-import com.android.internal.logging.InstanceId;
-import com.android.internal.util.ScreenshotRequest;
-import com.android.internal.view.AppearanceRegion;
-import com.android.launcher3.dagger.ApplicationContext;
-import com.android.launcher3.dagger.LauncherAppSingleton;
-import com.android.launcher3.util.DaggerSingletonObject;
-import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.dagger.QuickstepBaseAppComponent;
-import com.android.quickstep.util.ActiveGestureProtoLogProxy;
-import com.android.quickstep.util.ContextualSearchInvoker;
-import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider;
-import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
-import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
-import com.android.systemui.shared.system.RecentsAnimationListener;
-import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController;
-import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
-import com.android.systemui.shared.system.smartspace.SmartspaceState;
-import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
-import com.android.systemui.unfold.progress.IUnfoldAnimation;
-import com.android.systemui.unfold.progress.IUnfoldTransitionListener;
-import com.android.wm.shell.back.IBackAnimation;
-import com.android.wm.shell.bubbles.IBubbles;
-import com.android.wm.shell.bubbles.IBubblesListener;
-import com.android.wm.shell.common.pip.IPip;
-import com.android.wm.shell.common.pip.IPipAnimationListener;
-import com.android.wm.shell.desktopmode.IDesktopMode;
-import com.android.wm.shell.desktopmode.IDesktopTaskListener;
-import com.android.wm.shell.draganddrop.IDragAndDrop;
-import com.android.wm.shell.onehanded.IOneHanded;
-import com.android.wm.shell.recents.IRecentTasks;
-import com.android.wm.shell.recents.IRecentTasksListener;
-import com.android.wm.shell.recents.IRecentsAnimationController;
-import com.android.wm.shell.recents.IRecentsAnimationRunner;
-import com.android.wm.shell.shared.GroupedTaskInfo;
-import com.android.wm.shell.shared.IShellTransitions;
-import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
-import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
-import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
-import com.android.wm.shell.shared.split.SplitBounds;
-import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
-import com.android.wm.shell.splitscreen.ISplitScreen;
-import com.android.wm.shell.splitscreen.ISplitScreenListener;
-import com.android.wm.shell.splitscreen.ISplitSelectListener;
-import com.android.wm.shell.startingsurface.IStartingWindow;
-import com.android.wm.shell.startingsurface.IStartingWindowListener;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.LinkedHashMap;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * Holds the reference to SystemUI.
- */
-@LauncherAppSingleton
-public class SystemUiProxy implements ISystemUiProxy, NavHandle {
-    private static final String TAG = "SystemUiProxy";
-
-    public static final DaggerSingletonObject<SystemUiProxy> INSTANCE =
-            new DaggerSingletonObject<>(QuickstepBaseAppComponent::getSystemUiProxy);
-
-    private static final int MSG_SET_SHELF_HEIGHT = 1;
-    private static final int MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT = 2;
-
-    private ISystemUiProxy mSystemUiProxy;
-    private IPip mPip;
-    private IBubbles mBubbles;
-    private ISysuiUnlockAnimationController mSysuiUnlockAnimationController;
-    private ISplitScreen mSplitScreen;
-    private IOneHanded mOneHanded;
-    private IShellTransitions mShellTransitions;
-    private IStartingWindow mStartingWindow;
-    private IRecentTasks mRecentTasks;
-    private IBackAnimation mBackAnimation;
-    private IDesktopMode mDesktopMode;
-    private IUnfoldAnimation mUnfoldAnimation;
-    private final DeathRecipient mSystemUiProxyDeathRecipient = () -> {
-        MAIN_EXECUTOR.execute(() -> clearProxy());
-    };
-
-    // Save the listeners passed into the proxy since OverviewProxyService may not have been bound
-    // yet, and we'll need to set/register these listeners with SysUI when they do.  Note that it is
-    // up to the caller to clear the listeners to prevent leaks as these can be held indefinitely
-    // in case SysUI needs to rebind.
-    private IPipAnimationListener mPipAnimationListener;
-    private IBubblesListener mBubblesListener;
-    private ISplitScreenListener mSplitScreenListener;
-    private ISplitSelectListener mSplitSelectListener;
-    private IStartingWindowListener mStartingWindowListener;
-    private ILauncherUnlockAnimationController mLauncherUnlockAnimationController;
-    private String mLauncherActivityClass;
-    private IRecentTasksListener mRecentTasksListener;
-    private IUnfoldTransitionListener mUnfoldAnimationListener;
-    private IDesktopTaskListener mDesktopTaskListener;
-    private final LinkedHashMap<RemoteTransition, TransitionFilter> mRemoteTransitions =
-            new LinkedHashMap<>();
-
-    private final List<Runnable> mStateChangeCallbacks = new ArrayList<>();
-
-    private IBinder mOriginalTransactionToken = null;
-    private IOnBackInvokedCallback mBackToLauncherCallback;
-    private IRemoteAnimationRunner mBackToLauncherRunner;
-    private IDragAndDrop mDragAndDrop;
-    private final HomeVisibilityState mHomeVisibilityState = new HomeVisibilityState();
-    private final FocusState mFocusState = new FocusState();
-
-    // Used to dedupe calls to SystemUI
-    private int mLastShelfHeight;
-    private boolean mLastShelfVisible;
-
-    // Used to dedupe calls to SystemUI
-    private int mLastLauncherKeepClearAreaHeight;
-    private boolean mLastLauncherKeepClearAreaHeightVisible;
-
-    private final Context mContext;
-    private final Handler mAsyncHandler;
-
-    // TODO(141886704): Find a way to remove this
-    @SystemUiStateFlags
-    private long mLastSystemUiStateFlags;
-
-    /**
-     * This is a singleton pending intent that is used to start recents via Shell (which is a
-     * different process). It is bare-bones, so it's expected that the component and options will
-     * be provided via fill-in intent.
-     */
-    private final PendingIntent mRecentsPendingIntent;
-
-    @Nullable
-    private final ProxyUnfoldTransitionProvider mUnfoldTransitionProvider;
-
-    @Inject
-    public SystemUiProxy(@ApplicationContext Context context) {
-        mContext = context;
-        mAsyncHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessageAsync);
-        final Intent baseIntent = new Intent().setPackage(mContext.getPackageName());
-        final ActivityOptions options = ActivityOptions.makeBasic()
-                .setPendingIntentCreatorBackgroundActivityStartMode(
-                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
-        mRecentsPendingIntent = PendingIntent.getActivity(mContext, 0, baseIntent,
-                PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT
-                        | Intent.FILL_IN_COMPONENT, options.toBundle());
-
-        mUnfoldTransitionProvider =
-                (enableUnfoldStateAnimation() && new ResourceUnfoldTransitionConfig().isEnabled())
-                         ? new ProxyUnfoldTransitionProvider() : null;
-    }
-
-    @Override
-    public void onBackEvent(KeyEvent backEvent) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.onBackEvent(backEvent);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onBackPressed", e);
-            }
-        }
-    }
-
-    @Override
-    public void onImeSwitcherPressed() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.onImeSwitcherPressed();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onImeSwitcherPressed", e);
-            }
-        }
-    }
-
-    @Override
-    public void onImeSwitcherLongPress() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.onImeSwitcherLongPress();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onImeSwitcherLongPress");
-            }
-        }
-    }
-
-    @Override
-    public void updateContextualEduStats(boolean isTrackpadGesture, String gestureType) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.updateContextualEduStats(isTrackpadGesture, gestureType);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call updateContextualEduStats");
-            }
-        }
-    }
-
-    @Override
-    public void setHomeRotationEnabled(boolean enabled) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.setHomeRotationEnabled(enabled);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onBackPressed", e);
-            }
-        }
-    }
-
-    @Override
-    public IBinder asBinder() {
-        // Do nothing
-        return null;
-    }
-
-    /**
-     * Sets proxy state, including death linkage, various listeners, and other configuration objects
-     */
-    @MainThread
-    public void setProxy(ISystemUiProxy proxy, IPip pip, IBubbles bubbles, ISplitScreen splitScreen,
-            IOneHanded oneHanded, IShellTransitions shellTransitions,
-            IStartingWindow startingWindow, IRecentTasks recentTasks,
-            ISysuiUnlockAnimationController sysuiUnlockAnimationController,
-            IBackAnimation backAnimation, IDesktopMode desktopMode,
-            IUnfoldAnimation unfoldAnimation, IDragAndDrop dragAndDrop) {
-        Preconditions.assertUIThread();
-        unlinkToDeath();
-        mSystemUiProxy = proxy;
-        mPip = pip;
-        mBubbles = bubbles;
-        mSplitScreen = splitScreen;
-        mOneHanded = oneHanded;
-        mShellTransitions = shellTransitions;
-        mStartingWindow = startingWindow;
-        mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
-        mRecentTasks = recentTasks;
-        mBackAnimation = backAnimation;
-        mDesktopMode = desktopMode;
-        mUnfoldAnimation = enableUnfoldStateAnimation() ? null : unfoldAnimation;
-        mDragAndDrop = dragAndDrop;
-        linkToDeath();
-        // re-attach the listeners once missing due to setProxy has not been initialized yet.
-        setPipAnimationListener(mPipAnimationListener);
-        setBubblesListener(mBubblesListener);
-        registerSplitScreenListener(mSplitScreenListener);
-        registerSplitSelectListener(mSplitSelectListener);
-        mHomeVisibilityState.init(mShellTransitions);
-        mFocusState.init(mShellTransitions);
-        setStartingWindowListener(mStartingWindowListener);
-        setLauncherUnlockAnimationController(
-                mLauncherActivityClass, mLauncherUnlockAnimationController);
-        new LinkedHashMap<>(mRemoteTransitions).forEach(this::registerRemoteTransition);
-        setupTransactionQueue();
-        registerRecentTasksListener(mRecentTasksListener);
-        setBackToLauncherCallback(mBackToLauncherCallback, mBackToLauncherRunner);
-        setUnfoldAnimationListener(mUnfoldAnimationListener);
-        setDesktopTaskListener(mDesktopTaskListener);
-        setAssistantOverridesRequested(new ContextualSearchInvoker(mContext)
-                .getSysUiAssistOverrideInvocationTypes());
-        mStateChangeCallbacks.forEach(Runnable::run);
-
-        if (mUnfoldTransitionProvider != null) {
-            if (unfoldAnimation != null) {
-                try {
-                    unfoldAnimation.setListener(mUnfoldTransitionProvider);
-                    mUnfoldTransitionProvider.setActive(true);
-                } catch (RemoteException e) {
-                    // Ignore
-                }
-            } else {
-                mUnfoldTransitionProvider.setActive(false);
-            }
-        }
-    }
-
-    /**
-     * Clear the proxy to release held resources and turn the majority of its operations into no-ops
-     */
-    @MainThread
-    public void clearProxy() {
-        setProxy(null, null, null, null, null, null, null, null, null, null, null, null, null);
-    }
-
-    /**
-     * Adds a callback to be notified whenever the active state changes
-     */
-    public void addOnStateChangeListener(Runnable callback) {
-        mStateChangeCallbacks.add(callback);
-    }
-
-    /**
-     * Removes a previously added state change callback
-     */
-    public void removeOnStateChangeListener(Runnable callback) {
-        mStateChangeCallbacks.remove(callback);
-    }
-
-    // TODO(141886704): Find a way to remove this
-    public void setLastSystemUiStateFlags(@SystemUiStateFlags long stateFlags) {
-        mLastSystemUiStateFlags = stateFlags;
-    }
-
-    // TODO(141886704): Find a way to remove this
-    @SystemUiStateFlags
-    public long getLastSystemUiStateFlags() {
-        return mLastSystemUiStateFlags;
-    }
-
-    public boolean isActive() {
-        return mSystemUiProxy != null;
-    }
-
-    private void linkToDeath() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.asBinder().linkToDeath(mSystemUiProxyDeathRecipient, 0 /* flags */);
-            } catch (RemoteException e) {
-                Log.e(TAG, "Failed to link sysui proxy death recipient");
-            }
-        }
-    }
-
-    private void unlinkToDeath() {
-        if (mSystemUiProxy != null) {
-            mSystemUiProxy.asBinder().unlinkToDeath(mSystemUiProxyDeathRecipient, 0 /* flags */);
-        }
-    }
-
-    @Override
-    public void startScreenPinning(int taskId) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.startScreenPinning(taskId);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startScreenPinning", e);
-            }
-        }
-    }
-
-    @Override
-    public void onOverviewShown(boolean fromHome) {
-        onOverviewShown(fromHome, TAG);
-    }
-
-    public void onOverviewShown(boolean fromHome, String tag) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.onOverviewShown(fromHome);
-            } catch (RemoteException e) {
-                Log.w(tag, "Failed call onOverviewShown from: " + (fromHome ? "home" : "app"), e);
-            }
-        }
-    }
-
-    @MainThread
-    @Override
-    public void onStatusBarTouchEvent(MotionEvent event) {
-        Preconditions.assertUIThread();
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.onStatusBarTouchEvent(event);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onStatusBarTouchEvent with arg: " + event, e);
-            }
-        }
-    }
-
-    @Override
-    public void onStatusBarTrackpadEvent(MotionEvent event) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.onStatusBarTrackpadEvent(event);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onStatusBarTrackpadEvent with arg: " + event, e);
-            }
-        }
-    }
-
-    @Override
-    public void onAssistantProgress(float progress) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.onAssistantProgress(progress);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onAssistantProgress with progress: " + progress, e);
-            }
-        }
-    }
-
-    @Override
-    public void onAssistantGestureCompletion(float velocity) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.onAssistantGestureCompletion(velocity);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onAssistantGestureCompletion", e);
-            }
-        }
-    }
-
-    @Override
-    public void startAssistant(Bundle args) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.startAssistant(args);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startAssistant", e);
-            }
-        }
-    }
-
-    @Override
-    public void setAssistantOverridesRequested(int[] invocationTypes) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.setAssistantOverridesRequested(invocationTypes);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setAssistantOverridesRequested", e);
-            }
-        }
-    }
-
-    @Override
-    public void animateNavBarLongPress(boolean isTouchDown, boolean shrink, long durationMs) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.animateNavBarLongPress(isTouchDown, shrink, durationMs);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call animateNavBarLongPress", e);
-            }
-        }
-    }
-
-    @Override
-    public void setOverrideHomeButtonLongPress(long duration, float slopMultiplier,
-            boolean haptic) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.setOverrideHomeButtonLongPress(duration, slopMultiplier, haptic);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setOverrideHomeButtonLongPress", e);
-            }
-        }
-    }
-
-    @Override
-    public void notifyAccessibilityButtonClicked(int displayId) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.notifyAccessibilityButtonClicked(displayId);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call notifyAccessibilityButtonClicked", e);
-            }
-        }
-    }
-
-    @Override
-    public void notifyAccessibilityButtonLongClicked() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.notifyAccessibilityButtonLongClicked();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call notifyAccessibilityButtonLongClicked", e);
-            }
-        }
-    }
-
-    @Override
-    public void stopScreenPinning() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.stopScreenPinning();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call stopScreenPinning", e);
-            }
-        }
-    }
-
-    @Override
-    public void notifyPrioritizedRotation(int rotation) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.notifyPrioritizedRotation(rotation);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call notifyPrioritizedRotation with arg: " + rotation, e);
-            }
-        }
-    }
-
-    @Override
-    public void notifyTaskbarStatus(boolean visible, boolean stashed) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.notifyTaskbarStatus(visible, stashed);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call notifyTaskbarStatus with arg: " +
-                        visible + ", " + stashed, e);
-            }
-        }
-    }
-
-    /**
-     * NOTE: If called to suspend, caller MUST call this method to also un-suspend
-     * @param suspend should be true to stop auto-hide, false to resume normal behavior
-     */
-    @Override
-    public void notifyTaskbarAutohideSuspend(boolean suspend) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.notifyTaskbarAutohideSuspend(suspend);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call notifyTaskbarAutohideSuspend with arg: " +
-                        suspend, e);
-            }
-        }
-    }
-
-    @Override
-    public void takeScreenshot(ScreenshotRequest request) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.takeScreenshot(request);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call takeScreenshot");
-            }
-        }
-    }
-
-    @Override
-    public void expandNotificationPanel() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.expandNotificationPanel();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call expandNotificationPanel", e);
-            }
-        }
-    }
-
-    @Override
-    public void toggleNotificationPanel() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.toggleNotificationPanel();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call toggleNotificationPanel", e);
-            }
-        }
-    }
-
-    @Override
-    public void toggleQuickSettingsPanel() {
-        if (mSystemUiProxy != null) {
-            try {
-                mSystemUiProxy.toggleQuickSettingsPanel();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call toggleQuickSettingsPanel", e);
-            }
-        }
-    }
-
-    //
-    // Pip
-    //
-
-    /**
-     * Sets the shelf height.
-     */
-    public void setShelfHeight(boolean visible, int shelfHeight) {
-        Message.obtain(mAsyncHandler, MSG_SET_SHELF_HEIGHT,
-                visible ? 1 : 0 , shelfHeight).sendToTarget();
-    }
-
-    @WorkerThread
-    private void setShelfHeightAsync(int visibleInt, int shelfHeight) {
-        boolean visible = visibleInt != 0;
-        boolean changed = visible != mLastShelfVisible || shelfHeight != mLastShelfHeight;
-        IPip pip = mPip;
-        if (pip != null && changed) {
-            mLastShelfVisible = visible;
-            mLastShelfHeight = shelfHeight;
-            try {
-                pip.setShelfHeight(visible, shelfHeight);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setShelfHeight visible: " + visible
-                        + " height: " + shelfHeight, e);
-            }
-        }
-    }
-
-    /**
-     * Sets the height of the keep clear area that is going to be reported by
-     * the Launcher for the Hotseat.
-     */
-    public void setLauncherKeepClearAreaHeight(boolean visible, int height) {
-        Message.obtain(mAsyncHandler, MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT,
-                visible ? 1 : 0 , height).sendToTarget();
-    }
-
-    @WorkerThread
-    private void setLauncherKeepClearAreaHeight(int visibleInt, int height) {
-        boolean visible = visibleInt != 0;
-        boolean changed = visible != mLastLauncherKeepClearAreaHeightVisible
-                || height != mLastLauncherKeepClearAreaHeight;
-        IPip pip = mPip;
-        if (pip != null && changed) {
-            mLastLauncherKeepClearAreaHeightVisible = visible;
-            mLastLauncherKeepClearAreaHeight = height;
-            try {
-                pip.setLauncherKeepClearAreaHeight(visible, height);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setLauncherKeepClearAreaHeight visible: " + visible
-                        + " height: " + height, e);
-            }
-        }
-    }
-
-    /**
-     * Sets listener to get pip animation callbacks.
-     */
-    public void setPipAnimationListener(IPipAnimationListener listener) {
-        if (mPip != null) {
-            try {
-                mPip.setPipAnimationListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setPinnedStackAnimationListener", e);
-            }
-        }
-        mPipAnimationListener = listener;
-    }
-
-    /**
-     * @return Destination bounds of auto-pip animation, {@code null} if the animation is not ready.
-     */
-    @Nullable
-    public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
-            PictureInPictureParams pictureInPictureParams, int launcherRotation,
-            Rect hotseatKeepClearArea) {
-        if (mPip != null) {
-            try {
-                return mPip.startSwipePipToHome(componentName, activityInfo,
-                        pictureInPictureParams, launcherRotation, hotseatKeepClearArea);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startSwipePipToHome", e);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Notifies WM Shell that launcher has finished the preparation of the animation for swipe to
-     * home. WM Shell can choose to fade out the overlay when entering PIP is finished, and WM Shell
-     * should be responsible for cleaning up the overlay.
-     */
-    public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
-            SurfaceControl overlay, Rect appBounds, Rect sourceRectHint) {
-        if (mPip != null) {
-            try {
-                mPip.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay,
-                        appBounds, sourceRectHint);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call stopSwipePipToHome");
-            }
-        }
-    }
-
-    /**
-     * Notifies WM Shell that launcher has aborted all the animation for swipe to home. WM Shell
-     * can use this callback to clean up its internal states.
-     */
-    public void abortSwipePipToHome(int taskId, ComponentName componentName) {
-        if (mPip != null) {
-            try {
-                mPip.abortSwipePipToHome(taskId, componentName);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call abortSwipePipToHome");
-            }
-        }
-    }
-
-    /**
-     * Sets the next pip animation type to be the alpha animation.
-     */
-    public void setPipAnimationTypeToAlpha() {
-        if (mPip != null) {
-            try {
-                mPip.setPipAnimationTypeToAlpha();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setPipAnimationTypeToAlpha", e);
-            }
-        }
-    }
-
-    /**
-     * Sets the app icon size in pixel used by Launcher all apps.
-     */
-    public void setLauncherAppIconSize(int iconSizePx) {
-        if (mPip != null) {
-            try {
-                mPip.setLauncherAppIconSize(iconSizePx);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setLauncherAppIconSize", e);
-            }
-        }
-    }
-
-    //
-    // Bubbles
-    //
-
-    /**
-     * Sets the listener to be notified of bubble state changes.
-     */
-    public void setBubblesListener(IBubblesListener listener) {
-        if (mBubbles != null) {
-            try {
-                if (mBubblesListener != null) {
-                    // Clear out any previous listener
-                    mBubbles.unregisterBubbleListener(mBubblesListener);
-                }
-                if (listener != null) {
-                    mBubbles.registerBubbleListener(listener);
-                }
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call registerBubblesListener");
-            }
-        }
-        mBubblesListener = listener;
-    }
-
-    /**
-     * Tells SysUI to show the bubble with the provided key.
-     * @param key the key of the bubble to show.
-     * @param top top coordinate of bubble bar on screen
-     */
-    public void showBubble(String key, int top) {
-        if (mBubbles != null) {
-            try {
-                mBubbles.showBubble(key, top);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call showBubble");
-            }
-        }
-    }
-
-    /**
-     * Tells SysUI to remove all bubbles.
-     */
-    public void removeAllBubbles() {
-        if (mBubbles == null) return;
-        try {
-            mBubbles.removeAllBubbles();
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call removeAllBubbles");
-        }
-    }
-
-    /**
-     * Tells SysUI to collapse the bubbles.
-     */
-    public void collapseBubbles() {
-        if (mBubbles != null) {
-            try {
-                mBubbles.collapseBubbles();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call collapseBubbles");
-            }
-        }
-    }
-
-    /**
-     * Tells SysUI when the bubble is being dragged.
-     * Should be called only when the bubble bar is expanded.
-     * @param bubbleKey key of the bubble being dragged
-     */
-    public void startBubbleDrag(@Nullable String bubbleKey) {
-        if (mBubbles == null) return;
-        try {
-            mBubbles.startBubbleDrag(bubbleKey);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call startBubbleDrag");
-        }
-    }
-
-    /**
-     * Tells SysUI when the bubble stops being dragged.
-     * Should be called only when the bubble bar is expanded.
-     *
-     * @param location location of the bubble bar
-     * @param top      new top coordinate for bubble bar on screen
-     */
-    public void stopBubbleDrag(BubbleBarLocation location, int top) {
-        if (mBubbles == null) return;
-        try {
-            mBubbles.stopBubbleDrag(location, top);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call stopBubbleDrag");
-        }
-    }
-
-    /**
-     * Tells SysUI to dismiss the bubble with the provided key.
-     *
-     * @param key the key of the bubble to dismiss.
-     * @param timestamp the timestamp when the removal happened.
-     */
-    public void dragBubbleToDismiss(String key, long timestamp) {
-        if (mBubbles == null) return;
-        try {
-            mBubbles.dragBubbleToDismiss(key, timestamp);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call dragBubbleToDismiss");
-        }
-    }
-
-    /**
-     * Tells SysUI to show user education relative to the reference point provided.
-     * @param position the bubble bar top center position in Screen coordinates.
-     */
-    public void showUserEducation(Point position) {
-        try {
-            mBubbles.showUserEducation(position.x, position.y);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call showUserEducation");
-        }
-    }
-
-    /**
-     * Tells SysUI to update the bubble bar location to the new location.
-     * @param location new location for the bubble bar
-     * @param source what triggered the location update
-     */
-    public void setBubbleBarLocation(BubbleBarLocation location,
-            @BubbleBarLocation.UpdateSource int source) {
-        try {
-            mBubbles.setBubbleBarLocation(location, source);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call setBubbleBarLocation");
-        }
-    }
-
-    /**
-     * Tells SysUI the top coordinate of bubble bar on screen
-     *
-     * @param topOnScreen top coordinate for bubble bar on screen
-     */
-    public void updateBubbleBarTopOnScreen(int topOnScreen) {
-        try {
-            if (mBubbles != null) {
-                mBubbles.updateBubbleBarTopOnScreen(topOnScreen);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call updateBubbleBarTopOnScreen");
-        }
-    }
-
-    /**
-     * Tells SysUI to show a shortcut bubble.
-     *
-     * @param info the shortcut info used to create or identify the bubble.
-     */
-    public void showShortcutBubble(ShortcutInfo info) {
-        try {
-            if (mBubbles != null) {
-                mBubbles.showShortcutBubble(info);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call show bubble for shortcut");
-        }
-    }
-
-    /**
-     * Tells SysUI to show a bubble of an app.
-     *
-     * @param intent the intent used to create the bubble.
-     */
-    public void showAppBubble(Intent intent) {
-        try {
-            if (mBubbles != null) {
-                mBubbles.showAppBubble(intent);
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed call show bubble for app");
-        }
-    }
-
-    /** Tells SysUI to show the expanded view. */
-    public void showExpandedView() {
-        try {
-            if (mBubbles != null) {
-                mBubbles.showExpandedView();
-            }
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to call showExpandedView");
-        }
-    }
-
-    //
-    // Splitscreen
-    //
-
-    public void registerSplitScreenListener(ISplitScreenListener listener) {
-        if (mSplitScreen != null) {
-            try {
-                mSplitScreen.registerSplitScreenListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call registerSplitScreenListener");
-            }
-        }
-        mSplitScreenListener = listener;
-    }
-
-    public void unregisterSplitScreenListener(ISplitScreenListener listener) {
-        if (mSplitScreen != null) {
-            try {
-                mSplitScreen.unregisterSplitScreenListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call unregisterSplitScreenListener");
-            }
-        }
-        mSplitScreenListener = null;
-    }
-
-    public void registerSplitSelectListener(ISplitSelectListener listener) {
-        if (mSplitScreen != null) {
-            try {
-                mSplitScreen.registerSplitSelectListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call registerSplitSelectListener");
-            }
-        }
-        mSplitSelectListener = listener;
-    }
-
-    public void unregisterSplitSelectListener(ISplitSelectListener listener) {
-        if (mSplitScreen != null) {
-            try {
-                mSplitScreen.unregisterSplitSelectListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call unregisterSplitSelectListener");
-            }
-        }
-        mSplitSelectListener = null;
-    }
-
-    /** Start multiple tasks in split-screen simultaneously. */
-    public void startTasks(int taskId1, Bundle options1, int taskId2, Bundle options2,
-            @StagePosition int splitPosition, @PersistentSnapPosition int snapPosition,
-            RemoteTransition remoteTransition, InstanceId instanceId) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSplitScreen.startTasks(taskId1, options1, taskId2, options2, splitPosition,
-                        snapPosition, remoteTransition, instanceId);
-            } catch (RemoteException e) {
-                Log.w(TAG, splitFailureMessage("startTasks", "RemoteException"), e);
-            }
-        }
-    }
-
-    public void startIntentAndTask(PendingIntent pendingIntent, int userId1, Bundle options1,
-            int taskId, Bundle options2, @StagePosition int splitPosition,
-            @PersistentSnapPosition int snapPosition, RemoteTransition remoteTransition,
-            InstanceId instanceId) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSplitScreen.startIntentAndTask(pendingIntent, userId1, options1, taskId, options2,
-                        splitPosition, snapPosition, remoteTransition, instanceId);
-            } catch (RemoteException e) {
-                Log.w(TAG, splitFailureMessage("startIntentAndTask", "RemoteException"), e);
-            }
-        }
-    }
-
-    public void startIntents(PendingIntent pendingIntent1, int userId1,
-            @Nullable ShortcutInfo shortcutInfo1, Bundle options1, PendingIntent pendingIntent2,
-            int userId2, @Nullable ShortcutInfo shortcutInfo2, Bundle options2,
-            @StagePosition int splitPosition, @PersistentSnapPosition int snapPosition,
-            RemoteTransition remoteTransition, InstanceId instanceId) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSplitScreen.startIntents(pendingIntent1, userId1, shortcutInfo1, options1,
-                        pendingIntent2, userId2, shortcutInfo2, options2, splitPosition,
-                        snapPosition, remoteTransition, instanceId);
-            } catch (RemoteException e) {
-                Log.w(TAG, splitFailureMessage("startIntents", "RemoteException"), e);
-            }
-        }
-    }
-
-    public void startShortcutAndTask(ShortcutInfo shortcutInfo, Bundle options1, int taskId,
-            Bundle options2, @StagePosition int splitPosition,
-            @PersistentSnapPosition int snapPosition, RemoteTransition remoteTransition,
-            InstanceId instanceId) {
-        if (mSystemUiProxy != null) {
-            try {
-                mSplitScreen.startShortcutAndTask(shortcutInfo, options1, taskId, options2,
-                        splitPosition, snapPosition, remoteTransition, instanceId);
-            } catch (RemoteException e) {
-                Log.w(TAG, splitFailureMessage("startShortcutAndTask", "RemoteException"), e);
-            }
-        }
-    }
-
-    public void startShortcut(String packageName, String shortcutId, int position,
-            Bundle options, UserHandle user, InstanceId instanceId) {
-        if (mSplitScreen != null) {
-            try {
-                mSplitScreen.startShortcut(packageName, shortcutId, position, options,
-                        user, instanceId);
-            } catch (RemoteException e) {
-                Log.w(TAG, splitFailureMessage("startShortcut", "RemoteException"), e);
-            }
-        }
-    }
-
-    public void startIntent(PendingIntent intent, int userId, Intent fillInIntent, int position,
-            Bundle options, InstanceId instanceId) {
-        if (mSplitScreen != null) {
-            try {
-                mSplitScreen.startIntent(intent, userId, fillInIntent, position, options,
-                        instanceId);
-            } catch (RemoteException e) {
-                Log.w(TAG, splitFailureMessage("startIntent", "RemoteException"), e);
-            }
-        }
-    }
-
-    //
-    // One handed
-    //
-
-    public void startOneHandedMode() {
-        if (mOneHanded != null) {
-            try {
-                mOneHanded.startOneHanded();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startOneHandedMode", e);
-            }
-        }
-    }
-
-    public void stopOneHandedMode() {
-        if (mOneHanded != null) {
-            try {
-                mOneHanded.stopOneHanded();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call stopOneHandedMode", e);
-            }
-        }
-    }
-
-    //
-    // Remote transitions
-    //
-
-    public void registerRemoteTransition(
-            RemoteTransition remoteTransition, TransitionFilter filter) {
-        if (mShellTransitions != null) {
-            try {
-                mShellTransitions.registerRemote(filter, remoteTransition);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call registerRemoteTransition");
-            }
-        }
-        if (!mRemoteTransitions.containsKey(remoteTransition)) {
-            mRemoteTransitions.put(remoteTransition, filter);
-        }
-    }
-
-    public void unregisterRemoteTransition(RemoteTransition remoteTransition) {
-        if (mShellTransitions != null) {
-            try {
-                mShellTransitions.unregisterRemote(remoteTransition);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call registerRemoteTransition");
-            }
-        }
-        mRemoteTransitions.remove(remoteTransition);
-    }
-
-    public HomeVisibilityState getHomeVisibilityState() {
-        return mHomeVisibilityState;
-    }
-
-    public FocusState getFocusState() {
-        return mFocusState;
-    }
-
-    /**
-     * Returns a surface which can be used to attach overlays to home task or null if
-     * the task doesn't exist or sysui is not connected
-     */
-    @Nullable
-    public SurfaceControl getHomeTaskOverlayContainer() {
-        // Use a local reference as this method can be called on a worker thread, which can lead
-        // to NullPointer exceptions if mShellTransitions is modified on the main thread.
-        IShellTransitions shellTransitions = mShellTransitions;
-        if (shellTransitions != null) {
-            try {
-                return mShellTransitions.getHomeTaskOverlayContainer();
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call getOverlayContainerForTask", e);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Use SystemUI's transaction-queue instead of Launcher's independent one. This is necessary
-     * if Launcher and SystemUI need to coordinate transactions (eg. for shell transitions).
-     */
-    public void shareTransactionQueue() {
-        if (mOriginalTransactionToken == null) {
-            mOriginalTransactionToken = SurfaceControl.Transaction.getDefaultApplyToken();
-        }
-        setupTransactionQueue();
-    }
-
-    /**
-     * Switch back to using Launcher's independent transaction queue.
-     */
-    public void unshareTransactionQueue() {
-        if (mOriginalTransactionToken == null) {
-            return;
-        }
-        SurfaceControl.Transaction.setDefaultApplyToken(mOriginalTransactionToken);
-        mOriginalTransactionToken = null;
-    }
-
-    private void setupTransactionQueue() {
-        if (mOriginalTransactionToken == null) {
-            return;
-        }
-        if (mShellTransitions == null) {
-            SurfaceControl.Transaction.setDefaultApplyToken(mOriginalTransactionToken);
-            return;
-        }
-        final IBinder shellApplyToken;
-        try {
-            shellApplyToken = mShellTransitions.getShellApplyToken();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error getting Shell's apply token", e);
-            return;
-        }
-        if (shellApplyToken == null) {
-            Log.e(TAG, "Didn't receive apply token from Shell");
-            return;
-        }
-        SurfaceControl.Transaction.setDefaultApplyToken(shellApplyToken);
-    }
-
-    //
-    // Starting window
-    //
-
-    /**
-     * Sets listener to get callbacks when launching a task.
-     */
-    public void setStartingWindowListener(IStartingWindowListener listener) {
-        if (mStartingWindow != null) {
-            try {
-                mStartingWindow.setStartingWindowListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setStartingWindowListener", e);
-            }
-        }
-        mStartingWindowListener = listener;
-    }
-
-    //
-    // SmartSpace transitions
-    //
-
-    /**
-     * Sets the instance of {@link ILauncherUnlockAnimationController} that System UI should use to
-     * control the launcher side of the unlock animation. This will also cause us to dispatch the
-     * current state of the smartspace to System UI (this will subsequently happen if the state
-     * changes).
-     */
-    public void setLauncherUnlockAnimationController(
-            String activityClass, ILauncherUnlockAnimationController controller) {
-        if (mSysuiUnlockAnimationController != null) {
-            try {
-                mSysuiUnlockAnimationController.setLauncherUnlockController(
-                        activityClass, controller);
-                if (controller != null) {
-                    controller.dispatchSmartspaceStateToSysui();
-                }
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setLauncherUnlockAnimationController", e);
-            }
-        }
-        mLauncherActivityClass = activityClass;
-        mLauncherUnlockAnimationController = controller;
-    }
-
-    /**
-     * Tells System UI that the Launcher's smartspace state has been updated, so that it can prepare
-     * the unlock animation accordingly.
-     */
-    public void notifySysuiSmartspaceStateUpdated(SmartspaceState state) {
-        if (mSysuiUnlockAnimationController != null) {
-            try {
-                mSysuiUnlockAnimationController.onLauncherSmartspaceStateUpdated(state);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call notifySysuiSmartspaceStateUpdated", e);
-                e.printStackTrace();
-            }
-        }
-    }
-
-    //
-    // Recents
-    //
-
-    public void registerRecentTasksListener(IRecentTasksListener listener) {
-        if (mRecentTasks != null) {
-            try {
-                mRecentTasks.registerRecentTasksListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call registerRecentTasksListener", e);
-            }
-        }
-        mRecentTasksListener = listener;
-    }
-
-    public void unregisterRecentTasksListener(IRecentTasksListener listener) {
-        if (mRecentTasks != null) {
-            try {
-                mRecentTasks.unregisterRecentTasksListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call unregisterRecentTasksListener");
-            }
-        }
-        mRecentTasksListener = null;
-    }
-
-    //
-    // Back navigation transitions
-    //
-
-    /** Sets the launcher {@link android.window.IOnBackInvokedCallback} to shell */
-    public void setBackToLauncherCallback(IOnBackInvokedCallback callback,
-            IRemoteAnimationRunner runner) {
-        mBackToLauncherCallback = callback;
-        mBackToLauncherRunner = runner;
-        if (mBackAnimation == null || mBackToLauncherCallback == null) {
-            return;
-        }
-        try {
-            mBackAnimation.setBackToLauncherCallback(callback, runner);
-        } catch (RemoteException | SecurityException e) {
-            Log.e(TAG, "Failed call setBackToLauncherCallback", e);
-        }
-    }
-
-    /** Clears the previously registered {@link IOnBackInvokedCallback}.
-     *
-     * @param callback The previously registered callback instance.
-     */
-    public void clearBackToLauncherCallback(IOnBackInvokedCallback callback) {
-        if (mBackToLauncherCallback != callback) {
-            return;
-        }
-        mBackToLauncherCallback = null;
-        mBackToLauncherRunner = null;
-        if (mBackAnimation == null) {
-            return;
-        }
-        try {
-            mBackAnimation.clearBackToLauncherCallback();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed call clearBackToLauncherCallback", e);
-        }
-    }
-
-    /**
-     * Called when the status bar color needs to be customized when back navigation.
-     */
-    public void customizeStatusBarAppearance(AppearanceRegion appearance) {
-        if (mBackAnimation == null) {
-            return;
-        }
-        try {
-            mBackAnimation.customizeStatusBarAppearance(appearance);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed call useLauncherSysBarFlags", e);
-        }
-    }
-
-    public static class GetRecentTasksException extends Exception {
-        public GetRecentTasksException(String message) {
-            super(message);
-        }
-
-        public GetRecentTasksException(String message, Throwable cause) {
-            super(message, cause);
-        }
-    }
-
-    /**
-     * Retrieves a list of Recent tasks from ActivityManager.
-     * @throws GetRecentTasksException if IRecentTasks is not initialized, or when we get
-     * RemoteException from server side
-     */
-    public ArrayList<GroupedTaskInfo> getRecentTasks(int numTasks,
-            int userId) throws GetRecentTasksException {
-        if (mRecentTasks == null) {
-            Log.e(TAG, "getRecentTasks() failed due to null mRecentTasks");
-            throw new GetRecentTasksException("null mRecentTasks");
-        }
-        try {
-            final GroupedTaskInfo[] rawTasks =
-                    mRecentTasks.getRecentTasks(numTasks, RECENT_IGNORE_UNAVAILABLE, userId);
-            if (rawTasks == null) {
-                return new ArrayList<>();
-            }
-            return new ArrayList<>(Arrays.asList(rawTasks));
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed call getRecentTasks", e);
-            throw new GetRecentTasksException("Failed call getRecentTasks", e);
-        }
-    }
-
-    /**
-     * Gets the set of running tasks.
-     */
-    public ArrayList<ActivityManager.RunningTaskInfo> getRunningTasks(int numTasks) {
-        if (mRecentTasks != null && shouldEnableRunningTasksForDesktopMode()) {
-            try {
-                return new ArrayList<>(Arrays.asList(mRecentTasks.getRunningTasks(numTasks)));
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call getRunningTasks", e);
-            }
-        }
-        return new ArrayList<>();
-    }
-
-    private boolean shouldEnableRunningTasksForDesktopMode() {
-        return DesktopModeStatus.canEnterDesktopMode(mContext)
-                && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS.isTrue();
-    }
-
-    private boolean handleMessageAsync(Message msg) {
-        switch (msg.what) {
-            case MSG_SET_SHELF_HEIGHT:
-                setShelfHeightAsync(msg.arg1, msg.arg2);
-                return true;
-            case MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT:
-                setLauncherKeepClearAreaHeight(msg.arg1, msg.arg2);
-                return true;
-        }
-
-        return false;
-    }
-
-    //
-    // Desktop Mode
-    //
-
-    /** Call shell to show all apps active on the desktop */
-    public void showDesktopApps(int displayId, @Nullable RemoteTransition transition) {
-        if (mDesktopMode != null) {
-            try {
-                mDesktopMode.showDesktopApps(displayId, transition);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call showDesktopApps", e);
-            }
-        }
-    }
-
-    /**
-     * If task with the given id is on the desktop, bring it to front
-     */
-    public void showDesktopApp(int taskId, @Nullable RemoteTransition transition) {
-        if (mDesktopMode != null) {
-            try {
-                mDesktopMode.showDesktopApp(taskId, transition);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call showDesktopApp", e);
-            }
-        }
-    }
-
-    /** Call shell to get number of visible freeform tasks */
-    public int getVisibleDesktopTaskCount(int displayId) {
-        if (mDesktopMode != null) {
-            try {
-                return mDesktopMode.getVisibleTaskCount(displayId);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call getVisibleDesktopTaskCount", e);
-            }
-        }
-        return 0;
-    }
-
-    /** Set a listener on shell to get updates about desktop task state */
-    public void setDesktopTaskListener(@Nullable IDesktopTaskListener listener) {
-        mDesktopTaskListener = listener;
-        if (mDesktopMode != null) {
-            try {
-                mDesktopMode.setTaskListener(listener);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setDesktopTaskListener", e);
-            }
-        }
-    }
-
-    /** Perform cleanup transactions after animation to split select is complete */
-    public void onDesktopSplitSelectAnimComplete(ActivityManager.RunningTaskInfo taskInfo) {
-        if (mDesktopMode != null) {
-            try {
-                mDesktopMode.onDesktopSplitSelectAnimComplete(taskInfo);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call onDesktopSplitSelectAnimComplete", e);
-            }
-        }
-    }
-
-    /** Call shell to move a task with given `taskId` to desktop  */
-    public void moveToDesktop(int taskId, DesktopModeTransitionSource transitionSource,
-            @Nullable RemoteTransition transition) {
-        if (mDesktopMode != null) {
-            try {
-                mDesktopMode.moveToDesktop(taskId, transitionSource, transition);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call moveToDesktop", e);
-            }
-        }
-    }
-
-    /** Call shell to remove the desktop that is on given `displayId` */
-    public void removeDesktop(int displayId) {
-        if (mDesktopMode != null) {
-            try {
-                mDesktopMode.removeDesktop(displayId);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call removeDesktop", e);
-            }
-        }
-    }
-
-    /** Call shell to move a task with given `taskId` to external display. */
-    public void moveToExternalDisplay(int taskId) {
-        if (mDesktopMode != null) {
-            try {
-                mDesktopMode.moveToExternalDisplay(taskId);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call moveToExternalDisplay", e);
-            }
-        }
-    }
-
-    //
-    // Unfold transition
-    //
-
-    /** Sets the unfold animation lister to sysui. */
-    public void setUnfoldAnimationListener(IUnfoldTransitionListener callback) {
-        mUnfoldAnimationListener = callback;
-        if (mUnfoldAnimation == null) {
-            return;
-        }
-        try {
-            Log.d(TAG, "Registering unfold animation receiver");
-            mUnfoldAnimation.setListener(callback);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed call setUnfoldAnimationListener", e);
-        }
-    }
-
-    @Nullable
-    public ProxyUnfoldTransitionProvider getUnfoldTransitionProvider() {
-        return mUnfoldTransitionProvider;
-    }
-
-    //
-    // Recents
-    //
-
-    /**
-     * Starts the recents activity. The caller should manage the thread on which this is called.
-     */
-    public boolean startRecentsActivity(Intent intent, ActivityOptions options,
-            RecentsAnimationListener listener, boolean useSyntheticRecentsTransition) {
-        if (mRecentTasks == null) {
-            ActiveGestureProtoLogProxy.logRecentTasksMissing();
-            return false;
-        }
-        final IRecentsAnimationRunner runner = new IRecentsAnimationRunner.Stub() {
-            @Override
-            public void onAnimationStart(IRecentsAnimationController controller,
-                    RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
-                    Rect homeContentInsets, Rect minimizedHomeBounds, Bundle extras) {
-                // Aidl bundles need to explicitly set class loader
-                // https://developer.android.com/guide/components/aidl#Bundles
-                if (extras != null) {
-                    extras.setClassLoader(SplitBounds.class.getClassLoader());
-                }
-                listener.onAnimationStart(new RecentsAnimationControllerCompat(controller), apps,
-                        wallpapers, homeContentInsets, minimizedHomeBounds, extras);
-            }
-
-            @Override
-            public void onAnimationCanceled(int[] taskIds, TaskSnapshot[] taskSnapshots) {
-                listener.onAnimationCanceled(
-                        ThumbnailData.wrap(taskIds, taskSnapshots));
-            }
-
-            @Override
-            public void onTasksAppeared(RemoteAnimationTarget[] apps) {
-                listener.onTasksAppeared(apps);
-            }
-        };
-        final Bundle optsBundle = options.toBundle();
-        if (useSyntheticRecentsTransition) {
-            optsBundle.putBoolean("is_synthetic_recents_transition", true);
-        }
-        try {
-            mRecentTasks.startRecentsTransition(mRecentsPendingIntent, intent, optsBundle,
-                    mContext.getIApplicationThread(), runner);
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error starting recents via shell", e);
-            return false;
-        }
-    }
-
-    //
-    // Drag and drop
-    //
-
-    /**
-     * For testing purposes.  Returns `true` only if the shell drop target has shown and
-     * drawn and is ready to handle drag events and the subsequent drop.
-     */
-    public boolean isDragAndDropReady() {
-        if (mDragAndDrop == null) {
-            return false;
-        }
-        try {
-            return mDragAndDrop.isReadyToHandleDrag();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error querying drag state", e);
-            return false;
-        }
-    }
-
-    public void dump(PrintWriter pw) {
-        pw.println(TAG + ":");
-
-        pw.println("\tmSystemUiProxy=" + mSystemUiProxy);
-        pw.println("\tmPip=" + mPip);
-        pw.println("\tmPipAnimationListener=" + mPipAnimationListener);
-        pw.println("\tmBubbles=" + mBubbles);
-        pw.println("\tmBubblesListener=" + mBubblesListener);
-        pw.println("\tmSplitScreen=" + mSplitScreen);
-        pw.println("\tmSplitScreenListener=" + mSplitScreenListener);
-        pw.println("\tmSplitSelectListener=" + mSplitSelectListener);
-        pw.println("\tmOneHanded=" + mOneHanded);
-        pw.println("\tmShellTransitions=" + mShellTransitions);
-        pw.println("\tmHomeVisibilityState=" + mHomeVisibilityState);
-        pw.println("\tmFocusState=" + mFocusState);
-        pw.println("\tmStartingWindow=" + mStartingWindow);
-        pw.println("\tmStartingWindowListener=" + mStartingWindowListener);
-        pw.println("\tmSysuiUnlockAnimationController=" + mSysuiUnlockAnimationController);
-        pw.println("\tmLauncherActivityClass=" + mLauncherActivityClass);
-        pw.println("\tmLauncherUnlockAnimationController=" + mLauncherUnlockAnimationController);
-        pw.println("\tmRecentTasks=" + mRecentTasks);
-        pw.println("\tmRecentTasksListener=" + mRecentTasksListener);
-        pw.println("\tmBackAnimation=" + mBackAnimation);
-        pw.println("\tmBackToLauncherCallback=" + mBackToLauncherCallback);
-        pw.println("\tmBackToLauncherRunner=" + mBackToLauncherRunner);
-        pw.println("\tmDesktopMode=" + mDesktopMode);
-        pw.println("\tmDesktopTaskListener=" + mDesktopTaskListener);
-        pw.println("\tmUnfoldAnimation=" + mUnfoldAnimation);
-        pw.println("\tmUnfoldAnimationListener=" + mUnfoldAnimationListener);
-        pw.println("\tmDragAndDrop=" + mDragAndDrop);
-    }
-
-    /**
-     * Adds all interfaces held by this proxy to the bundle
-     */
-    @VisibleForTesting
-    public void addAllInterfaces(Bundle out) {
-        QuickStepContract.addInterface(mSystemUiProxy, out);
-        QuickStepContract.addInterface(mPip, out);
-        QuickStepContract.addInterface(mBubbles, out);
-        QuickStepContract.addInterface(mSysuiUnlockAnimationController, out);
-        QuickStepContract.addInterface(mSplitScreen, out);
-        QuickStepContract.addInterface(mOneHanded, out);
-        QuickStepContract.addInterface(mShellTransitions, out);
-        QuickStepContract.addInterface(mStartingWindow, out);
-        QuickStepContract.addInterface(mRecentTasks, out);
-        QuickStepContract.addInterface(mBackAnimation, out);
-        QuickStepContract.addInterface(mDesktopMode, out);
-        QuickStepContract.addInterface(mUnfoldAnimation, out);
-        QuickStepContract.addInterface(mDragAndDrop, out);
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.kt b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
new file mode 100644
index 0000000..506f85d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.kt
@@ -0,0 +1,1331 @@
+/*
+ * Copyright (C) 2019 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 android.app.ActivityManager
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.ActivityOptions
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ShortcutInfo
+import android.graphics.Point
+import android.graphics.Rect
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.os.Message
+import android.os.RemoteException
+import android.os.UserHandle
+import android.util.Log
+import android.view.IRemoteAnimationRunner
+import android.view.KeyEvent
+import android.view.MotionEvent
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS
+import android.window.IOnBackInvokedCallback
+import android.window.RemoteTransition
+import android.window.TaskSnapshot
+import android.window.TransitionFilter
+import android.window.TransitionInfo
+import androidx.annotation.MainThread
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
+import com.android.internal.logging.InstanceId
+import com.android.internal.util.ScreenshotRequest
+import com.android.internal.view.AppearanceRegion
+import com.android.launcher3.Flags
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.DaggerSingletonObject
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.Preconditions
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
+import com.android.quickstep.util.ActiveGestureProtoLogProxy
+import com.android.quickstep.util.ContextualSearchInvoker
+import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider
+import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.shared.recents.ISystemUiProxy
+import com.android.systemui.shared.recents.model.ThumbnailData.Companion.wrap
+import com.android.systemui.shared.system.QuickStepContract
+import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags
+import com.android.systemui.shared.system.RecentsAnimationControllerCompat
+import com.android.systemui.shared.system.RecentsAnimationListener
+import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
+import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController
+import com.android.systemui.shared.system.smartspace.SmartspaceState
+import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
+import com.android.systemui.unfold.progress.IUnfoldAnimation
+import com.android.systemui.unfold.progress.IUnfoldTransitionListener
+import com.android.wm.shell.back.IBackAnimation
+import com.android.wm.shell.bubbles.IBubbles
+import com.android.wm.shell.bubbles.IBubblesListener
+import com.android.wm.shell.common.pip.IPip
+import com.android.wm.shell.common.pip.IPipAnimationListener
+import com.android.wm.shell.desktopmode.IDesktopMode
+import com.android.wm.shell.desktopmode.IDesktopTaskListener
+import com.android.wm.shell.desktopmode.IMoveToDesktopCallback
+import com.android.wm.shell.draganddrop.IDragAndDrop
+import com.android.wm.shell.onehanded.IOneHanded
+import com.android.wm.shell.recents.IRecentTasks
+import com.android.wm.shell.recents.IRecentTasksListener
+import com.android.wm.shell.recents.IRecentsAnimationController
+import com.android.wm.shell.recents.IRecentsAnimationRunner
+import com.android.wm.shell.shared.GroupedTaskInfo
+import com.android.wm.shell.shared.IShellTransitions
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation.UpdateSource
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
+import com.android.wm.shell.shared.desktopmode.DesktopTaskToFrontReason
+import com.android.wm.shell.shared.split.SplitBounds
+import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition
+import com.android.wm.shell.splitscreen.ISplitScreen
+import com.android.wm.shell.splitscreen.ISplitScreenListener
+import com.android.wm.shell.splitscreen.ISplitSelectListener
+import com.android.wm.shell.startingsurface.IStartingWindow
+import com.android.wm.shell.startingsurface.IStartingWindowListener
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** Holds the reference to SystemUI. */
+@LauncherAppSingleton
+class SystemUiProxy @Inject constructor(@ApplicationContext private val context: Context) :
+    NavHandle {
+
+    private var systemUiProxy: ISystemUiProxy? = null
+    private var pip: IPip? = null
+    private var bubbles: IBubbles? = null
+    private var sysuiUnlockAnimationController: ISysuiUnlockAnimationController? = null
+    private var splitScreen: ISplitScreen? = null
+    private var oneHanded: IOneHanded? = null
+    private var shellTransitions: IShellTransitions? = null
+    private var startingWindow: IStartingWindow? = null
+    private var recentTasks: IRecentTasks? = null
+    private var backAnimation: IBackAnimation? = null
+    private var desktopMode: IDesktopMode? = null
+    private var unfoldAnimation: IUnfoldAnimation? = null
+
+    private val systemUiProxyDeathRecipient =
+        IBinder.DeathRecipient { Executors.MAIN_EXECUTOR.execute { clearProxy() } }
+
+    // Save the listeners passed into the proxy since LauncherProxyService may not have been bound
+    // yet, and we'll need to set/register these listeners with SysUI when they do.  Note that it is
+    // up to the caller to clear the listeners to prevent leaks as these can be held indefinitely
+    // in case SysUI needs to rebind.
+    private var pipAnimationListener: IPipAnimationListener? = null
+    private var bubblesListener: IBubblesListener? = null
+    private var splitScreenListener: ISplitScreenListener? = null
+    private var splitSelectListener: ISplitSelectListener? = null
+    private var startingWindowListener: IStartingWindowListener? = null
+    private var launcherUnlockAnimationController: ILauncherUnlockAnimationController? = null
+    private var launcherActivityClass: String? = null
+    private var recentTasksListener: IRecentTasksListener? = null
+    private var unfoldAnimationListener: IUnfoldTransitionListener? = null
+    private var desktopTaskListener: IDesktopTaskListener? = null
+    private val remoteTransitions = LinkedHashMap<RemoteTransition, TransitionFilter>()
+
+    private val stateChangeCallbacks: MutableList<Runnable> = ArrayList()
+
+    private var originalTransactionToken: IBinder? = null
+    private var backToLauncherCallback: IOnBackInvokedCallback? = null
+    private var backToLauncherRunner: IRemoteAnimationRunner? = null
+    private var dragAndDrop: IDragAndDrop? = null
+    val homeVisibilityState = HomeVisibilityState()
+    val focusState = FocusState()
+
+    // Used to dedupe calls to SystemUI
+    private var lastShelfHeight = 0
+    private var lastShelfVisible = false
+
+    // Used to dedupe calls to SystemUI
+    private var lastLauncherKeepClearAreaHeight = 0
+    private var lastLauncherKeepClearAreaHeightVisible = false
+
+    private val asyncHandler =
+        Handler(Executors.UI_HELPER_EXECUTOR.looper) { handleMessageAsync(it) }
+
+    // TODO(141886704): Find a way to remove this
+    @SystemUiStateFlags var lastSystemUiStateFlags: Long = 0
+
+    /**
+     * This is a singleton pending intent that is used to start recents via Shell (which is a
+     * different process). It is bare-bones, so it's expected that the component and options will be
+     * provided via fill-in intent.
+     */
+    private val recentsPendingIntent by lazy {
+        PendingIntent.getActivity(
+            context,
+            0,
+            Intent().setPackage(context.packageName),
+            PendingIntent.FLAG_MUTABLE or
+                PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT or
+                Intent.FILL_IN_COMPONENT,
+            ActivityOptions.makeBasic()
+                .setPendingIntentCreatorBackgroundActivityStartMode(
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+                )
+                .toBundle(),
+        )
+    }
+
+    val unfoldTransitionProvider: ProxyUnfoldTransitionProvider? =
+        if ((Flags.enableUnfoldStateAnimation() && ResourceUnfoldTransitionConfig().isEnabled))
+            ProxyUnfoldTransitionProvider()
+        else null
+
+    private inline fun executeWithErrorLog(
+        errorMsg: () -> String,
+        tag: String = TAG,
+        callback: () -> Any?,
+    ) {
+        try {
+            callback.invoke()
+        } catch (e: RemoteException) {
+            Log.w(tag, errorMsg.invoke(), e)
+        }
+    }
+
+    fun onBackEvent(backEvent: KeyEvent?) =
+        executeWithErrorLog({ "Failed call onBackPressed" }) {
+            systemUiProxy?.onBackEvent(backEvent)
+        }
+
+    fun onImeSwitcherPressed() =
+        executeWithErrorLog({ "Failed call onImeSwitcherPressed" }) {
+            systemUiProxy?.onImeSwitcherPressed()
+        }
+
+    fun onImeSwitcherLongPress() =
+        executeWithErrorLog({ "Failed call onImeSwitcherLongPress" }) {
+            systemUiProxy?.onImeSwitcherLongPress()
+        }
+
+    fun updateContextualEduStats(isTrackpadGesture: Boolean, gestureType: GestureType) =
+        executeWithErrorLog({ "Failed call updateContextualEduStats" }) {
+            systemUiProxy?.updateContextualEduStats(isTrackpadGesture, gestureType.name)
+        }
+
+    fun setHomeRotationEnabled(enabled: Boolean) =
+        executeWithErrorLog({ "Failed call setHomeRotationEnabled" }) {
+            systemUiProxy?.setHomeRotationEnabled(enabled)
+        }
+
+    /**
+     * Sets proxy state, including death linkage, various listeners, and other configuration objects
+     */
+    @MainThread
+    fun setProxy(
+        proxy: ISystemUiProxy?,
+        pip: IPip?,
+        bubbles: IBubbles?,
+        splitScreen: ISplitScreen?,
+        oneHanded: IOneHanded?,
+        shellTransitions: IShellTransitions?,
+        startingWindow: IStartingWindow?,
+        recentTasks: IRecentTasks?,
+        sysuiUnlockAnimationController: ISysuiUnlockAnimationController?,
+        backAnimation: IBackAnimation?,
+        desktopMode: IDesktopMode?,
+        unfoldAnimation: IUnfoldAnimation?,
+        dragAndDrop: IDragAndDrop?,
+    ) {
+        Preconditions.assertUIThread()
+        unlinkToDeath()
+        systemUiProxy = proxy
+        this.pip = pip
+        this.bubbles = bubbles
+        this.splitScreen = splitScreen
+        this.oneHanded = oneHanded
+        this.shellTransitions = shellTransitions
+        this.startingWindow = startingWindow
+        this.sysuiUnlockAnimationController = sysuiUnlockAnimationController
+        this.recentTasks = recentTasks
+        this.backAnimation = backAnimation
+        this.desktopMode = desktopMode
+        this.unfoldAnimation = if (Flags.enableUnfoldStateAnimation()) null else unfoldAnimation
+        this.dragAndDrop = dragAndDrop
+        linkToDeath()
+        // re-attach the listeners once missing due to setProxy has not been initialized yet.
+        setPipAnimationListener(pipAnimationListener)
+        setBubblesListener(bubblesListener)
+        registerSplitScreenListener(splitScreenListener)
+        registerSplitSelectListener(splitSelectListener)
+        homeVisibilityState.init(this.shellTransitions)
+        focusState.init(this.shellTransitions)
+        setStartingWindowListener(startingWindowListener)
+        setLauncherUnlockAnimationController(
+            launcherActivityClass,
+            launcherUnlockAnimationController,
+        )
+        LinkedHashMap(remoteTransitions).forEach { (remoteTransition, filter) ->
+            registerRemoteTransition(remoteTransition, filter)
+        }
+        setupTransactionQueue()
+        registerRecentTasksListener(recentTasksListener)
+        setBackToLauncherCallback(backToLauncherCallback, backToLauncherRunner)
+        setUnfoldAnimationListener(unfoldAnimationListener)
+        setDesktopTaskListener(desktopTaskListener)
+        setAssistantOverridesRequested(
+            ContextualSearchInvoker(context).getSysUiAssistOverrideInvocationTypes()
+        )
+        stateChangeCallbacks.forEach { it.run() }
+
+        if (unfoldTransitionProvider != null) {
+            if (unfoldAnimation != null) {
+                try {
+                    unfoldAnimation.setListener(unfoldTransitionProvider)
+                    unfoldTransitionProvider.isActive = true
+                } catch (e: RemoteException) {
+                    // Ignore
+                }
+            } else {
+                unfoldTransitionProvider.isActive = false
+            }
+        }
+    }
+
+    /**
+     * Clear the proxy to release held resources and turn the majority of its operations into no-ops
+     */
+    @MainThread
+    fun clearProxy() =
+        setProxy(null, null, null, null, null, null, null, null, null, null, null, null, null)
+
+    /** Adds a callback to be notified whenever the active state changes */
+    fun addOnStateChangeListener(callback: Runnable) = stateChangeCallbacks.add(callback)
+
+    /** Removes a previously added state change callback */
+    fun removeOnStateChangeListener(callback: Runnable) = stateChangeCallbacks.remove(callback)
+
+    fun isActive() = systemUiProxy != null
+
+    private fun linkToDeath() =
+        executeWithErrorLog({ "Failed to link sysui proxy death recipient" }) {
+            systemUiProxy?.asBinder()?.linkToDeath(systemUiProxyDeathRecipient, 0 /* flags */)
+        }
+
+    private fun unlinkToDeath() =
+        systemUiProxy?.asBinder()?.unlinkToDeath(systemUiProxyDeathRecipient, 0 /* flags */)
+
+    fun startScreenPinning(taskId: Int) =
+        executeWithErrorLog({ "Failed call startScreenPinning" }) {
+            systemUiProxy?.startScreenPinning(taskId)
+        }
+
+    fun onOverviewShown(fromHome: Boolean, tag: String = TAG) =
+        executeWithErrorLog(
+            { "Failed call onOverviewShown from: ${(if (fromHome) "home" else "app")}" },
+            tag = tag,
+        ) {
+            systemUiProxy?.onOverviewShown(fromHome)
+        }
+
+    @MainThread
+    fun onStatusBarTouchEvent(event: MotionEvent) {
+        Preconditions.assertUIThread()
+        executeWithErrorLog({ "Failed call onStatusBarTouchEvent with arg: $event" }) {
+            systemUiProxy?.onStatusBarTouchEvent(event)
+        }
+    }
+
+    fun onStatusBarTrackpadEvent(event: MotionEvent) =
+        executeWithErrorLog({ "Failed call onStatusBarTrackpadEvent with arg: $event" }) {
+            systemUiProxy?.onStatusBarTrackpadEvent(event)
+        }
+
+    fun onAssistantProgress(progress: Float) =
+        executeWithErrorLog({ "Failed call onAssistantProgress with progress: $progress" }) {
+            systemUiProxy?.onAssistantProgress(progress)
+        }
+
+    fun onAssistantGestureCompletion(velocity: Float) =
+        executeWithErrorLog({ "Failed call onAssistantGestureCompletion" }) {
+            systemUiProxy?.onAssistantGestureCompletion(velocity)
+        }
+
+    fun startAssistant(args: Bundle) =
+        executeWithErrorLog({ "Failed call startAssistant" }) {
+            systemUiProxy?.startAssistant(args)
+        }
+
+    fun setAssistantOverridesRequested(invocationTypes: IntArray) =
+        executeWithErrorLog({ "Failed call setAssistantOverridesRequested" }) {
+            systemUiProxy?.setAssistantOverridesRequested(invocationTypes)
+        }
+
+    override fun animateNavBarLongPress(isTouchDown: Boolean, shrink: Boolean, durationMs: Long) =
+        executeWithErrorLog({ "Failed call animateNavBarLongPress" }) {
+            systemUiProxy?.animateNavBarLongPress(isTouchDown, shrink, durationMs)
+        }
+
+    fun setOverrideHomeButtonLongPress(duration: Long, slopMultiplier: Float, haptic: Boolean) =
+        executeWithErrorLog({ "Failed call setOverrideHomeButtonLongPress" }) {
+            systemUiProxy?.setOverrideHomeButtonLongPress(duration, slopMultiplier, haptic)
+        }
+
+    fun notifyAccessibilityButtonClicked(displayId: Int) =
+        executeWithErrorLog({ "Failed call notifyAccessibilityButtonClicked" }) {
+            systemUiProxy?.notifyAccessibilityButtonClicked(displayId)
+        }
+
+    fun notifyAccessibilityButtonLongClicked() =
+        executeWithErrorLog({ "Failed call notifyAccessibilityButtonLongClicked" }) {
+            systemUiProxy?.notifyAccessibilityButtonLongClicked()
+        }
+
+    fun stopScreenPinning() =
+        executeWithErrorLog({ "Failed call stopScreenPinning" }) {
+            systemUiProxy?.stopScreenPinning()
+        }
+
+    fun notifyPrioritizedRotation(rotation: Int) =
+        executeWithErrorLog({ "Failed call notifyPrioritizedRotation with arg: $rotation" }) {
+            systemUiProxy?.notifyPrioritizedRotation(rotation)
+        }
+
+    fun notifyTaskbarStatus(visible: Boolean, stashed: Boolean) =
+        executeWithErrorLog({ "Failed call notifyTaskbarStatus with arg: $visible, $stashed" }) {
+            systemUiProxy?.notifyTaskbarStatus(visible, stashed)
+        }
+
+    /**
+     * NOTE: If called to suspend, caller MUST call this method to also un-suspend. [suspend] should
+     * be `true` to stop auto-hide, `false` to resume normal behavior
+     */
+    fun notifyTaskbarAutohideSuspend(suspend: Boolean) =
+        executeWithErrorLog({ "Failed call notifyTaskbarAutohideSuspend with arg: $suspend" }) {
+            systemUiProxy?.notifyTaskbarAutohideSuspend(suspend)
+        }
+
+    fun takeScreenshot(request: ScreenshotRequest) =
+        executeWithErrorLog({ "Failed call takeScreenshot" }) {
+            systemUiProxy?.takeScreenshot(request)
+        }
+
+    fun expandNotificationPanel() =
+        executeWithErrorLog({ "Failed call expandNotificationPanel" }) {
+            systemUiProxy?.expandNotificationPanel()
+        }
+
+    fun toggleNotificationPanel() =
+        executeWithErrorLog({ "Failed call toggleNotificationPanel" }) {
+            systemUiProxy?.toggleNotificationPanel()
+        }
+
+    fun toggleQuickSettingsPanel() =
+        executeWithErrorLog({ "Failed call toggleQuickSettingsPanel" }) {
+            systemUiProxy?.toggleQuickSettingsPanel()
+        }
+
+    //
+    // Pip
+    //
+    /** Sets the shelf height. */
+    fun setShelfHeight(visible: Boolean, shelfHeight: Int) =
+        Message.obtain(asyncHandler, MSG_SET_SHELF_HEIGHT, if (visible) 1 else 0, shelfHeight)
+            .sendToTarget()
+
+    @WorkerThread
+    private fun setShelfHeightAsync(visibleInt: Int, shelfHeight: Int) {
+        val visible = visibleInt != 0
+        val changed = visible != lastShelfVisible || shelfHeight != lastShelfHeight
+        val pip = pip
+        if (pip != null && changed) {
+            lastShelfVisible = visible
+            lastShelfHeight = shelfHeight
+            executeWithErrorLog({
+                "Failed call setShelfHeight visible: $visible height: $shelfHeight"
+            }) {
+                pip.setShelfHeight(visible, shelfHeight)
+            }
+        }
+    }
+
+    /**
+     * Sets the height of the keep clear area that is going to be reported by the Launcher for the
+     * Hotseat.
+     */
+    fun setLauncherKeepClearAreaHeight(visible: Boolean, height: Int) =
+        Message.obtain(
+                asyncHandler,
+                MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT,
+                if (visible) 1 else 0,
+                height,
+            )
+            .sendToTarget()
+
+    @WorkerThread
+    private fun setLauncherKeepClearAreaHeight(visibleInt: Int, height: Int) {
+        val visible = visibleInt != 0
+        val changed =
+            visible != lastLauncherKeepClearAreaHeightVisible ||
+                height != lastLauncherKeepClearAreaHeight
+        val pip = pip
+        if (pip != null && changed) {
+            lastLauncherKeepClearAreaHeightVisible = visible
+            lastLauncherKeepClearAreaHeight = height
+            executeWithErrorLog({
+                "Failed call setLauncherKeepClearAreaHeight visible: $visible height: $height"
+            }) {
+                pip.setLauncherKeepClearAreaHeight(visible, height)
+            }
+        }
+    }
+
+    /** Sets listener to get pip animation callbacks. */
+    fun setPipAnimationListener(listener: IPipAnimationListener?) {
+        executeWithErrorLog({ "Failed call setPinnedStackAnimationListener" }) {
+            pip?.setPipAnimationListener(listener)
+        }
+        pipAnimationListener = listener
+    }
+
+    /** @return Destination bounds of auto-pip animation, `null` if the animation is not ready. */
+    fun startSwipePipToHome(
+        taskInfo: RunningTaskInfo,
+        launcherRotation: Int,
+        hotseatKeepClearArea: Rect?,
+    ): Rect? {
+        executeWithErrorLog({ "Failed call startSwipePipToHome" }) {
+            return pip?.startSwipePipToHome(taskInfo, launcherRotation, hotseatKeepClearArea)
+        }
+        return null
+    }
+
+    /**
+     * Notifies WM Shell that launcher has finished the preparation of the animation for swipe to
+     * home. WM Shell can choose to fade out the overlay when entering PIP is finished, and WM Shell
+     * should be responsible for cleaning up the overlay.
+     */
+    fun stopSwipePipToHome(
+        taskId: Int,
+        componentName: ComponentName?,
+        destinationBounds: Rect?,
+        overlay: SurfaceControl?,
+        appBounds: Rect?,
+        sourceRectHint: Rect?,
+    ) =
+        executeWithErrorLog({ "Failed call stopSwipePipToHome" }) {
+            pip?.stopSwipePipToHome(
+                taskId,
+                componentName,
+                destinationBounds,
+                overlay,
+                appBounds,
+                sourceRectHint,
+            )
+        }
+
+    /**
+     * Notifies WM Shell that launcher has aborted all the animation for swipe to home. WM Shell can
+     * use this callback to clean up its internal states.
+     */
+    fun abortSwipePipToHome(taskId: Int, componentName: ComponentName?) =
+        executeWithErrorLog({ "Failed call abortSwipePipToHome" }) {
+            pip?.abortSwipePipToHome(taskId, componentName)
+        }
+
+    /** Sets the next pip animation type to be the alpha animation. */
+    fun setPipAnimationTypeToAlpha() =
+        executeWithErrorLog({ "Failed call setPipAnimationTypeToAlpha" }) {
+            pip?.setPipAnimationTypeToAlpha()
+        }
+
+    /** Sets the app icon size in pixel used by Launcher all apps. */
+    fun setLauncherAppIconSize(iconSizePx: Int) =
+        executeWithErrorLog({ "Failed call setLauncherAppIconSize" }) {
+            pip?.setLauncherAppIconSize(iconSizePx)
+        }
+
+    //
+    // Bubbles
+    //
+    /** Sets the listener to be notified of bubble state changes. */
+    fun setBubblesListener(listener: IBubblesListener?) {
+        executeWithErrorLog({ "Failed call registerBubblesListener" }) {
+            bubbles?.apply {
+                bubblesListener?.let { unregisterBubbleListener(it) }
+                listener?.let { registerBubbleListener(it) }
+            }
+        }
+        bubblesListener = listener
+    }
+
+    /**
+     * Tells SysUI to show the bubble with the provided key.
+     *
+     * @param key the key of the bubble to show.
+     * @param top top coordinate of bubble bar on screen
+     */
+    fun showBubble(key: String?, top: Int) =
+        executeWithErrorLog({ "Failed call showBubble" }) { bubbles?.showBubble(key, top) }
+
+    /** Tells SysUI to remove all bubbles. */
+    fun removeAllBubbles() =
+        executeWithErrorLog({ "Failed call removeAllBubbles" }) { bubbles?.removeAllBubbles() }
+
+    /** Tells SysUI to collapse the bubbles. */
+    fun collapseBubbles() =
+        executeWithErrorLog({ "Failed call collapseBubbles" }) { bubbles?.collapseBubbles() }
+
+    /**
+     * Tells SysUI when the bubble is being dragged. Should be called only when the bubble bar is
+     * expanded.
+     *
+     * @param bubbleKey key of the bubble being dragged
+     */
+    fun startBubbleDrag(bubbleKey: String?) =
+        executeWithErrorLog({ "Failed call startBubbleDrag" }) {
+            bubbles?.startBubbleDrag(bubbleKey)
+        }
+
+    /**
+     * Tells SysUI when the bubble stops being dragged. Should be called only when the bubble bar is
+     * expanded.
+     *
+     * @param location location of the bubble bar
+     * @param top new top coordinate for bubble bar on screen
+     */
+    fun stopBubbleDrag(location: BubbleBarLocation?, top: Int) =
+        executeWithErrorLog({ "Failed call stopBubbleDrag" }) {
+            bubbles?.stopBubbleDrag(location, top)
+        }
+
+    /**
+     * Tells SysUI to dismiss the bubble with the provided key.
+     *
+     * @param key the key of the bubble to dismiss.
+     * @param timestamp the timestamp when the removal happened.
+     */
+    fun dragBubbleToDismiss(key: String?, timestamp: Long) =
+        executeWithErrorLog({ "Failed call dragBubbleToDismiss" }) {
+            bubbles?.dragBubbleToDismiss(key, timestamp)
+        }
+
+    /**
+     * Tells SysUI to show user education relative to the reference point provided.
+     *
+     * @param position the bubble bar top center position in Screen coordinates.
+     */
+    fun showUserEducation(position: Point) =
+        executeWithErrorLog({ "Failed call showUserEducation" }) {
+            bubbles?.showUserEducation(position.x, position.y)
+        }
+
+    /**
+     * Tells SysUI to update the bubble bar location to the new location.
+     *
+     * @param location new location for the bubble bar
+     * @param source what triggered the location update
+     */
+    fun setBubbleBarLocation(location: BubbleBarLocation?, @UpdateSource source: Int) =
+        executeWithErrorLog({ "Failed call setBubbleBarLocation" }) {
+            bubbles?.setBubbleBarLocation(location, source)
+        }
+
+    /**
+     * Tells SysUI the top coordinate of bubble bar on screen
+     *
+     * @param topOnScreen top coordinate for bubble bar on screen
+     */
+    fun updateBubbleBarTopOnScreen(topOnScreen: Int) =
+        executeWithErrorLog({ "Failed call updateBubbleBarTopOnScreen" }) {
+            bubbles?.updateBubbleBarTopOnScreen(topOnScreen)
+        }
+
+    /**
+     * Tells SysUI to show a shortcut bubble.
+     *
+     * @param info the shortcut info used to create or identify the bubble.
+     * @param bubbleBarLocation the optional location of the bubble bar.
+     */
+    @JvmOverloads
+    fun showShortcutBubble(info: ShortcutInfo?, bubbleBarLocation: BubbleBarLocation? = null) =
+        executeWithErrorLog({ "Failed call showShortcutBubble" }) {
+            bubbles?.showShortcutBubble(info, bubbleBarLocation)
+        }
+
+    /**
+     * Tells SysUI to show a bubble of an app.
+     *
+     * @param intent the intent used to create the bubble.
+     * @param bubbleBarLocation the optional location of the bubble bar.
+     */
+    @JvmOverloads
+    fun showAppBubble(
+        intent: Intent?,
+        user: UserHandle,
+        bubbleBarLocation: BubbleBarLocation? = null,
+    ) =
+        executeWithErrorLog({ "Failed call showAppBubble" }) {
+            bubbles?.showAppBubble(intent, user, bubbleBarLocation)
+        }
+
+    /** Tells SysUI to show the expanded view. */
+    fun showExpandedView() =
+        executeWithErrorLog({ "Failed call showExpandedView" }) { bubbles?.showExpandedView() }
+
+    /** Tells SysUI to show the bubble drop target. */
+    @JvmOverloads
+    fun showBubbleDropTarget(show: Boolean, bubbleBarLocation: BubbleBarLocation? = null) =
+        executeWithErrorLog({ "Failed call showDropTarget" }) {
+            bubbles?.showDropTarget(show, bubbleBarLocation)
+        }
+
+    /** Tells SysUI to move the bubble to full screen. */
+    fun moveBubbleToFullscreen(key: String) {
+        executeWithErrorLog({ "Failed to call moveBubbleToFullscreen"}) {
+            bubbles?.moveBubbleToFullscreen(key)
+        }
+    }
+
+    //
+    // Splitscreen
+    //
+    fun registerSplitScreenListener(listener: ISplitScreenListener?) {
+        executeWithErrorLog({ "Failed call registerSplitScreenListener" }) {
+            splitScreen?.registerSplitScreenListener(listener)
+        }
+        splitScreenListener = listener
+    }
+
+    fun unregisterSplitScreenListener(listener: ISplitScreenListener?) {
+        executeWithErrorLog({ "Failed call unregisterSplitScreenListener" }) {
+            splitScreen?.unregisterSplitScreenListener(listener)
+        }
+        splitScreenListener = null
+    }
+
+    fun registerSplitSelectListener(listener: ISplitSelectListener?) {
+        executeWithErrorLog({ "Failed call registerSplitSelectListener" }) {
+            splitScreen?.registerSplitSelectListener(listener)
+        }
+        splitSelectListener = listener
+    }
+
+    fun unregisterSplitSelectListener(listener: ISplitSelectListener?) {
+        executeWithErrorLog({ "Failed call unregisterSplitSelectListener" }) {
+            splitScreen?.unregisterSplitSelectListener(listener)
+        }
+        splitSelectListener = null
+    }
+
+    /** Start multiple tasks in split-screen simultaneously. */
+    fun startTasks(
+        taskId1: Int,
+        options1: Bundle?,
+        taskId2: Int,
+        options2: Bundle?,
+        @StagePosition splitPosition: Int,
+        @PersistentSnapPosition snapPosition: Int,
+        remoteTransition: RemoteTransition?,
+        instanceId: InstanceId?,
+    ) =
+        executeWithErrorLog({ "Failed call startTasks" }) {
+            splitScreen?.startTasks(
+                taskId1,
+                options1,
+                taskId2,
+                options2,
+                splitPosition,
+                snapPosition,
+                remoteTransition,
+                instanceId,
+            )
+        }
+
+    fun startIntentAndTask(
+        pendingIntent: PendingIntent?,
+        userId1: Int,
+        options1: Bundle?,
+        taskId: Int,
+        options2: Bundle?,
+        @StagePosition splitPosition: Int,
+        @PersistentSnapPosition snapPosition: Int,
+        remoteTransition: RemoteTransition?,
+        instanceId: InstanceId?,
+    ) =
+        executeWithErrorLog({ "Failed call startIntentAndTask" }) {
+            splitScreen?.startIntentAndTask(
+                pendingIntent,
+                userId1,
+                options1,
+                taskId,
+                options2,
+                splitPosition,
+                snapPosition,
+                remoteTransition,
+                instanceId,
+            )
+        }
+
+    fun startIntents(
+        pendingIntent1: PendingIntent?,
+        userId1: Int,
+        shortcutInfo1: ShortcutInfo?,
+        options1: Bundle?,
+        pendingIntent2: PendingIntent?,
+        userId2: Int,
+        shortcutInfo2: ShortcutInfo?,
+        options2: Bundle?,
+        @StagePosition splitPosition: Int,
+        @PersistentSnapPosition snapPosition: Int,
+        remoteTransition: RemoteTransition?,
+        instanceId: InstanceId?,
+    ) =
+        executeWithErrorLog({ "Failed call startIntents" }) {
+            splitScreen?.startIntents(
+                pendingIntent1,
+                userId1,
+                shortcutInfo1,
+                options1,
+                pendingIntent2,
+                userId2,
+                shortcutInfo2,
+                options2,
+                splitPosition,
+                snapPosition,
+                remoteTransition,
+                instanceId,
+            )
+        }
+
+    fun startShortcutAndTask(
+        shortcutInfo: ShortcutInfo?,
+        options1: Bundle?,
+        taskId: Int,
+        options2: Bundle?,
+        @StagePosition splitPosition: Int,
+        @PersistentSnapPosition snapPosition: Int,
+        remoteTransition: RemoteTransition?,
+        instanceId: InstanceId?,
+    ) =
+        executeWithErrorLog({ "Failed call startShortcutAndTask" }) {
+            splitScreen?.startShortcutAndTask(
+                shortcutInfo,
+                options1,
+                taskId,
+                options2,
+                splitPosition,
+                snapPosition,
+                remoteTransition,
+                instanceId,
+            )
+        }
+
+    fun startShortcut(
+        packageName: String?,
+        shortcutId: String?,
+        position: Int,
+        options: Bundle?,
+        user: UserHandle?,
+        instanceId: InstanceId?,
+    ) =
+        executeWithErrorLog({ "Failed call startShortcut" }) {
+            splitScreen?.startShortcut(packageName, shortcutId, position, options, user, instanceId)
+        }
+
+    fun startIntent(
+        intent: PendingIntent?,
+        userId: Int,
+        fillInIntent: Intent?,
+        position: Int,
+        options: Bundle?,
+        instanceId: InstanceId?,
+    ) =
+        executeWithErrorLog({ "Failed call startIntent" }) {
+            splitScreen?.startIntent(intent, userId, fillInIntent, position, options, instanceId)
+        }
+
+    /**
+     * Call the desktop mode interface to start a TRANSIT_OPEN transition when launching an intent
+     * from the taskbar so that it can be handled in desktop mode.
+     */
+    fun startLaunchIntentTransition(intent: Intent, options: Bundle, displayId: Int) =
+        executeWithErrorLog({ "Failed call startLaunchIntentTransition" }) {
+            desktopMode?.startLaunchIntentTransition(intent, options, displayId)
+        }
+
+    //
+    // One handed
+    //
+    fun startOneHandedMode() =
+        executeWithErrorLog({ "Failed call startOneHandedMode" }) { oneHanded?.startOneHanded() }
+
+    fun stopOneHandedMode() =
+        executeWithErrorLog({ "Failed call stopOneHandedMode" }) { oneHanded?.stopOneHanded() }
+
+    //
+    // Remote transitions
+    //
+    fun registerRemoteTransition(remoteTransition: RemoteTransition?, filter: TransitionFilter) {
+        remoteTransition ?: return
+        executeWithErrorLog({ "Failed call registerRemoteTransition" }) {
+            shellTransitions?.registerRemote(filter, remoteTransition)
+        }
+        remoteTransitions.putIfAbsent(remoteTransition, filter)
+    }
+
+    fun unregisterRemoteTransition(remoteTransition: RemoteTransition?) {
+        executeWithErrorLog({ "Failed call unregisterRemoteTransition" }) {
+            shellTransitions?.unregisterRemote(remoteTransition)
+        }
+        remoteTransitions.remove(remoteTransition)
+    }
+
+    /**
+     * Returns a surface which can be used to attach overlays to home task or null if the task
+     * doesn't exist or sysui is not connected
+     */
+    fun getHomeTaskOverlayContainer(): SurfaceControl? {
+        executeWithErrorLog({ "Failed call getHomeTaskOverlayContainer" }) {
+            return shellTransitions?.homeTaskOverlayContainer
+        }
+        return null
+    }
+
+    /**
+     * Use SystemUI's transaction-queue instead of Launcher's independent one. This is necessary if
+     * Launcher and SystemUI need to coordinate transactions (eg. for shell transitions).
+     */
+    fun shareTransactionQueue() {
+        if (originalTransactionToken == null) {
+            originalTransactionToken = Transaction.getDefaultApplyToken()
+        }
+        setupTransactionQueue()
+    }
+
+    /** Switch back to using Launcher's independent transaction queue. */
+    fun unshareTransactionQueue() {
+        if (originalTransactionToken == null) {
+            return
+        }
+        Transaction.setDefaultApplyToken(originalTransactionToken)
+        originalTransactionToken = null
+    }
+
+    private fun setupTransactionQueue() =
+        executeWithErrorLog({ "Error getting Shell's apply token" }) {
+            val token: IBinder =
+                shellTransitions?.shellApplyToken ?: originalTransactionToken ?: return
+            Transaction.setDefaultApplyToken(token)
+        }
+
+    //
+    // Starting window
+    //
+    /** Sets listener to get callbacks when launching a task. */
+    fun setStartingWindowListener(listener: IStartingWindowListener?) {
+        executeWithErrorLog({ "Failed call setStartingWindowListener" }) {
+            startingWindow?.setStartingWindowListener(listener)
+        }
+        startingWindowListener = listener
+    }
+
+    //
+    // SmartSpace transitions
+    //
+    /**
+     * Sets the instance of [ILauncherUnlockAnimationController] that System UI should use to
+     * control the launcher side of the unlock animation. This will also cause us to dispatch the
+     * current state of the smartspace to System UI (this will subsequently happen if the state
+     * changes).
+     */
+    fun setLauncherUnlockAnimationController(
+        activityClass: String?,
+        controller: ILauncherUnlockAnimationController?,
+    ) {
+        executeWithErrorLog({ "Failed call setLauncherUnlockAnimationController" }) {
+            sysuiUnlockAnimationController?.apply {
+                setLauncherUnlockController(activityClass, controller)
+                controller?.dispatchSmartspaceStateToSysui()
+            }
+        }
+        launcherActivityClass = activityClass
+        launcherUnlockAnimationController = controller
+    }
+
+    /**
+     * Tells System UI that the Launcher's smartspace state has been updated, so that it can prepare
+     * the unlock animation accordingly.
+     */
+    fun notifySysuiSmartspaceStateUpdated(state: SmartspaceState?) =
+        executeWithErrorLog({ "Failed call notifySysuiSmartspaceStateUpdated" }) {
+            sysuiUnlockAnimationController?.onLauncherSmartspaceStateUpdated(state)
+        }
+
+    //
+    // Recents
+    //
+    fun registerRecentTasksListener(listener: IRecentTasksListener?) {
+        executeWithErrorLog({ "Failed call registerRecentTasksListener" }) {
+            recentTasks?.registerRecentTasksListener(listener)
+        }
+        recentTasksListener = listener
+    }
+
+    fun unregisterRecentTasksListener(listener: IRecentTasksListener?) {
+        executeWithErrorLog({ "Failed call unregisterRecentTasksListener" }) {
+            recentTasks?.unregisterRecentTasksListener(listener)
+        }
+        recentTasksListener = null
+    }
+
+    //
+    // Back navigation transitions
+    //
+    /** Sets the launcher [android.window.IOnBackInvokedCallback] to shell */
+    fun setBackToLauncherCallback(
+        callback: IOnBackInvokedCallback?,
+        runner: IRemoteAnimationRunner?,
+    ) {
+        backToLauncherCallback = callback
+        backToLauncherRunner = runner
+        if (callback == null) return
+        try {
+            backAnimation?.setBackToLauncherCallback(callback, runner)
+        } catch (e: RemoteException) {
+            Log.e(TAG, "Failed call setBackToLauncherCallback", e)
+        } catch (e: SecurityException) {
+            Log.e(TAG, "Failed call setBackToLauncherCallback", e)
+        }
+    }
+
+    /**
+     * Clears the previously registered [IOnBackInvokedCallback].
+     *
+     * @param callback The previously registered callback instance.
+     */
+    fun clearBackToLauncherCallback(callback: IOnBackInvokedCallback) {
+        if (backToLauncherCallback !== callback) {
+            return
+        }
+        backToLauncherCallback = null
+        backToLauncherRunner = null
+        executeWithErrorLog({ "Failed call clearBackToLauncherCallback" }) {
+            backAnimation?.clearBackToLauncherCallback()
+        }
+    }
+
+    /** Called when the status bar color needs to be customized when back navigation. */
+    fun customizeStatusBarAppearance(appearance: AppearanceRegion?) =
+        executeWithErrorLog({ "Failed call customizeStatusBarAppearance" }) {
+            backAnimation?.customizeStatusBarAppearance(appearance)
+        }
+
+    class GetRecentTasksException : Exception {
+        constructor(message: String?) : super(message)
+
+        constructor(message: String?, cause: Throwable?) : super(message, cause)
+    }
+
+    /**
+     * Retrieves a list of Recent tasks from ActivityManager.
+     *
+     * @throws GetRecentTasksException if IRecentTasks is not initialized, or when we get
+     *   RemoteException from server side
+     */
+    @Throws(GetRecentTasksException::class)
+    fun getRecentTasks(numTasks: Int, userId: Int): ArrayList<GroupedTaskInfo> {
+        if (recentTasks == null) {
+            Log.e(TAG, "getRecentTasks() failed due to null mRecentTasks")
+            throw GetRecentTasksException("null mRecentTasks")
+        }
+        try {
+            val rawTasks =
+                recentTasks?.getRecentTasks(
+                    numTasks,
+                    ActivityManager.RECENT_IGNORE_UNAVAILABLE,
+                    userId,
+                ) ?: return ArrayList()
+            return ArrayList(rawTasks.asList())
+        } catch (e: RemoteException) {
+            Log.e(TAG, "Failed call getRecentTasks", e)
+            throw GetRecentTasksException("Failed call getRecentTasks", e)
+        }
+    }
+
+    /** Gets the set of running tasks. */
+    fun getRunningTasks(numTasks: Int): List<RunningTaskInfo> {
+        if (!shouldEnableRunningTasksForDesktopMode()) return emptyList()
+        executeWithErrorLog({ "Failed call getRunningTasks" }) {
+            return recentTasks?.getRunningTasks(numTasks)?.asList() ?: emptyList()
+        }
+        return emptyList()
+    }
+
+    private fun shouldEnableRunningTasksForDesktopMode(): Boolean =
+        DesktopModeStatus.canEnterDesktopMode(context) &&
+            ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS.isTrue
+
+    private fun handleMessageAsync(msg: Message): Boolean {
+        return when (msg.what) {
+            MSG_SET_SHELF_HEIGHT -> {
+                setShelfHeightAsync(msg.arg1, msg.arg2)
+                true
+            }
+
+            MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT -> {
+                setLauncherKeepClearAreaHeight(msg.arg1, msg.arg2)
+                true
+            }
+
+            else -> false
+        }
+    }
+
+    //
+    // Desktop Mode
+    //
+    /** Calls shell to create a new desk (if possible) on the display whose ID is `displayId`. */
+    fun createDesk(displayId: Int) =
+        executeWithErrorLog({ "Failed call createDesk" }) { desktopMode?.createDesk(displayId) }
+
+    /**
+     * Calls shell to activate the desk whose ID is `deskId` on whatever display it exists on. This
+     * will bring all tasks on this desk to the front.
+     */
+    fun activateDesk(deskId: Int, transition: RemoteTransition?) =
+        executeWithErrorLog({ "Failed call activateDesk" }) {
+            desktopMode?.activateDesk(deskId, transition)
+        }
+
+    /** Calls shell to remove the desk whose ID is `deskId`. */
+    fun removeDesk(deskId: Int) =
+        executeWithErrorLog({ "Failed call removeDesk" }) { desktopMode?.removeDesk(deskId) }
+
+    /** Calls shell to remove all the available desks on all displays. */
+    fun removeAllDesks() =
+        executeWithErrorLog({ "Failed call removeAllDesks" }) { desktopMode?.removeAllDesks() }
+
+    /** Call shell to show all apps active on the desktop */
+    fun showDesktopApps(displayId: Int, transition: RemoteTransition?) =
+        executeWithErrorLog({ "Failed call showDesktopApps" }) {
+            desktopMode?.showDesktopApps(displayId, transition)
+        }
+
+    /** If task with the given id is on the desktop, bring it to front */
+    fun showDesktopApp(
+        taskId: Int,
+        transition: RemoteTransition?,
+        toFrontReason: DesktopTaskToFrontReason,
+    ) =
+        executeWithErrorLog({ "Failed call showDesktopApp" }) {
+            desktopMode?.showDesktopApp(taskId, transition, toFrontReason)
+        }
+
+    /** Set a listener on shell to get updates about desktop task state */
+    fun setDesktopTaskListener(listener: IDesktopTaskListener?) {
+        desktopTaskListener = listener
+        executeWithErrorLog({ "Failed call setDesktopTaskListener" }) {
+            desktopMode?.setTaskListener(listener)
+        }
+    }
+
+    /** Perform cleanup transactions after animation to split select is complete */
+    fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo?) =
+        executeWithErrorLog({ "Failed call onDesktopSplitSelectAnimComplete" }) {
+            desktopMode?.onDesktopSplitSelectAnimComplete(taskInfo)
+        }
+
+    /** Call shell to move a task with given `taskId` to desktop */
+    fun moveToDesktop(
+        taskId: Int,
+        transitionSource: DesktopModeTransitionSource?,
+        transition: RemoteTransition?,
+        successCallback: Runnable,
+    ) =
+        executeWithErrorLog({ "Failed call moveToDesktop" }) {
+            desktopMode?.moveToDesktop(
+                taskId,
+                transitionSource,
+                transition,
+                object : IMoveToDesktopCallback.Stub() {
+                    override fun onTaskMovedToDesktop() {
+                        successCallback.run()
+                    }
+                },
+            )
+        }
+
+    /** Call shell to remove the desktop that is on given `displayId` */
+    fun removeDefaultDeskInDisplay(displayId: Int) =
+        executeWithErrorLog({ "Failed call removeDefaultDeskInDisplay" }) {
+            desktopMode?.removeDefaultDeskInDisplay(displayId)
+        }
+
+    /** Call shell to move a task with given `taskId` to external display. */
+    fun moveToExternalDisplay(taskId: Int) =
+        executeWithErrorLog({ "Failed call moveToExternalDisplay" }) {
+            desktopMode?.moveToExternalDisplay(taskId)
+        }
+
+    //
+    // Unfold transition
+    //
+    /** Sets the unfold animation lister to sysui. */
+    fun setUnfoldAnimationListener(callback: IUnfoldTransitionListener?) {
+        unfoldAnimationListener = callback
+        executeWithErrorLog({ "Failed call setUnfoldAnimationListener" }) {
+            unfoldAnimation?.setListener(callback)
+        }
+    }
+
+    //
+    // Recents
+    //
+    /** Starts the recents activity. The caller should manage the thread on which this is called. */
+    fun startRecentsActivity(
+        intent: Intent?,
+        options: ActivityOptions,
+        listener: RecentsAnimationListener,
+        useSyntheticRecentsTransition: Boolean,
+    ): Boolean {
+        executeWithErrorLog({ "Error starting recents via shell" }) {
+            recentTasks?.startRecentsTransition(
+                recentsPendingIntent,
+                intent,
+                options.toBundle().apply {
+                    if (useSyntheticRecentsTransition) {
+                        putBoolean("is_synthetic_recents_transition", true)
+                    }
+                },
+                context.iApplicationThread,
+                RecentsAnimationListenerStub(listener),
+            )
+                ?: run {
+                    ActiveGestureProtoLogProxy.logRecentTasksMissing()
+                    return false
+                }
+            return true
+        }
+        return false
+    }
+
+    private class RecentsAnimationListenerStub(val listener: RecentsAnimationListener) :
+        IRecentsAnimationRunner.Stub() {
+        override fun onAnimationStart(
+            controller: IRecentsAnimationController,
+            apps: Array<RemoteAnimationTarget>?,
+            wallpapers: Array<RemoteAnimationTarget>?,
+            homeContentInsets: Rect?,
+            minimizedHomeBounds: Rect?,
+            extras: Bundle?,
+            transitionInfo: TransitionInfo?,
+        ) =
+            listener.onAnimationStart(
+                RecentsAnimationControllerCompat(controller),
+                apps,
+                wallpapers,
+                homeContentInsets,
+                minimizedHomeBounds,
+                extras?.apply {
+                    // Aidl bundles need to explicitly set class loader
+                    // https://developer.android.com/guide/components/aidl#Bundles
+                    classLoader = SplitBounds::class.java.classLoader
+                },
+                transitionInfo,
+            )
+
+        override fun onAnimationCanceled(taskIds: IntArray?, taskSnapshots: Array<TaskSnapshot>?) =
+            listener.onAnimationCanceled(wrap(taskIds, taskSnapshots))
+
+        override fun onTasksAppeared(
+            apps: Array<RemoteAnimationTarget>?,
+            transitionInfo: TransitionInfo?,
+        ) {
+            listener.onTasksAppeared(apps, transitionInfo)
+        }
+    }
+
+    //
+    // Drag and drop
+    //
+    /**
+     * For testing purposes. Returns `true` only if the shell drop target has shown and drawn and is
+     * ready to handle drag events and the subsequent drop.
+     */
+    fun isDragAndDropReady(): Boolean {
+        executeWithErrorLog({ "Error querying drag state" }) {
+            return dragAndDrop?.isReadyToHandleDrag ?: false
+        }
+        return false
+    }
+
+    fun dump(pw: PrintWriter) {
+        pw.println("$TAG:")
+
+        pw.println("\tmSystemUiProxy=$systemUiProxy")
+        pw.println("\tmPip=$pip")
+        pw.println("\tmPipAnimationListener=$pipAnimationListener")
+        pw.println("\tmBubbles=$bubbles")
+        pw.println("\tmBubblesListener=$bubblesListener")
+        pw.println("\tmSplitScreen=$splitScreen")
+        pw.println("\tmSplitScreenListener=$splitScreenListener")
+        pw.println("\tmSplitSelectListener=$splitSelectListener")
+        pw.println("\tmOneHanded=$oneHanded")
+        pw.println("\tmShellTransitions=$shellTransitions")
+        pw.println("\tmHomeVisibilityState=" + homeVisibilityState)
+        pw.println("\tmFocusState=" + focusState)
+        pw.println("\tmStartingWindow=$startingWindow")
+        pw.println("\tmStartingWindowListener=$startingWindowListener")
+        pw.println("\tmSysuiUnlockAnimationController=$sysuiUnlockAnimationController")
+        pw.println("\tmLauncherActivityClass=$launcherActivityClass")
+        pw.println("\tmLauncherUnlockAnimationController=$launcherUnlockAnimationController")
+        pw.println("\tmRecentTasks=$recentTasks")
+        pw.println("\tmRecentTasksListener=$recentTasksListener")
+        pw.println("\tmBackAnimation=$backAnimation")
+        pw.println("\tmBackToLauncherCallback=$backToLauncherCallback")
+        pw.println("\tmBackToLauncherRunner=$backToLauncherRunner")
+        pw.println("\tmDesktopMode=$desktopMode")
+        pw.println("\tmDesktopTaskListener=$desktopTaskListener")
+        pw.println("\tmUnfoldAnimation=$unfoldAnimation")
+        pw.println("\tmUnfoldAnimationListener=$unfoldAnimationListener")
+        pw.println("\tmDragAndDrop=$dragAndDrop")
+    }
+
+    /** Adds all interfaces held by this proxy to the bundle */
+    @VisibleForTesting
+    fun addAllInterfaces(out: Bundle) {
+        QuickStepContract.addInterface(systemUiProxy, out)
+        QuickStepContract.addInterface(pip, out)
+        QuickStepContract.addInterface(bubbles, out)
+        QuickStepContract.addInterface(sysuiUnlockAnimationController, out)
+        QuickStepContract.addInterface(splitScreen, out)
+        QuickStepContract.addInterface(oneHanded, out)
+        QuickStepContract.addInterface(shellTransitions, out)
+        QuickStepContract.addInterface(startingWindow, out)
+        QuickStepContract.addInterface(recentTasks, out)
+        QuickStepContract.addInterface(backAnimation, out)
+        QuickStepContract.addInterface(desktopMode, out)
+        QuickStepContract.addInterface(unfoldAnimation, out)
+        QuickStepContract.addInterface(dragAndDrop, out)
+    }
+
+    companion object {
+        private const val TAG = "SystemUiProxy"
+
+        @JvmField val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getSystemUiProxy)
+
+        private const val MSG_SET_SHELF_HEIGHT = 1
+        private const val MSG_SET_LAUNCHER_KEEP_CLEAR_AREA_HEIGHT = 2
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 0ea128a..1c7f23c 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -20,7 +20,6 @@
 import static com.android.launcher3.Flags.enableHandleDelayedGestureCallbacks;
 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.NavigationMode.NO_BUTTON;
 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
 import static com.android.quickstep.GestureState.STATE_END_TARGET_ANIMATION_FINISHED;
@@ -36,23 +35,24 @@
 import android.os.SystemProperties;
 import android.util.Log;
 import android.view.RemoteAnimationTarget;
+import android.window.TransitionInfo;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
 import com.android.internal.util.ArrayUtils;
-import com.android.launcher3.Flags;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.taskbar.TaskbarUIController;
 import com.android.launcher3.util.DisplayController;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
+import com.android.quickstep.fallback.window.RecentsWindowFlags;
 import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
 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;
@@ -63,12 +63,13 @@
 public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAnimationListener {
     public static final boolean SHELL_TRANSITIONS_ROTATION =
             SystemProperties.getBoolean("persist.wm.debug.shell_transit_rotate", false);
-
     private final Context mCtx;
-    private RecentsWindowManager mRecentsWindowsManager;
     private RecentsAnimationController mController;
     private RecentsAnimationCallbacks mCallbacks;
     private RecentsAnimationTargets mTargets;
+    private TransitionInfo mTransitionInfo;
+    private RecentsAnimationDeviceState mDeviceState;
+
     // Temporary until we can hook into gesture state events
     private GestureState mLastGestureState;
     private RemoteAnimationTarget[] mLastAppearedTaskTargets;
@@ -100,24 +101,14 @@
         }
     };
 
-    TaskAnimationManager(Context ctx, RecentsWindowManager manager) {
+    TaskAnimationManager(Context ctx, RecentsAnimationDeviceState deviceState) {
         mCtx = ctx;
-        mRecentsWindowsManager = manager;
+        mDeviceState = deviceState;
     }
     SystemUiProxy getSystemUiProxy() {
         return SystemUiProxy.INSTANCE.get(mCtx);
     }
 
-    /**
-     * Preloads the recents animation.
-     */
-    public void preloadRecentsAnimation(Intent intent) {
-        // Pass null animation handler to indicate this start is for preloading
-        UI_HELPER_EXECUTOR.execute(() -> {
-            ActivityManagerWrapper.getInstance().preloadRecentsActivity(intent);
-        });
-    }
-
     boolean shouldIgnoreMotionEvents() {
         return mShouldIgnoreMotionEvents;
     }
@@ -164,7 +155,7 @@
         mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
             @Override
             public void onRecentsAnimationStart(RecentsAnimationController controller,
-                    RecentsAnimationTargets targets) {
+                    RecentsAnimationTargets targets, @Nullable TransitionInfo transitionInfo) {
                 if (enableHandleDelayedGestureCallbacks() && mRecentsAnimationStartPending) {
                     ActiveGestureProtoLogProxy.logStartRecentsAnimationCallback(
                             "onRecentsAnimationStart");
@@ -178,6 +169,7 @@
                 }
                 mController = controller;
                 mTargets = targets;
+                mTransitionInfo = transitionInfo;
                 // TODO(b/236226779): We can probably get away w/ setting mLastAppearedTaskTargets
                 //  to all appeared targets directly vs just looking at running ones
                 int[] runningTaskIds = mLastGestureState.getRunningTaskIds(targets.apps.length > 1);
@@ -238,7 +230,8 @@
             }
 
             @Override
-            public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
+            public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets,
+                    @Nullable TransitionInfo transitionInfo) {
                 RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
                 BaseContainerInterface containerInterface =
                         mLastGestureState.getContainerInterface();
@@ -271,7 +264,8 @@
                         recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId,
                                 appearedTaskTargets,
                                 new RemoteAnimationTarget[0] /* wallpaper */,
-                                nonAppTargets /* nonApps */);
+                                nonAppTargets /* nonApps */,
+                                transitionInfo);
                         return;
                     } else {
                         ActiveGestureProtoLogProxy.logLaunchingSideTaskFailed();
@@ -291,41 +285,40 @@
         mCallbacks.addListener(listener);
 
         final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setPendingIntentBackgroundActivityStartMode(
+                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
+        options.setTransientLaunch();
+        options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
 
-        // TODO:(b/365777482) if flag is enabled, but on launcher it will crash.
+        // Notify taskbar that we should skip reacting to launcher visibility change to
+        // avoid a jumping taskbar.
+        TaskbarUIController taskbarUIController = containerInterface.getTaskbarController();
+        if (enableScalingRevealHomeAnimation() && taskbarUIController != null) {
+            taskbarUIController.setSkipLauncherVisibilityChange(true);
+
+            mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
+                @Override
+                public void onRecentsAnimationCanceled(
+                        @NonNull HashMap<Integer, ThumbnailData> thumbnailDatas) {
+                    taskbarUIController.setSkipLauncherVisibilityChange(false);
+                }
+
+                @Override
+                public void onRecentsAnimationFinished(
+                        @NonNull RecentsAnimationController controller) {
+                    taskbarUIController.setSkipLauncherVisibilityChange(false);
+                }
+            });
+        }
+
         if(containerInterface.getCreatedContainer() instanceof RecentsWindowManager
-                && (Flags.enableFallbackOverviewInWindow()
-                        || Flags.enableLauncherOverviewInWindow())) {
+                && RecentsWindowFlags.Companion.getEnableOverviewInWindow()) {
             mRecentsAnimationStartPending = getSystemUiProxy().startRecentsActivity(intent, options,
                     mCallbacks, gestureState.useSyntheticRecentsTransition());
-            mRecentsWindowsManager.startRecentsWindow(mCallbacks);
+            RecentsDisplayModel.getINSTANCE().get(mCtx)
+                    .getRecentsWindowManager(gestureState.getDisplayId())
+                    .startRecentsWindow(mCallbacks);
         } else {
-            options.setPendingIntentBackgroundActivityStartMode(
-                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS);
-            options.setTransientLaunch();
-            options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_RECENTS_ANIMATION, eventTime);
-
-            // Notify taskbar that we should skip reacting to launcher visibility change to
-            // avoid a jumping taskbar.
-            TaskbarUIController taskbarUIController = containerInterface.getTaskbarController();
-            if (enableScalingRevealHomeAnimation() && taskbarUIController != null) {
-                taskbarUIController.setSkipLauncherVisibilityChange(true);
-
-                mCallbacks.addListener(new RecentsAnimationCallbacks.RecentsAnimationListener() {
-                    @Override
-                    public void onRecentsAnimationCanceled(
-                            @NonNull HashMap<Integer, ThumbnailData> thumbnailDatas) {
-                        taskbarUIController.setSkipLauncherVisibilityChange(false);
-                    }
-
-                    @Override
-                    public void onRecentsAnimationFinished(
-                            @NonNull RecentsAnimationController controller) {
-                        taskbarUIController.setSkipLauncherVisibilityChange(false);
-                    }
-                });
-            }
-
             mRecentsAnimationStartPending = getSystemUiProxy().startRecentsActivity(intent,
                     options, mCallbacks, false /* useSyntheticRecentsTransition */);
         }
@@ -444,7 +437,7 @@
     public void notifyRecentsAnimationState(
             RecentsAnimationCallbacks.RecentsAnimationListener listener) {
         if (isRecentsAnimationRunning()) {
-            listener.onRecentsAnimationStart(mController, mTargets);
+            listener.onRecentsAnimationStart(mController, mTargets, mTransitionInfo);
         }
         // TODO: Do we actually need to report canceled/finished?
     }
@@ -484,6 +477,7 @@
         mController = null;
         mCallbacks = null;
         mTargets = null;
+        mTransitionInfo = null;
         mLastGestureState = null;
         mLastAppearedTaskTargets = null;
     }
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
deleted file mode 100644
index c4221a1..0000000
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityManager.TaskDescription;
-import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.UserHandle;
-import android.text.TextUtils;
-import android.util.SparseArray;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.WorkerThread;
-
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.icons.BaseIconFactory;
-import com.android.launcher3.icons.BaseIconFactory.IconOptions;
-import com.android.launcher3.icons.BitmapInfo;
-import com.android.launcher3.icons.IconProvider;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.CancellableTask;
-import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
-import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.FlagOp;
-import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.task.thumbnail.data.TaskIconDataSource;
-import com.android.quickstep.util.TaskKeyLruCache;
-import com.android.quickstep.util.TaskVisualsChangeListener;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.Task.TaskKey;
-import com.android.systemui.shared.system.PackageManagerWrapper;
-
-import java.util.concurrent.Executor;
-
-/**
- * Manages the caching of task icons and related data.
- */
-public class TaskIconCache implements TaskIconDataSource, DisplayInfoChangeListener {
-
-    private final Executor mBgExecutor;
-
-    private final Context mContext;
-    private final TaskKeyLruCache<TaskCacheEntry> mIconCache;
-    private final SparseArray<BitmapInfo> mDefaultIcons = new SparseArray<>();
-    private BitmapInfo mDefaultIconBase = null;
-
-    private final IconProvider mIconProvider;
-
-    private BaseIconFactory mIconFactory;
-
-    @Nullable
-    public TaskVisualsChangeListener mTaskVisualsChangeListener = null;
-
-    public TaskIconCache(Context context, Executor bgExecutor, IconProvider iconProvider) {
-        mContext = context;
-        mBgExecutor = bgExecutor;
-        mIconProvider = iconProvider;
-
-        Resources res = context.getResources();
-        int cacheSize = res.getInteger(R.integer.recentsIconCacheSize);
-
-        mIconCache = new TaskKeyLruCache<>(cacheSize);
-
-        DisplayController.INSTANCE.get(mContext).addChangeListener(this);
-    }
-
-    @Override
-    public void onDisplayInfoChanged(Context context, Info info, int flags) {
-        if ((flags & CHANGE_DENSITY) != 0) {
-            clearCache();
-        }
-    }
-
-    /**
-     * Asynchronously fetches the icon and other task data.
-     *
-     * @param task The task to fetch the data for
-     * @param callback The callback to receive the task after its data has been populated.
-     * @return A cancelable handle to the request
-     */
-    @Override
-    public CancellableTask getIconInBackground(Task task, @NonNull GetTaskIconCallback callback) {
-        Preconditions.assertUIThread();
-        if (task.icon != null) {
-            // Nothing to load, the icon is already loaded
-            callback.onTaskIconReceived(task.icon, task.titleDescription, task.title);
-            return null;
-        }
-        CancellableTask<TaskCacheEntry> request = new CancellableTask<>(
-                () -> getCacheEntry(task),
-                MAIN_EXECUTOR,
-                result -> {
-                    task.icon = result.icon;
-                    task.titleDescription = result.contentDescription;
-                    task.title = result.title;
-
-                    callback.onTaskIconReceived(
-                            result.icon,
-                            result.contentDescription,
-                            result.title);
-                    dispatchIconUpdate(task.key.id);
-                }
-        );
-        mBgExecutor.execute(request);
-        return request;
-    }
-
-    /**
-     * Clears the icon cache
-     */
-    public void clearCache() {
-        mBgExecutor.execute(this::resetFactory);
-    }
-
-    void onTaskRemoved(TaskKey taskKey) {
-        mIconCache.remove(taskKey);
-    }
-
-    void invalidateCacheEntries(String pkg, UserHandle handle) {
-        mBgExecutor.execute(() -> mIconCache.removeAll(key ->
-                pkg.equals(key.getPackageName()) && handle.getIdentifier() == key.userId));
-    }
-
-    @WorkerThread
-    private TaskCacheEntry getCacheEntry(Task task) {
-        TaskCacheEntry entry = mIconCache.getAndInvalidateIfModified(task.key);
-        if (entry != null) {
-            return entry;
-        }
-
-        TaskDescription desc = task.taskDescription;
-        TaskKey key = task.key;
-        ActivityInfo activityInfo = null;
-
-        // Create new cache entry
-        entry = new TaskCacheEntry();
-
-        // Load icon
-        // TODO: Load icon resource (b/143363444)
-        Bitmap icon = getIcon(desc, key.userId);
-        if (icon != null) {
-            entry.icon = getBitmapInfo(
-                    new BitmapDrawable(mContext.getResources(), icon),
-                    key.userId,
-                    desc.getPrimaryColor(),
-                    false /* isInstantApp */).newIcon(mContext);
-        } else {
-            activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(
-                    key.getComponent(), key.userId);
-            if (activityInfo != null) {
-                BitmapInfo bitmapInfo = getBitmapInfo(
-                        mIconProvider.getIcon(activityInfo),
-                        key.userId,
-                        desc.getPrimaryColor(),
-                        activityInfo.applicationInfo.isInstantApp());
-                entry.icon = bitmapInfo.newIcon(mContext);
-            } else {
-                entry.icon = getDefaultIcon(key.userId);
-            }
-        }
-
-        // Skip loading the content description if the activity no longer exists
-        if (activityInfo == null) {
-            activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(
-                    key.getComponent(), key.userId);
-        }
-        if (activityInfo != null) {
-            entry.contentDescription = getBadgedContentDescription(
-                    activityInfo, task.key.userId, task.taskDescription);
-            if (enableOverviewIconMenu()) {
-                entry.title = Utilities.trim(activityInfo.loadLabel(mContext.getPackageManager()));
-            }
-        }
-
-        mIconCache.put(task.key, entry);
-        return entry;
-    }
-
-    private Bitmap getIcon(ActivityManager.TaskDescription desc, int userId) {
-        if (desc.getInMemoryIcon() != null) {
-            return desc.getInMemoryIcon();
-        }
-        return ActivityManager.TaskDescription.loadTaskDescriptionIcon(
-                desc.getIconFilename(), userId);
-    }
-
-    private String getBadgedContentDescription(ActivityInfo info, int userId, TaskDescription td) {
-        PackageManager pm = mContext.getPackageManager();
-        String taskLabel = td == null ? null : Utilities.trim(td.getLabel());
-        if (TextUtils.isEmpty(taskLabel)) {
-            taskLabel = Utilities.trim(info.loadLabel(pm));
-        }
-
-        String applicationLabel = Utilities.trim(info.applicationInfo.loadLabel(pm));
-        String badgedApplicationLabel = userId != UserHandle.myUserId()
-                ? pm.getUserBadgedLabel(applicationLabel, UserHandle.of(userId)).toString()
-                : applicationLabel;
-        return applicationLabel.equals(taskLabel)
-                ? badgedApplicationLabel : badgedApplicationLabel + " " + taskLabel;
-    }
-
-    @WorkerThread
-    private Drawable getDefaultIcon(int userId) {
-        synchronized (mDefaultIcons) {
-            if (mDefaultIconBase == null) {
-                try (BaseIconFactory bif = getIconFactory()) {
-                    mDefaultIconBase = bif.makeDefaultIcon(mIconProvider);
-                }
-            }
-
-            int index;
-            if ((index = mDefaultIcons.indexOfKey(userId)) >= 0) {
-                return mDefaultIcons.valueAt(index).newIcon(mContext);
-            } else {
-                BitmapInfo info = mDefaultIconBase.withFlags(
-                        UserCache.INSTANCE.get(mContext).getUserInfo(UserHandle.of(userId))
-                                .applyBitmapInfoFlags(FlagOp.NO_OP));
-                mDefaultIcons.put(userId, info);
-                return info.newIcon(mContext);
-            }
-        }
-    }
-
-    @WorkerThread
-    private BitmapInfo getBitmapInfo(Drawable drawable, int userId,
-            int primaryColor, boolean isInstantApp) {
-        try (BaseIconFactory bif = getIconFactory()) {
-            bif.setWrapperBackgroundColor(primaryColor);
-
-            // User version code O, so that the icon is always wrapped in an adaptive icon container
-            return bif.createBadgedIconBitmap(drawable,
-                    new IconOptions()
-                            .setUser(UserCache.INSTANCE.get(mContext)
-                                    .getUserInfo(UserHandle.of(userId)))
-                            .setInstantApp(isInstantApp)
-                            .setExtractedColor(0));
-        }
-    }
-
-    @WorkerThread
-    private BaseIconFactory getIconFactory() {
-        if (mIconFactory == null) {
-            mIconFactory = new BaseIconFactory(mContext,
-                    DisplayController.INSTANCE.get(mContext).getInfo().getDensityDpi(),
-                    mContext.getResources().getDimensionPixelSize(
-                            R.dimen.task_icon_cache_default_icon_size));
-        }
-        return mIconFactory;
-    }
-
-    @WorkerThread
-    private void resetFactory() {
-        mIconFactory = null;
-        mIconCache.evictAll();
-    }
-
-    private static class TaskCacheEntry {
-        public Drawable icon;
-        public String contentDescription = "";
-        public String title = "";
-    }
-
-    /** Callback used when retrieving app icons from cache. */
-    public interface GetTaskIconCallback {
-        /** Called when task icon is retrieved. */
-        void onTaskIconReceived(Drawable icon, String contentDescription, String title);
-    }
-
-    void registerTaskVisualsChangeListener(TaskVisualsChangeListener newListener) {
-        mTaskVisualsChangeListener = newListener;
-    }
-
-    void removeTaskVisualsChangeListener() {
-        mTaskVisualsChangeListener = null;
-    }
-
-    void dispatchIconUpdate(int taskId) {
-        if (mTaskVisualsChangeListener != null) {
-            mTaskVisualsChangeListener.onTaskIconChanged(taskId);
-        }
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.kt b/quickstep/src/com/android/quickstep/TaskIconCache.kt
new file mode 100644
index 0000000..f0b9b7b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.kt
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.graphics.Bitmap
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.util.SparseArray
+import androidx.annotation.WorkerThread
+import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.Flags.enableRefactorTaskThumbnail
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.icons.BaseIconFactory
+import com.android.launcher3.icons.BaseIconFactory.IconOptions
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.IconProvider
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.CancellableTask
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.FlagOp
+import com.android.launcher3.util.Preconditions
+import com.android.quickstep.task.thumbnail.data.TaskIconDataSource
+import com.android.quickstep.util.IconLabelUtil.getBadgedContentDescription
+import com.android.quickstep.util.TaskKeyLruCache
+import com.android.quickstep.util.TaskVisualsChangeListener
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.Task.TaskKey
+import com.android.systemui.shared.system.PackageManagerWrapper
+import java.util.concurrent.Executor
+
+/** Manages the caching of task icons and related data. */
+class TaskIconCache(
+    private val context: Context,
+    private val bgExecutor: Executor,
+    private val iconProvider: IconProvider,
+    displayController: DisplayController,
+) : TaskIconDataSource, DisplayInfoChangeListener {
+    private val iconCache =
+        TaskKeyLruCache<TaskCacheEntry>(
+            context.resources.getInteger(R.integer.recentsIconCacheSize)
+        )
+    private val defaultIcons = SparseArray<BitmapInfo>()
+    private var defaultIconBase: BitmapInfo? = null
+
+    private var _iconFactory: BaseIconFactory? = null
+    @get:WorkerThread
+    private val iconFactory: BaseIconFactory
+        get() =
+            if (enableRefactorTaskThumbnail()) createIconFactory()
+            else _iconFactory ?: createIconFactory().also { _iconFactory = it }
+
+    var taskVisualsChangeListener: TaskVisualsChangeListener? = null
+
+    init {
+        // TODO (b/397205964): this will need to be updated when we support caches for different
+        //  displays.
+        displayController.addChangeListener(this)
+    }
+
+    override fun onDisplayInfoChanged(context: Context, info: DisplayController.Info, flags: Int) {
+        if ((flags and DisplayController.CHANGE_DENSITY) != 0) {
+            clearCache()
+        }
+    }
+
+    // TODO(b/387496731): Add ensureActive() calls if they show performance benefit
+    override suspend fun getIcon(task: Task): TaskCacheEntry {
+        task.icon?.let {
+            // Nothing to load, the icon is already loaded
+            return TaskCacheEntry(it, task.titleDescription ?: "", task.title)
+        }
+
+        val entry = getCacheEntry(task)
+        task.icon = entry.icon
+        task.titleDescription = entry.contentDescription
+        task.title = entry.title
+
+        dispatchIconUpdate(task.key.id)
+        return entry
+    }
+
+    /**
+     * Asynchronously fetches the icon and other task data.
+     *
+     * @param task The task to fetch the data for
+     * @param callback The callback to receive the task after its data has been populated.
+     * @return A cancelable handle to the request
+     */
+    fun getIconInBackground(task: Task, callback: GetTaskIconCallback): CancellableTask<*>? {
+        Preconditions.assertUIThread()
+        task.icon?.let {
+            // Nothing to load, the icon is already loaded
+            callback.onTaskIconReceived(it, task.titleDescription ?: "", task.title ?: "")
+            return null
+        }
+        val request =
+            CancellableTask(
+                { getCacheEntry(task) },
+                Executors.MAIN_EXECUTOR,
+                { result: TaskCacheEntry ->
+                    task.icon = result.icon
+                    task.titleDescription = result.contentDescription
+                    task.title = result.title
+
+                    callback.onTaskIconReceived(
+                        result.icon,
+                        result.contentDescription,
+                        result.title,
+                    )
+                    dispatchIconUpdate(task.key.id)
+                },
+            )
+        bgExecutor.execute(request)
+        return request
+    }
+
+    /** Clears the icon cache */
+    fun clearCache() {
+        bgExecutor.execute { resetFactory() }
+    }
+
+    fun onTaskRemoved(taskKey: TaskKey) {
+        iconCache.remove(taskKey)
+    }
+
+    fun invalidateCacheEntries(pkg: String, handle: UserHandle) {
+        bgExecutor.execute {
+            iconCache.removeAll { key: TaskKey ->
+                pkg == key.packageName && handle.identifier == key.userId
+            }
+        }
+    }
+
+    @WorkerThread
+    private fun createIconFactory() =
+        BaseIconFactory(
+            context,
+            DisplayController.INSTANCE.get(context).info.densityDpi,
+            context.resources.getDimensionPixelSize(R.dimen.task_icon_cache_default_icon_size),
+        )
+
+    @WorkerThread
+    private fun getCacheEntry(task: Task): TaskCacheEntry {
+        iconCache.getAndInvalidateIfModified(task.key)?.let {
+            return it
+        }
+
+        val desc = task.taskDescription
+        val key = task.key
+        var activityInfo: ActivityInfo? = null
+
+        // Create new cache entry
+
+        // Load icon
+        val icon = getIcon(desc, key.userId)
+        val entryIcon =
+            if (icon != null) {
+                getBitmapInfo(
+                        BitmapDrawable(context.resources, icon),
+                        key.userId,
+                        desc.primaryColor,
+                        false, /* isInstantApp */
+                    )
+                    .newIcon(context)
+            } else {
+                activityInfo =
+                    PackageManagerWrapper.getInstance().getActivityInfo(key.component, key.userId)
+                if (activityInfo != null) {
+                    val bitmapInfo =
+                        getBitmapInfo(
+                            iconProvider.getIcon(activityInfo),
+                            key.userId,
+                            desc.primaryColor,
+                            activityInfo.applicationInfo.isInstantApp,
+                        )
+                    bitmapInfo.newIcon(context)
+                } else {
+                    getDefaultIcon(key.userId)
+                }
+            }
+
+        activityInfo =
+            activityInfo
+                ?: PackageManagerWrapper.getInstance().getActivityInfo(key.component, key.userId)
+
+        return when {
+            // Skip loading the content description if the activity no longer exists
+            activityInfo == null -> TaskCacheEntry(entryIcon)
+            enableOverviewIconMenu() ->
+                TaskCacheEntry(
+                    entryIcon,
+                    getBadgedContentDescription(
+                        context,
+                        activityInfo,
+                        task.key.userId,
+                        task.taskDescription,
+                    ),
+                    Utilities.trim(activityInfo.loadLabel(context.packageManager)),
+                )
+            else ->
+                TaskCacheEntry(
+                    entryIcon,
+                    getBadgedContentDescription(
+                        context,
+                        activityInfo,
+                        task.key.userId,
+                        task.taskDescription,
+                    ),
+                )
+        }.also { iconCache.put(task.key, it) }
+    }
+
+    private fun getIcon(desc: ActivityManager.TaskDescription, userId: Int): Bitmap? =
+        desc.inMemoryIcon
+            ?: ActivityManager.TaskDescription.loadTaskDescriptionIcon(desc.iconFilename, userId)
+
+    @WorkerThread
+    private fun getDefaultIcon(userId: Int): Drawable {
+        synchronized(defaultIcons) {
+            val defaultIconBase =
+                defaultIconBase ?: iconFactory.use { it.makeDefaultIcon(iconProvider) }
+            val index: Int = defaultIcons.indexOfKey(userId)
+            return if (index >= 0) {
+                defaultIcons.valueAt(index).newIcon(context)
+            } else {
+                val info =
+                    defaultIconBase.withFlags(
+                        UserCache.INSTANCE.get(context)
+                            .getUserInfo(UserHandle.of(userId))
+                            .applyBitmapInfoFlags(FlagOp.NO_OP)
+                    )
+                defaultIcons.put(userId, info)
+                info.newIcon(context)
+            }
+        }
+    }
+
+    @WorkerThread
+    private fun getBitmapInfo(
+        drawable: Drawable,
+        userId: Int,
+        primaryColor: Int,
+        isInstantApp: Boolean,
+    ): BitmapInfo {
+        iconFactory.use { iconFactory ->
+            iconFactory.setWrapperBackgroundColor(primaryColor)
+            // User version code O, so that the icon is always wrapped in an adaptive icon container
+            return iconFactory.createBadgedIconBitmap(
+                drawable,
+                IconOptions()
+                    .setUser(UserCache.INSTANCE.get(context).getUserInfo(UserHandle.of(userId)))
+                    .setInstantApp(isInstantApp)
+                    .setExtractedColor(0),
+            )
+        }
+    }
+
+    @WorkerThread
+    private fun resetFactory() {
+        _iconFactory = null
+        iconCache.evictAll()
+    }
+
+    data class TaskCacheEntry(
+        val icon: Drawable,
+        val contentDescription: String = "",
+        val title: String = "",
+    )
+
+    /** Callback used when retrieving app icons from cache. */
+    fun interface GetTaskIconCallback {
+        /** Called when task icon is retrieved. */
+        fun onTaskIconReceived(icon: Drawable, contentDescription: String, title: String)
+    }
+
+    fun registerTaskVisualsChangeListener(newListener: TaskVisualsChangeListener?) {
+        taskVisualsChangeListener = newListener
+    }
+
+    fun removeTaskVisualsChangeListener() {
+        taskVisualsChangeListener = null
+    }
+
+    private fun dispatchIconUpdate(taskId: Int) {
+        taskVisualsChangeListener?.onTaskIconChanged(taskId)
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
index ff9c9f6..df66a5e 100644
--- a/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskOverlayFactory.java
@@ -42,7 +42,7 @@
 import com.android.launcher3.util.ResourceBasedOverride;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.Snackbar;
-import com.android.quickstep.task.util.TaskOverlayHelper;
+import com.android.quickstep.recents.domain.usecase.ThumbnailPosition;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.views.DesktopTaskView;
 import com.android.quickstep.views.GroupedTaskView;
@@ -52,6 +52,7 @@
 import com.android.quickstep.views.TaskContainer;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.shared.recents.model.ThumbnailData;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -120,7 +121,8 @@
             TaskShortcutFactory.WELLBEING,
             TaskShortcutFactory.SAVE_APP_PAIR,
             TaskShortcutFactory.SCREENSHOT,
-            TaskShortcutFactory.MODAL
+            TaskShortcutFactory.MODAL,
+            TaskShortcutFactory.CLOSE,
     };
 
     /**
@@ -133,43 +135,50 @@
 
         private T mActionsView;
         protected ImageActionsApi mImageApi;
-        protected TaskOverlayHelper mHelper;
+        private ThumbnailData mThumbnailData = null;
 
         protected TaskOverlay(TaskContainer taskContainer) {
             mApplicationContext = taskContainer.getTaskView().getContext().getApplicationContext();
             mTaskContainer = taskContainer;
-            if (enableRefactorTaskThumbnail()) {
-                mHelper = new TaskOverlayHelper(mTaskContainer.getTask(), this);
-            }
             mImageApi = new ImageActionsApi(mApplicationContext, this::getThumbnail);
         }
 
-        /**
-         * Initialize the overlay when a Task is bound to the TaskView.
-         */
-        public void init() {
-            if (enableRefactorTaskThumbnail()) {
-                mHelper.init();
-            }
-        }
-
-        /**
-         * Destroy the overlay when the TaskView is recycled.
-         */
-        public void destroy() {
-            if (enableRefactorTaskThumbnail()) {
-                mHelper.destroy();
-            }
+        public void setThumbnailState(@Nullable ThumbnailData thumbnailData) {
+            mThumbnailData = thumbnailData;
         }
 
         protected @Nullable Bitmap getThumbnail() {
-            return enableRefactorTaskThumbnail() ? mHelper.getEnabledState().getThumbnail()
-                    : mTaskContainer.getThumbnailViewDeprecated().getThumbnail();
+            if (enableRefactorTaskThumbnail()) {
+                return mThumbnailData == null ? null : mThumbnailData.getThumbnail();
+            } else {
+                return mTaskContainer.getThumbnailViewDeprecated().getThumbnail();
+            }
+        }
+        /**
+         * Returns whether the snapshot is real. If the device is locked for the user of the task,
+         * the snapshot used will be an app-theme generated snapshot instead of a real snapshot.
+         */
+        protected boolean isRealSnapshot() {
+            if (enableRefactorTaskThumbnail()) {
+                if (mThumbnailData == null) return false;
+
+                return mThumbnailData.isRealSnapshot && !mTaskContainer.getTask().isLocked;
+            } else {
+                return mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot();
+            }
         }
 
-        protected boolean isRealSnapshot() {
-            return enableRefactorTaskThumbnail() ? mHelper.getEnabledState().isRealSnapshot()
-                    : mTaskContainer.getThumbnailViewDeprecated().isRealSnapshot();
+        /**
+         * Returns whether the snapshot is rotated compared to the current task orientation.
+         */
+        public boolean isThumbnailRotationDifferentFromTask() {
+            if (enableRefactorTaskThumbnail()) {
+                ThumbnailPosition thumbnailPosition = mTaskContainer.getThumbnailPosition();
+                return thumbnailPosition != null && thumbnailPosition.isRotated();
+            }
+
+            return mTaskContainer.getThumbnailViewDeprecated()
+                    .isThumbnailRotationDifferentFromTask();
         }
 
         protected T getActionsView() {
@@ -233,7 +242,7 @@
             RecentsView overviewPanel = mTaskContainer.getTaskView().getRecentsView();
             // Task has already been dismissed
             if (overviewPanel == null) return;
-            overviewPanel.initiateSplitSelect(mTaskContainer.getTaskView());
+            overviewPanel.initiateSplitSelect(mTaskContainer);
         }
 
         protected void saveAppPair() {
@@ -315,9 +324,16 @@
             // inverse tells us where the view would be in the bitmaps coordinates. The insets are
             // the difference between the bitmap bounds and the projected view bounds.
             Matrix boundsToBitmapSpace = new Matrix();
-            Matrix thumbnailMatrix = enableRefactorTaskThumbnail()
-                    ? mHelper.getThumbnailMatrix()
-                    : mTaskContainer.getThumbnailViewDeprecated().getThumbnailMatrix();
+            Matrix thumbnailMatrix;
+            if (enableRefactorTaskThumbnail()) {
+                if (mTaskContainer.getThumbnailPosition() != null) {
+                    thumbnailMatrix = mTaskContainer.getThumbnailPosition().getMatrix();
+                } else {
+                    thumbnailMatrix = Matrix.IDENTITY_MATRIX;
+                }
+            } else {
+                thumbnailMatrix = mTaskContainer.getThumbnailViewDeprecated().getThumbnailMatrix();
+            }
             thumbnailMatrix.invert(boundsToBitmapSpace);
             RectF boundsInBitmapSpace = new RectF();
             boundsToBitmapSpace.mapRect(boundsInBitmapSpace, viewRect);
@@ -358,18 +374,15 @@
 
         private class ScreenshotSystemShortcut extends SystemShortcut {
 
-            private final RecentsViewContainer mContainer;
-
             ScreenshotSystemShortcut(RecentsViewContainer container, ItemInfo itemInfo,
                     View originalView) {
                 super(R.drawable.ic_screenshot, R.string.action_screenshot, container, itemInfo,
                         originalView);
-                mContainer = container;
             }
 
             @Override
             public void onClick(View view) {
-                saveScreenshot(mTaskContainer.getTaskView().getFirstTask());
+                saveScreenshot(mTaskContainer.getTask());
                 dismissTaskMenuView();
             }
         }
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index 785666f..bc46ace 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -20,7 +20,10 @@
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.view.Surface.ROTATION_0;
 
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
 import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
+import static com.android.launcher3.Flags.enableShowEnabledShortcutsInAccessibilityMenu;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_CLOSE_APP_TAP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_FREE_FORM_TAP;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 
@@ -41,13 +44,13 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
 import com.android.launcher3.model.WellbeingModel;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.popup.SystemShortcut.AppInfo;
 import com.android.launcher3.util.InstantAppResolver;
+import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
@@ -128,20 +131,28 @@
     };
 
     class SplitSelectSystemShortcut extends SystemShortcut {
-        private final TaskView mTaskView;
+        private final TaskContainer mTaskContainer;
         private final SplitPositionOption mSplitPositionOption;
 
-        public SplitSelectSystemShortcut(RecentsViewContainer container, TaskView taskView,
+        public SplitSelectSystemShortcut(RecentsViewContainer container,
+                TaskContainer taskContainer, TaskView taskView,
                 SplitPositionOption option) {
-            super(option.iconResId, option.textResId, container, taskView.getFirstItemInfo(),
+            super(option.iconResId, option.textResId, container, taskContainer.getItemInfo(),
                     taskView);
-            mTaskView = taskView;
+            mTaskContainer = taskContainer;
             mSplitPositionOption = option;
         }
 
         @Override
         public void onClick(View view) {
-            mTaskView.initiateSplitSelect(mSplitPositionOption);
+            RecentsView recentsView = mTaskContainer.getTaskView().getRecentsView();
+            if (recentsView != null) {
+                recentsView.initiateSplitSelect(
+                        mTaskContainer,
+                        mSplitPositionOption.stagePosition,
+                        SplitConfigurationOptions.getLogEventForPosition(
+                                mSplitPositionOption.stagePosition));
+            }
         }
     }
 
@@ -152,11 +163,9 @@
     class SaveAppPairSystemShortcut extends SystemShortcut<RecentsViewContainer> {
         private final GroupedTaskView mTaskView;
 
-
         public SaveAppPairSystemShortcut(RecentsViewContainer container, GroupedTaskView taskView,
             int iconResId) {
-            super(iconResId, R.string.save_app_pair, container, taskView.getFirstItemInfo(),
-                    taskView);
+            super(iconResId, R.string.save_app_pair, container, taskView.getItemInfo(), taskView);
             mTaskView = taskView;
         }
 
@@ -202,14 +211,14 @@
         }
 
         private void startActivity() {
-            final Task.TaskKey taskKey = mTaskView.getFirstTask().key;
-            final int taskId = taskKey.id;
             final ActivityOptions options = makeLaunchOptions(mTarget);
-            if (options != null) {
-                options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+            if (options == null) {
+                return;
             }
-            if (options != null
-                    && ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
+            final Task.TaskKey taskKey = mTaskContainer.getTask().key;
+            final int taskId = taskKey.id;
+            options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+            if (ActivityManagerWrapper.getInstance().startActivityFromRecents(taskId,
                     options)) {
                 final Runnable animStartedListener = () -> {
                     // Hide the task view and wait for the window to be resized
@@ -228,15 +237,14 @@
                         position[0] + width, position[1] + height);
 
                 // Take the thumbnail of the task without a scrim and apply it back after
-                // TODO(b/348643341) add ability to get override the scrim for this Bitmap retrieval
-                float alpha = 0f;
-                if (!enableRefactorTaskThumbnail()) {
-                    alpha = mTaskContainer.getThumbnailViewDeprecated().getDimAlpha();
+                Bitmap thumbnail;
+                if (enableRefactorTaskThumbnail()) {
+                    thumbnail = mTaskContainer.getThumbnail();
+                } else {
+                    float alpha = mTaskContainer.getThumbnailViewDeprecated().getDimAlpha();
                     mTaskContainer.getThumbnailViewDeprecated().setDimAlpha(0);
-                }
-                Bitmap thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
-                        taskBounds.width(), taskBounds.height(), snapShotView, 1f, Color.BLACK);
-                if (!enableRefactorTaskThumbnail()) {
+                    thumbnail = RecentsTransition.drawViewIntoHardwareBitmap(
+                            taskBounds.width(), taskBounds.height(), snapShotView, 1f, Color.BLACK);
                     mTaskContainer.getThumbnailViewDeprecated().setDimAlpha(alpha);
                 }
 
@@ -252,8 +260,8 @@
                 overridePendingAppTransitionMultiThumbFuture(
                         future, animStartedListener, mHandler, true /* scaleUp */,
                         taskKey.displayId);
-                mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getFirstItemInfo())
-                        .log(mLauncherEvent);
+                mTarget.getStatsLogManager().logger().withItemInfo(mTaskContainer.getItemInfo())
+                            .log(mLauncherEvent);
             }
         }
 
@@ -289,6 +297,29 @@
         }
     }
 
+    class CloseSystemShortcut extends SystemShortcut {
+        private final TaskContainer mTaskContainer;
+
+        public CloseSystemShortcut(int iconResId, int textResId, RecentsViewContainer container,
+                TaskContainer taskContainer) {
+            super(iconResId, textResId, container, taskContainer.getTaskView().getFirstItemInfo(),
+                    taskContainer.getTaskView());
+            mTaskContainer = taskContainer;
+        }
+
+        @Override
+        public void onClick(View view) {
+            TaskView taskView = mTaskContainer.getTaskView();
+            RecentsView<?, ?> recentsView = taskView.getRecentsView();
+            if (recentsView != null) {
+                dismissTaskMenuView();
+                recentsView.dismissTaskView(taskView, true, true);
+                mTarget.getStatsLogManager().logger().withItemInfo(mTaskContainer.getItemInfo())
+                        .log(LAUNCHER_SYSTEM_SHORTCUT_CLOSE_APP_TAP);
+            }
+        }
+    }
+
     /**
      * Does NOT add split options in the following scenarios:
      * * 1. Taskbar is not present AND aren't at least 2 tasks in overview to show split options for
@@ -315,19 +346,25 @@
             boolean isTaskSplitNotSupported = !task.isDockable ||
                     (intentFlags & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
             boolean hideForExistingMultiWindow = container.getDeviceProfile().isMultiWindowMode;
-            boolean isLargeTile = deviceProfile.isTablet && taskView.isLargeTile();
-            boolean isTaskInExpectedScrollPosition =
-                    recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
 
-            if (notEnoughTasksToSplit || isTaskSplitNotSupported || hideForExistingMultiWindow
-                    || (isLargeTile && isTaskInExpectedScrollPosition)) {
+            if (notEnoughTasksToSplit || isTaskSplitNotSupported || hideForExistingMultiWindow) {
                 return null;
             }
 
+            if (!enableShowEnabledShortcutsInAccessibilityMenu()) {
+                boolean isLargeTile = deviceProfile.isTablet && taskView.isLargeTile();
+                boolean isTaskInExpectedScrollPosition =
+                        recentsView.isTaskInExpectedScrollPosition(taskView);
+                if (isLargeTile && isTaskInExpectedScrollPosition) {
+                    return null;
+                }
+            }
+
             return orientationHandler.getSplitPositionOptions(deviceProfile)
                     .stream()
                     .map((Function<SplitPositionOption, SystemShortcut>) option ->
-                            new SplitSelectSystemShortcut(container, taskView, option))
+                            new SplitSelectSystemShortcut(container, taskContainer, taskView,
+                                    option))
                     .collect(Collectors.toList());
         }
     };
@@ -342,7 +379,7 @@
             final RecentsView recentsView = taskView.getRecentsView();
             boolean isLargeTile = deviceProfile.isTablet && taskView.isLargeTile();
             boolean isInExpectedScrollPosition =
-                    recentsView.isTaskInExpectedScrollPosition(recentsView.indexOfChild(taskView));
+                    recentsView.isTaskInExpectedScrollPosition(taskView);
             boolean shouldShowActionsButtonInstead =
                     isLargeTile && isInExpectedScrollPosition;
 
@@ -420,24 +457,24 @@
 
         private static final String TAG = "PinSystemShortcut";
 
-        private final TaskView mTaskView;
+        private final TaskContainer mTaskContainer;
 
         public PinSystemShortcut(RecentsViewContainer target,
                 TaskContainer taskContainer) {
             super(R.drawable.ic_pin, R.string.recent_task_option_pin, target,
                     taskContainer.getItemInfo(), taskContainer.getTaskView());
-            mTaskView = taskContainer.getTaskView();
+            mTaskContainer = taskContainer;
         }
 
         @Override
         public void onClick(View view) {
-            if (mTaskView.launchAsStaticTile() != null) {
+            if (mTaskContainer.getTaskView().launchAsStaticTile() != null) {
                 SystemUiProxy.INSTANCE.get(mTarget.asContext()).startScreenPinning(
-                        mTaskView.getFirstTask().key.id);
+                        mTaskContainer.getTask().key.id);
             }
             dismissTaskMenuView();
-            mTarget.getStatsLogManager().logger().withItemInfo(mTaskView.getFirstItemInfo())
-                    .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
+            mTarget.getStatsLogManager().logger().withItemInfo(mTaskContainer.getItemInfo())
+                        .log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_PIN_TAP);
         }
     }
 
@@ -469,16 +506,23 @@
         @Override
         public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
                 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) {
+            if (enableShowEnabledShortcutsInAccessibilityMenu()) {
+                if (!taskContainer.getOverlay().isRealSnapshot()) {
                     return null;
                 }
+            } else {
+                boolean isTablet = container.getDeviceProfile().isTablet;
+                boolean isGridOnlyOverview = isTablet && 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;
+                    }
+                }
             }
 
             SystemShortcut screenshotShortcut = taskContainer.getOverlay().getScreenshotShortcut(
@@ -496,10 +540,39 @@
         @Override
         public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
                 TaskContainer taskContainer) {
-            boolean isTablet = container.getDeviceProfile().isTablet;
-            boolean isGridOnlyOverview = isTablet && Flags.enableGridOnlyOverview();
-            if (!isGridOnlyOverview) {
-                return null;
+            if (enableShowEnabledShortcutsInAccessibilityMenu()) {
+                if (!taskContainer.getOverlay().isRealSnapshot()) {
+                    return null;
+                }
+
+                // Modal only works with grid size tiles with enableGridOnlyOverview enabled on
+                // tablets / foldables. With enableGridOnlyOverview off, for large tiles it works,
+                // but the tile needs to be in the center of Recents / Overview.
+                boolean isTablet = container.getDeviceProfile().isTablet;
+                RecentsView recentsView = container.getOverviewPanel();
+                boolean isLargeTileInCenterOfOverview = taskContainer.getTaskView().isLargeTile()
+                        && recentsView.isFocusedTaskInExpectedScrollPosition();
+                if (isTablet
+                        && !isLargeTileInCenterOfOverview
+                        && !enableGridOnlyOverview()) {
+                    return null;
+                }
+
+                boolean isFakeLandscape = !taskContainer.getTaskView().getPagedOrientationHandler()
+                        .isLayoutNaturalToLauncher();
+                if (isFakeLandscape) {
+                    return null;
+                }
+
+                if (taskContainer.getOverlay().isThumbnailRotationDifferentFromTask()) {
+                    return null;
+                }
+            } else {
+                boolean isTablet = container.getDeviceProfile().isTablet;
+                boolean isGridOnlyOverview = isTablet && enableGridOnlyOverview();
+                if (!isGridOnlyOverview) {
+                    return null;
+                }
             }
 
             SystemShortcut modalStateSystemShortcut =
@@ -508,4 +581,24 @@
             return createSingletonShortcutList(modalStateSystemShortcut);
         }
     };
+
+    TaskShortcutFactory CLOSE = new TaskShortcutFactory() {
+        @Override
+        public List<SystemShortcut> getShortcuts(RecentsViewContainer container,
+                TaskContainer taskContainer) {
+            return Collections.singletonList(new CloseSystemShortcut(
+                    R.drawable.ic_close_option,
+                    R.string.recent_task_option_close, container, taskContainer));
+        }
+
+        @Override
+        public boolean showForGroupedTask() {
+            return true;
+        }
+
+        @Override
+        public boolean showForDesktopTask() {
+            return true;
+        }
+    };
 }
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java b/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
deleted file mode 100644
index 580dcc2..0000000
--- a/quickstep/src/com/android/quickstep/TaskThumbnailCache.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep;
-
-import static com.android.launcher3.Flags.enableGridOnlyOverview;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.content.Context;
-import android.content.res.Resources;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.launcher3.R;
-import com.android.launcher3.util.CancellableTask;
-import com.android.launcher3.util.Preconditions;
-import com.android.quickstep.recents.data.HighResLoadingStateNotifier;
-import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource;
-import com.android.quickstep.util.TaskKeyByLastActiveTimeCache;
-import com.android.quickstep.util.TaskKeyCache;
-import com.android.quickstep.util.TaskKeyLruCache;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.recents.model.Task.TaskKey;
-import com.android.systemui.shared.recents.model.ThumbnailData;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
-
-import java.util.ArrayList;
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-public class TaskThumbnailCache implements TaskThumbnailDataSource {
-
-    private final Executor mBgExecutor;
-    private final TaskKeyCache<ThumbnailData> mCache;
-    private final HighResLoadingState mHighResLoadingState;
-    private final boolean mEnableTaskSnapshotPreloading;
-    private final Context mContext;
-
-    public static class HighResLoadingState implements HighResLoadingStateNotifier {
-        private boolean mForceHighResThumbnails;
-        private boolean mVisible;
-        private boolean mFlingingFast;
-        private boolean mHighResLoadingEnabled;
-        private ArrayList<HighResLoadingStateChangedCallback> mCallbacks = new ArrayList<>();
-
-        public interface HighResLoadingStateChangedCallback {
-            void onHighResLoadingStateChanged(boolean enabled);
-        }
-
-        private HighResLoadingState(Context context) {
-            // If the device does not support low-res thumbnails, only attempt to load high-res
-            // thumbnails
-            mForceHighResThumbnails = !supportsLowResThumbnails();
-        }
-
-        @Override
-        public void addCallback(@NonNull HighResLoadingStateChangedCallback callback) {
-            mCallbacks.add(callback);
-        }
-
-        @Override
-        public void removeCallback(@NonNull HighResLoadingStateChangedCallback callback) {
-            mCallbacks.remove(callback);
-        }
-
-        public void setVisible(boolean visible) {
-            mVisible = visible;
-            updateState();
-        }
-
-        public void setFlingingFast(boolean flingingFast) {
-            mFlingingFast = flingingFast;
-            updateState();
-        }
-
-        public boolean isEnabled() {
-            return mHighResLoadingEnabled;
-        }
-
-        private void updateState() {
-            boolean prevState = mHighResLoadingEnabled;
-            mHighResLoadingEnabled = mForceHighResThumbnails || (mVisible && !mFlingingFast);
-            if (prevState != mHighResLoadingEnabled) {
-                for (int i = mCallbacks.size() - 1; i >= 0; i--) {
-                    mCallbacks.get(i).onHighResLoadingStateChanged(mHighResLoadingEnabled);
-                }
-            }
-        }
-    }
-
-    public TaskThumbnailCache(Context context, Executor bgExecutor) {
-        this(context, bgExecutor,
-                context.getResources().getInteger(R.integer.recentsThumbnailCacheSize));
-    }
-
-    private TaskThumbnailCache(Context context, Executor bgExecutor, int cacheSize) {
-        this(context, bgExecutor,
-                enableGridOnlyOverview() ? new TaskKeyByLastActiveTimeCache<>(cacheSize)
-                        : new TaskKeyLruCache<>(cacheSize));
-    }
-
-    @VisibleForTesting
-    TaskThumbnailCache(Context context, Executor bgExecutor, TaskKeyCache<ThumbnailData> cache) {
-        mBgExecutor = bgExecutor;
-        mHighResLoadingState = new HighResLoadingState(context);
-        mContext = context;
-
-        Resources res = context.getResources();
-        mEnableTaskSnapshotPreloading = res.getBoolean(R.bool.config_enableTaskSnapshotPreloading);
-        mCache = cache;
-    }
-
-    /**
-     * Synchronously fetches the thumbnail for the given task at the specified resolution level, and
-     * puts it in the cache.
-     */
-    public void updateThumbnailInCache(Task task, boolean lowResolution) {
-        if (task == null) {
-            return;
-        }
-        Preconditions.assertUIThread();
-        // Fetch the thumbnail for this task and put it in the cache
-        if (task.thumbnail == null) {
-            getThumbnailInBackground(task.key, lowResolution, t -> task.thumbnail = t);
-        }
-    }
-
-    /**
-     * Synchronously updates the thumbnail in the cache if it is already there.
-     */
-    public void updateTaskSnapShot(int taskId, ThumbnailData thumbnail) {
-        Preconditions.assertUIThread();
-        mCache.updateIfAlreadyInCache(taskId, thumbnail);
-    }
-
-    /**
-     * Asynchronously fetches the thumbnail for the given {@code task}.
-     *
-     * @param callback The callback to receive the task after its data has been populated.
-     * @return A cancelable handle to the request
-     */
-    @Override
-    public CancellableTask<ThumbnailData> getThumbnailInBackground(
-            Task task, @NonNull Consumer<ThumbnailData> callback) {
-        Preconditions.assertUIThread();
-
-        boolean lowResolution = !mHighResLoadingState.isEnabled();
-        if (task.thumbnail != null && task.thumbnail.getThumbnail() != null
-                && (!task.thumbnail.reducedResolution || lowResolution)) {
-            // Nothing to load, the thumbnail is already high-resolution or matches what the
-            // request, so just callback
-            callback.accept(task.thumbnail);
-            return null;
-        }
-
-        return getThumbnailInBackground(task.key, !mHighResLoadingState.isEnabled(), callback);
-    }
-
-    /**
-     * Updates cache size and remove excess entries if current size is more than new cache size.
-     *
-     * @return whether cache size has increased
-     */
-    public boolean updateCacheSizeAndRemoveExcess() {
-        int newSize = mContext.getResources().getInteger(R.integer.recentsThumbnailCacheSize);
-        int oldSize = mCache.getMaxSize();
-        if (newSize == oldSize) {
-            // Return if no change in size
-            return false;
-        }
-
-        mCache.updateCacheSizeAndRemoveExcess(newSize);
-        return newSize > oldSize;
-    }
-
-    private CancellableTask<ThumbnailData> getThumbnailInBackground(TaskKey key,
-            boolean lowResolution, Consumer<ThumbnailData> callback) {
-        Preconditions.assertUIThread();
-
-        ThumbnailData cachedThumbnail = mCache.getAndInvalidateIfModified(key);
-        if (cachedThumbnail != null &&  cachedThumbnail.getThumbnail() != null
-                && (!cachedThumbnail.reducedResolution || lowResolution)) {
-            // Already cached, lets use that thumbnail
-            callback.accept(cachedThumbnail);
-            return null;
-        }
-
-        CancellableTask<ThumbnailData> request = new CancellableTask<>(
-                () -> {
-                    ThumbnailData thumbnailData = ActivityManagerWrapper.getInstance()
-                            .getTaskThumbnail(key.id, lowResolution);
-                    return thumbnailData.getThumbnail() != null ? thumbnailData
-                            : ActivityManagerWrapper.getInstance().takeTaskThumbnail(key.id);
-                },
-                MAIN_EXECUTOR,
-                result -> {
-                    // Avoid an async timing issue that a low res entry replaces an existing high
-                    // res entry in high res enabled state, so we check before putting it to cache
-                    if (enableGridOnlyOverview() && result.reducedResolution
-                            && getHighResLoadingState().isEnabled()) {
-                        ThumbnailData newCachedThumbnail = mCache.getAndInvalidateIfModified(key);
-                        if (newCachedThumbnail != null && newCachedThumbnail.getThumbnail() != null
-                                && !newCachedThumbnail.reducedResolution) {
-                            return;
-                        }
-                    }
-                    mCache.put(key, result);
-                    callback.accept(result);
-                }
-        );
-        mBgExecutor.execute(request);
-        return request;
-    }
-
-    /**
-     * Clears the cache.
-     */
-    public void clear() {
-        mCache.evictAll();
-    }
-
-    /**
-     * Removes the cached thumbnail for the given task.
-     */
-    public void remove(Task.TaskKey key) {
-        mCache.remove(key);
-    }
-
-    /**
-     * @return The cache size.
-     */
-    public int getCacheSize() {
-        return mCache.getMaxSize();
-    }
-
-    /**
-     * @return The mutable high-res loading state.
-     */
-    public HighResLoadingState getHighResLoadingState() {
-        return mHighResLoadingState;
-    }
-
-    /**
-     * @return Whether to enable background preloading of task thumbnails.
-     */
-    public boolean isPreloadingEnabled() {
-        return mEnableTaskSnapshotPreloading && mHighResLoadingState.mVisible;
-    }
-
-    /**
-     * @return Whether device supports low-res thumbnails. Low-res files are an optimization
-     * for faster load times of snapshots. Devices can optionally disable low-res files so that
-     * they only store snapshots at high-res scale. The actual scale can be configured in
-     * frameworks/base config overlay.
-     */
-    private static boolean supportsLowResThumbnails() {
-        Resources res = Resources.getSystem();
-        int resId = res.getIdentifier("config_lowResTaskSnapshotScale", "dimen", "android");
-        if (resId != 0) {
-            return 0 < res.getFloat(resId);
-        }
-        return true;
-    }
-
-}
diff --git a/quickstep/src/com/android/quickstep/TaskThumbnailCache.kt b/quickstep/src/com/android/quickstep/TaskThumbnailCache.kt
new file mode 100644
index 0000000..1d880ab
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/TaskThumbnailCache.kt
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep
+
+import android.content.Context
+import androidx.annotation.VisibleForTesting
+import androidx.annotation.WorkerThread
+import com.android.launcher3.Flags.enableGridOnlyOverview
+import com.android.launcher3.R
+import com.android.launcher3.util.CancellableTask
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.Preconditions
+import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource
+import com.android.quickstep.util.TaskKeyByLastActiveTimeCache
+import com.android.quickstep.util.TaskKeyCache
+import com.android.quickstep.util.TaskKeyLruCache
+import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.Task.TaskKey
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.system.ActivityManagerWrapper
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+
+class TaskThumbnailCache
+@VisibleForTesting
+internal constructor(
+    private val context: Context,
+    private val bgExecutor: Executor,
+    private val cache: TaskKeyCache<ThumbnailData>,
+) : TaskThumbnailDataSource {
+    val highResLoadingState = HighResLoadingState()
+    private val enableTaskSnapshotPreloading =
+        context.resources.getBoolean(R.bool.config_enableTaskSnapshotPreloading)
+
+    @JvmOverloads
+    constructor(
+        context: Context,
+        bgExecutor: Executor,
+        cacheSize: Int = context.resources.getInteger(R.integer.recentsThumbnailCacheSize),
+    ) : this(
+        context,
+        bgExecutor,
+        if (enableGridOnlyOverview()) TaskKeyByLastActiveTimeCache(cacheSize)
+        else TaskKeyLruCache(cacheSize),
+    )
+
+    /**
+     * Synchronously fetches the thumbnail for the given task at the specified resolution level, and
+     * puts it in the cache.
+     */
+    fun updateThumbnailInCache(task: Task?, lowResolution: Boolean) {
+        task ?: return
+
+        Preconditions.assertUIThread()
+        // Fetch the thumbnail for this task and put it in the cache
+        if (task.thumbnail == null) {
+            getThumbnailInBackground(task.key, lowResolution) { t: ThumbnailData? ->
+                task.thumbnail = t
+            }
+        }
+    }
+
+    /** Synchronously updates the thumbnail in the cache if it is already there. */
+    fun updateTaskSnapShot(taskId: Int, thumbnail: ThumbnailData?) {
+        Preconditions.assertUIThread()
+        cache.updateIfAlreadyInCache(taskId, thumbnail)
+    }
+
+    // TODO(b/387496731): Add ensureActive() calls if they show performance benefit
+    /**
+     * Retrieves a thumbnail for the provided `task` on the current thread. This should not be
+     * called from the main thread.
+     */
+    @WorkerThread
+    override suspend fun getThumbnail(task: Task): ThumbnailData? {
+        val lowResolution: Boolean = !highResLoadingState.isEnabled
+        // Check task for thumbnail
+        val taskThumbnail: ThumbnailData? = task.thumbnail
+        if (
+            taskThumbnail?.thumbnail != null && (!taskThumbnail.reducedResolution || lowResolution)
+        ) {
+            return taskThumbnail
+        }
+
+        // Check cache for thumbnail
+        val cachedThumbnail: ThumbnailData? = cache.getAndInvalidateIfModified(task.key)
+        if (
+            cachedThumbnail?.thumbnail != null &&
+                (!cachedThumbnail.reducedResolution || lowResolution)
+        ) {
+            return cachedThumbnail
+        }
+
+        // Get thumbnail from system
+        var thumbnailData =
+            ActivityManagerWrapper.getInstance().getTaskThumbnail(task.key.id, lowResolution)
+        if (thumbnailData.thumbnail == null) {
+            thumbnailData = ActivityManagerWrapper.getInstance().takeTaskThumbnail(task.key.id)
+        }
+
+        // Avoid an async timing issue that a low res entry replaces an existing high
+        // res entry in high res enabled state, so we check before putting it to cache
+        if (
+            enableGridOnlyOverview() &&
+                thumbnailData.reducedResolution &&
+                highResLoadingState.isEnabled
+        ) {
+            val newCachedThumbnail = cache.getAndInvalidateIfModified(task.key)
+            if (newCachedThumbnail?.thumbnail != null && !newCachedThumbnail.reducedResolution) {
+                return newCachedThumbnail
+            }
+        }
+        cache.put(task.key, thumbnailData)
+        return thumbnailData
+    }
+
+    /**
+     * Asynchronously fetches the thumbnail for the given `task`.
+     *
+     * @param callback The callback to receive the task after its data has been populated.
+     * @return a cancelable handle to the request
+     */
+    fun getThumbnailInBackground(
+        task: Task,
+        callback: Consumer<ThumbnailData>,
+    ): CancellableTask<ThumbnailData>? {
+        Preconditions.assertUIThread()
+
+        val lowResolution = !highResLoadingState.isEnabled
+        val taskThumbnail = task.thumbnail
+        if (
+            taskThumbnail?.thumbnail != null && (!taskThumbnail.reducedResolution || lowResolution)
+        ) {
+            // Nothing to load, the thumbnail is already high-resolution or matches what the
+            // request, so just callback
+            callback.accept(taskThumbnail)
+            return null
+        }
+
+        return getThumbnailInBackground(task.key, !highResLoadingState.isEnabled, callback)
+    }
+
+    /**
+     * Updates cache size and remove excess entries if current size is more than new cache size.
+     *
+     * @return whether cache size has increased
+     */
+    fun updateCacheSizeAndRemoveExcess(): Boolean {
+        val newSize = context.resources.getInteger(R.integer.recentsThumbnailCacheSize)
+        val oldSize = cache.maxSize
+        if (newSize == oldSize) {
+            // Return if no change in size
+            return false
+        }
+
+        cache.updateCacheSizeAndRemoveExcess(newSize)
+        return newSize > oldSize
+    }
+
+    private fun getThumbnailInBackground(
+        key: TaskKey,
+        lowResolution: Boolean,
+        callback: Consumer<ThumbnailData>,
+    ): CancellableTask<ThumbnailData>? {
+        Preconditions.assertUIThread()
+
+        val cachedThumbnail = cache.getAndInvalidateIfModified(key)
+        if (
+            cachedThumbnail?.thumbnail != null &&
+                (!cachedThumbnail.reducedResolution || lowResolution)
+        ) {
+            // Already cached, lets use that thumbnail
+            callback.accept(cachedThumbnail)
+            return null
+        }
+
+        val request =
+            CancellableTask(
+                {
+                    val thumbnailData =
+                        ActivityManagerWrapper.getInstance().getTaskThumbnail(key.id, lowResolution)
+                    if (thumbnailData.thumbnail != null) thumbnailData
+                    else ActivityManagerWrapper.getInstance().takeTaskThumbnail(key.id)
+                },
+                Executors.MAIN_EXECUTOR,
+                Consumer { result: ThumbnailData ->
+                    // Avoid an async timing issue that a low res entry replaces an existing high
+                    // res entry in high res enabled state, so we check before putting it to cache
+                    if (
+                        enableGridOnlyOverview() &&
+                            result.reducedResolution &&
+                            highResLoadingState.isEnabled
+                    ) {
+                        val newCachedThumbnail = cache.getAndInvalidateIfModified(key)
+                        if (
+                            newCachedThumbnail?.thumbnail != null &&
+                                !newCachedThumbnail.reducedResolution
+                        ) {
+                            return@Consumer
+                        }
+                    }
+                    cache.put(key, result)
+                    callback.accept(result)
+                },
+            )
+        bgExecutor.execute(request)
+        return request
+    }
+
+    /** Clears the cache. */
+    fun clear() {
+        cache.evictAll()
+    }
+
+    /** Removes the cached thumbnail for the given task. */
+    fun remove(key: TaskKey) {
+        cache.remove(key)
+    }
+
+    /** Returns The cache size. */
+    fun getCacheSize() = cache.maxSize
+
+    /** Returns Whether to enable background preloading of task thumbnails. */
+    fun isPreloadingEnabled() = enableTaskSnapshotPreloading && highResLoadingState.visible
+}
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index ecb17f6..855ff98 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -22,6 +22,8 @@
 import static com.android.app.animation.Interpolators.LINEAR;
 import static com.android.app.animation.Interpolators.TOUCH_RESPONSE;
 import static com.android.app.animation.Interpolators.clampToProgress;
+import static com.android.launcher3.Flags.enableDesktopExplodedView;
+import static com.android.launcher3.Flags.enableGridOnlyOverview;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
@@ -37,7 +39,9 @@
 import static com.android.launcher3.QuickstepTransitionManager.SPLIT_LAUNCH_DURATION;
 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
+import static com.android.quickstep.BaseContainerInterface.getTaskDimension;
 import static com.android.quickstep.util.AnimUtils.clampToDuration;
+import static com.android.wm.shell.shared.TransitionUtil.TYPE_SPLIT_SCREEN_DIM_LAYER;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -63,6 +67,7 @@
 import com.android.app.animation.Interpolators;
 import com.android.internal.jank.Cuj;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -100,6 +105,11 @@
 
     private TaskViewUtils() {}
 
+    private static final Rect TEMP_THUMBNAIL_BOUNDS = new Rect();
+    private static final Rect TEMP_FULLSCREEN_BOUNDS = new Rect();
+    private static final PointF TEMP_TASK_DIMENSION = new PointF();
+    private static final PointF TEMP_PIVOT = new PointF();
+
     /**
      * Try to find a TaskView that corresponds with the component of the launched view.
      *
@@ -108,7 +118,7 @@
      * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
      */
     public static TaskView findTaskViewToLaunch(
-            RecentsView recentsView, View v, RemoteAnimationTarget[] targets) {
+            RecentsView<?, ?> recentsView, View v, RemoteAnimationTarget[] targets) {
         if (v instanceof TaskView) {
             TaskView taskView = (TaskView) v;
             return recentsView.isTaskViewVisible(taskView) ? taskView : null;
@@ -121,10 +131,10 @@
             ComponentName componentName = itemInfo.getTargetComponent();
             int userId = itemInfo.user.getIdentifier();
             if (componentName != null) {
-                for (int i = 0; i < recentsView.getTaskViewCount(); i++) {
-                    TaskView taskView = recentsView.getTaskViewAt(i);
-                    if (recentsView.isTaskViewVisible(taskView)) {
-                        Task.TaskKey key = taskView.getFirstTask().key;
+                for (TaskView taskView : recentsView.getTaskViews()) {
+                    Task firstTask = taskView.getFirstTask();
+                    if (firstTask != null && recentsView.isTaskViewVisible(taskView)) {
+                        Task.TaskKey key = firstTask.key;
                         if (componentName.equals(key.getComponent()) && userId == key.userId) {
                             return taskView;
                         }
@@ -162,38 +172,42 @@
     public static <T extends Context & RecentsViewContainer & StatefulContainer<?>>
     void createRecentsWindowAnimator(
             @NonNull RecentsView<T, ?> recentsView,
-            @NonNull TaskView v,
+            @NonNull TaskView taskView,
             boolean skipViewChanges,
             @NonNull RemoteAnimationTarget[] appTargets,
             @NonNull RemoteAnimationTarget[] wallpaperTargets,
             @NonNull RemoteAnimationTarget[] nonAppTargets,
             @Nullable DepthController depthController,
+            @Nullable TransitionInfo transitionInfo,
             PendingAnimation out) {
-        boolean isQuickSwitch = v.isEndQuickSwitchCuj();
-        v.setEndQuickSwitchCuj(false);
+        boolean isQuickSwitch = taskView.isEndQuickSwitchCuj();
+        taskView.setEndQuickSwitchCuj(false);
 
         final RemoteAnimationTargets targets =
                 new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets,
                         MODE_OPENING);
         final RemoteAnimationTarget navBarTarget = targets.getNavBarRemoteAnimationTarget();
 
-        SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
+        SurfaceTransactionApplier applier = new SurfaceTransactionApplier(taskView);
         targets.addReleaseCheck(applier);
 
         RemoteTargetHandle[] remoteTargetHandles;
         RemoteTargetHandle[] recentsViewHandles = recentsView.getRemoteTargetHandles();
-        if (v.isRunningTask() && recentsViewHandles != null) {
+        if (taskView.isRunningTask() && recentsViewHandles != null) {
             // Re-use existing handles
             remoteTargetHandles = recentsViewHandles;
         } else {
-            boolean forDesktop = v instanceof DesktopTaskView;
-            RemoteTargetGluer gluer = new RemoteTargetGluer(v.getContext(),
+            boolean forDesktop = taskView instanceof DesktopTaskView;
+            RemoteTargetGluer gluer = new RemoteTargetGluer(taskView.getContext(),
                     recentsView.getSizeStrategy(), targets, forDesktop);
             if (forDesktop) {
-                remoteTargetHandles = gluer.assignTargetsForDesktop(targets);
-            } else if (v.containsMultipleTasks()) {
+                remoteTargetHandles = gluer.assignTargetsForDesktop(targets, transitionInfo);
+                if (enableDesktopExplodedView()) {
+                    ((DesktopTaskView) taskView).setRemoteTargetHandles(remoteTargetHandles);
+                }
+            } else if (taskView.containsMultipleTasks()) {
                 remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets,
-                        ((GroupedTaskView) v).getSplitBoundsConfig());
+                        ((GroupedTaskView) taskView).getSplitBoundsConfig());
             } else {
                 remoteTargetHandles = gluer.assignTargets(targets);
             }
@@ -207,8 +221,8 @@
             remoteTargetHandle.getTransformParams().setSyncTransactionApplier(applier);
         }
 
-        int taskIndex = recentsView.indexOfChild(v);
-        Context context = v.getContext();
+        int taskIndex = recentsView.indexOfChild(taskView);
+        Context context = taskView.getContext();
 
         T container = RecentsViewContainer.containerFromContext(context);
         DeviceProfile dp = container.getDeviceProfile();
@@ -216,11 +230,11 @@
         boolean parallaxCenterAndAdjacentTask =
                 !showAsGrid && taskIndex != recentsView.getCurrentPage();
         int taskRectTranslationPrimary = recentsView.getScrollOffset(taskIndex);
-        int taskRectTranslationSecondary = showAsGrid ? (int) v.getGridTranslationY() : 0;
+        int taskRectTranslationSecondary = showAsGrid ? (int) taskView.getGridTranslationY() : 0;
 
         RemoteTargetHandle[] topMostSimulators = null;
 
-        if (!v.isRunningTask()) {
+        if (!taskView.isRunningTask()) {
             // TVSs already initialized from the running task, no need to re-init
             for (RemoteTargetHandle targetHandle : remoteTargetHandles) {
                 TaskViewSimulator tvsLocal = targetHandle.getTaskViewSimulator();
@@ -233,12 +247,14 @@
 
                 tvsLocal.fullScreenProgress.value = 0;
                 tvsLocal.recentsViewScale.value = 1;
-                tvsLocal.setIsGridTask(v.isGridTask());
+                if (!enableGridOnlyOverview()) {
+                    tvsLocal.setIsGridTask(taskView.isGridTask());
+                }
                 tvsLocal.getOrientationState().getOrientationHandler().set(tvsLocal,
                         TaskViewSimulator::setTaskRectTranslation, taskRectTranslationPrimary,
                         taskRectTranslationSecondary);
 
-                if (v instanceof DesktopTaskView) {
+                if (taskView instanceof DesktopTaskView) {
                     targetHandle.getTransformParams().setTargetAlpha(1f);
                 } else {
                     // Fade in the task during the initial 20% of the animation
@@ -255,8 +271,11 @@
             out.setFloat(tvsLocal.recentsViewScale,
                     AnimatedFloat.VALUE, tvsLocal.getFullScreenScale(),
                     TOUCH_RESPONSE);
-            out.setFloat(tvsLocal.recentsViewScroll, AnimatedFloat.VALUE, 0,
-                    TOUCH_RESPONSE);
+            if (!enableGridOnlyOverview()) {
+                out.setFloat(tvsLocal.recentsViewScroll, AnimatedFloat.VALUE, 0,
+                        TOUCH_RESPONSE);
+            }
+
             out.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationStart(Animator animation) {
@@ -266,6 +285,18 @@
                         showTransaction.getTransaction().show(targets.apps[i].leash);
                     }
                     applier.scheduleApply(showTransaction);
+
+                    if (enableGridOnlyOverview()) {
+                        taskView.getThumbnailBounds(TEMP_THUMBNAIL_BOUNDS, /*relativeToDragLayer=*/
+                                true);
+                        getTaskDimension(context, container.getDeviceProfile(),
+                                TEMP_TASK_DIMENSION);
+                        TEMP_FULLSCREEN_BOUNDS.set(0, 0, (int) TEMP_TASK_DIMENSION.x,
+                                (int) TEMP_TASK_DIMENSION.y);
+                        Utilities.getPivotsForScalingRectToRect(TEMP_THUMBNAIL_BOUNDS,
+                                TEMP_FULLSCREEN_BOUNDS, TEMP_PIVOT);
+                        tvsLocal.setPivotOverride(TEMP_PIVOT);
+                    }
                 }
             });
             out.addOnFrameCallback(() -> {
@@ -316,7 +347,7 @@
 
         if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulators != null
                 && topMostSimulators.length > 0) {
-            out.addFloat(v, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f));
+            out.addFloat(taskView, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f));
 
             RemoteTargetHandle[] simulatorCopies = topMostSimulators;
             for (RemoteTargetHandle handle : simulatorCopies) {
@@ -335,7 +366,7 @@
             // During animation we apply transformation on the thumbnailView (and not the rootView)
             // to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
             //    Mt K(0)` K(t) Mt`
-            View[] thumbnails = v.getSnapshotViews();
+            View[] thumbnails = taskView.getSnapshotViews();
 
             // In case simulator copies and thumbnail size do no match, ensure we get the lesser.
             // This ensures we do not create arrays with empty elements or attempt to references
@@ -458,7 +489,7 @@
         final RecentsView recentsView = launchingTaskView.getRecentsView();
         composeRecentsLaunchAnimator(animatorSet, launchingTaskView, appTargets, wallpaperTargets,
                 nonAppTargets, /* launcherClosing */ true, stateManager, recentsView,
-                depthController);
+                depthController, /* transitionInfo= */ null);
 
         t.apply();
         animatorSet.start();
@@ -497,7 +528,7 @@
             composeRecentsLaunchAnimator(animatorSet, launchingTaskView,
                     appTargets, wallpaperTargets, nonAppTargets,
                     true, stateManager,
-                    recentsView, depthController);
+                    recentsView, depthController, /* transitionInfo= */ null);
             animatorSet.start();
             return;
         }
@@ -589,7 +620,7 @@
 
         composeRecentsLaunchAnimator(animatorSet, launchingTaskView, apps, wallpaper, nonApps,
                 true /* launcherClosing */, stateManager, launchingTaskView.getRecentsView(),
-                depthController);
+                depthController, transitionInfo);
 
         return animatorSet;
     }
@@ -599,13 +630,13 @@
             @NonNull RemoteAnimationTarget[] wallpaperTargets,
             @NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing,
             @NonNull StateManager stateManager, @NonNull RecentsView recentsView,
-            @Nullable DepthController depthController) {
+            @Nullable DepthController depthController, @Nullable TransitionInfo transitionInfo) {
         boolean skipLauncherChanges = !launcherClosing;
 
         TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets);
         PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
         createRecentsWindowAnimator(recentsView, taskView, skipLauncherChanges, appTargets,
-                wallpaperTargets, nonAppTargets, depthController, pa);
+                wallpaperTargets, nonAppTargets, depthController, transitionInfo, pa);
         if (launcherClosing) {
             // TODO(b/182592057): differentiate between "restore split" vs "launch fullscreen app"
             TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets, true /*shown*/,
@@ -728,7 +759,9 @@
         List<SurfaceControl> auxiliarySurfaces = new ArrayList<>();
         for (RemoteAnimationTarget target : nonApps) {
             final SurfaceControl leash = target.leash;
-            if (target.windowType == TYPE_DOCK_DIVIDER && leash != null && leash.isValid()) {
+            if ((target.windowType == TYPE_DOCK_DIVIDER
+                    || target.windowType == TYPE_SPLIT_SCREEN_DIM_LAYER)
+                    && leash != null && leash.isValid()) {
                 auxiliarySurfaces.add(leash);
             }
         }
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index 210065a..b3d9da3 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -24,12 +24,12 @@
 
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_A;
+import static com.android.wm.shell.Flags.enableShellTopTaskTracking;
 import static com.android.wm.shell.Flags.enableFlexibleSplit;
 import static com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
-import android.content.Context;
 import android.util.ArrayMap;
 import android.util.Log;
 
@@ -37,13 +37,15 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitStageInfo;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
 import com.android.launcher3.util.SplitConfigurationOptions.StageType;
 import com.android.launcher3.util.TraceHelper;
+import com.android.quickstep.dagger.QuickstepBaseAppComponent;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -60,20 +62,20 @@
 import java.util.LinkedList;
 import java.util.List;
 
+import javax.inject.Inject;
+
 /**
  * This class tracked the top-most task and  some 'approximate' task history to allow faster
  * system state estimation during touch interaction
  */
-public class TopTaskTracker extends ISplitScreenListener.Stub
-        implements TaskStackChangeListener, SafeCloseable {
+@LauncherAppSingleton
+public class TopTaskTracker extends ISplitScreenListener.Stub implements TaskStackChangeListener {
     private static final String TAG = "TopTaskTracker";
-    public static MainThreadInitializedObject<TopTaskTracker> INSTANCE =
-            new MainThreadInitializedObject<>(TopTaskTracker::new);
+    public static DaggerSingletonObject<TopTaskTracker> INSTANCE =
+            new DaggerSingletonObject<>(QuickstepBaseAppComponent::getTopTaskTracker);
 
     private static final int HISTORY_SIZE = 5;
 
-    private final Context mContext;
-
     // Only used when Flags.enableShellTopTaskTracking() is disabled
     // Ordered list with first item being the most recent task.
     private final LinkedList<TaskInfo> mOrderedTaskList = new LinkedList<>();
@@ -82,39 +84,33 @@
     private int mPinnedTaskId = INVALID_TASK_ID;
 
     // Only used when Flags.enableShellTopTaskTracking() is enabled
-    // Mapping of display id to running tasks.  Running tasks are ordered from top most to
-    // bottom most.
-    private ArrayMap<Integer, ArrayList<GroupedTaskInfo>> mVisibleTasks = new ArrayMap<>();
+    // Mapping of display id to visible tasks.  Visible tasks are ordered from top most to bottom
+    // most.
+    private ArrayMap<Integer, GroupedTaskInfo> mVisibleTasks = new ArrayMap<>();
 
-    private TopTaskTracker(Context context) {
-        mContext = context;
-
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-            // Just prepopulate a list for the default display tasks so we don't need to add null
-            // checks everywhere
-            mVisibleTasks.put(DEFAULT_DISPLAY, new ArrayList<>());
-        } else {
+    @Inject
+    public TopTaskTracker(DaggerSingletonTracker tracker, SystemUiProxy systemUiProxy) {
+        if (!enableShellTopTaskTracking()) {
             mMainStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_MAIN;
             mSideStagePosition.stageType = SplitConfigurationOptions.STAGE_TYPE_SIDE;
 
             TaskStackChangeListeners.getInstance().registerTaskStackListener(this);
-            SystemUiProxy.INSTANCE.get(context).registerSplitScreenListener(this);
-        }
-    }
-
-    @Override
-    public void close() {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-            return;
+            systemUiProxy.registerSplitScreenListener(this);
         }
 
-        TaskStackChangeListeners.getInstance().unregisterTaskStackListener(this);
-        SystemUiProxy.INSTANCE.get(mContext).unregisterSplitScreenListener(this);
+        tracker.addCloseable(() -> {
+            if (enableShellTopTaskTracking()) {
+                return;
+            }
+
+            TaskStackChangeListeners.getInstance().unregisterTaskStackListener(this);
+            systemUiProxy.unregisterSplitScreenListener(this);
+        });
     }
 
     @Override
     public void onTaskRemoved(int taskId) {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (enableShellTopTaskTracking()) {
             return;
         }
 
@@ -127,7 +123,7 @@
     }
 
     void handleTaskMovedToFront(TaskInfo taskInfo) {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (enableShellTopTaskTracking()) {
             return;
         }
 
@@ -181,32 +177,25 @@
      * Called when the set of visible tasks have changed.
      */
     public void onVisibleTasksChanged(GroupedTaskInfo[] visibleTasks) {
-        if (!com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (!enableShellTopTaskTracking()) {
             return;
         }
 
-        // TODO(346588978): Per-display info, just have everything in order by display
-
         // Clear existing tasks for each display
-        mVisibleTasks.forEach((displayId, visibleTasksOnDisplay) -> visibleTasksOnDisplay.clear());
+        mVisibleTasks.clear();
 
         // Update the visible tasks on each display
-        for (int i = 0; i < visibleTasks.length; i++) {
-            final int displayId = visibleTasks[i].getTaskInfo1().getDisplayId();
-            final ArrayList<GroupedTaskInfo> displayTasks;
-            if (mVisibleTasks.containsKey(displayId)) {
-                displayTasks = mVisibleTasks.get(displayId);
-            } else {
-                displayTasks = new ArrayList<>();
-                mVisibleTasks.put(displayId, displayTasks);
-            }
-            displayTasks.add(visibleTasks[i]);
+        Log.d(TAG, "onVisibleTasksChanged:");
+        for (GroupedTaskInfo groupedTask : visibleTasks) {
+            Log.d(TAG, "\t" + groupedTask);
+            final int displayId = groupedTask.getBaseGroupedTask().getTaskInfo1().getDisplayId();
+            mVisibleTasks.put(displayId, groupedTask);
         }
     }
 
     @Override
     public void onStagePositionChanged(@StageType int stage, @StagePosition int position) {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (enableShellTopTaskTracking()) {
             return;
         }
 
@@ -218,7 +207,7 @@
     }
 
     public void onTaskChanged(RunningTaskInfo taskInfo) {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (enableShellTopTaskTracking()) {
             return;
         }
 
@@ -232,7 +221,7 @@
 
     @Override
     public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (enableShellTopTaskTracking()) {
             return;
         }
 
@@ -256,7 +245,7 @@
 
     @Override
     public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (enableShellTopTaskTracking()) {
             return;
         }
 
@@ -265,7 +254,7 @@
 
     @Override
     public void onActivityUnpinned() {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (enableShellTopTaskTracking()) {
             return;
         }
 
@@ -273,16 +262,17 @@
     }
 
     /**
-     * @return index 0 will be task in left/top position, index 1 in right/bottom position.
-     * Will return empty array if device is not in staged split
+     * Return the running split task ids.  Index 0 will be task in left/top position, index 1 in
+     * right/bottom position, or and empty array if device is not in splitscreen.
      */
     public int[] getRunningSplitTaskIds() {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-            // TODO(346588978): This assumes default display for now
-            final ArrayList<GroupedTaskInfo> visibleTasks = mVisibleTasks.get(DEFAULT_DISPLAY);
-            final GroupedTaskInfo splitTaskInfo = visibleTasks.stream()
-                    .filter(taskInfo -> taskInfo.getType() == TYPE_SPLIT)
-                    .findFirst().orElse(null);
+        if (enableShellTopTaskTracking()) {
+            // TODO(346588978): This assumes default display as splitscreen is only currently there
+            final GroupedTaskInfo visibleTasks = mVisibleTasks.get(DEFAULT_DISPLAY);
+            final GroupedTaskInfo splitTaskInfo =
+                    visibleTasks != null && visibleTasks.isBaseType(TYPE_SPLIT)
+                            ? visibleTasks.getBaseGroupedTask()
+                            : null;
             if (splitTaskInfo != null && splitTaskInfo.getSplitBounds() != null) {
                 return new int[] {
                         splitTaskInfo.getSplitBounds().leftTopTaskId,
@@ -311,24 +301,13 @@
      * Dumps the list of tasks in top task tracker.
      */
     public void dump(PrintWriter pw) {
-        if (!com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (!enableShellTopTaskTracking()) {
             return;
         }
 
-        // TODO(346588978): This assumes default display for now
-        final ArrayList<GroupedTaskInfo> displayTasks = mVisibleTasks.get(DEFAULT_DISPLAY);
         pw.println("TopTaskTracker:");
-        pw.println("  tasks: [");
-        for (GroupedTaskInfo taskInfo : displayTasks) {
-            final TaskInfo info = taskInfo.getTaskInfo1();
-            final boolean isExcluded = (info.baseIntent.getFlags()
-                    & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0;
-            pw.println("    " + info.taskId + ": excluded=" + isExcluded
-                    + " visibleRequested=" + info.isVisibleRequested
-                    + " visible=" + info.isVisible
-                    + " " + info.baseIntent.getComponent());
-        }
-        pw.println("  ]");
+        mVisibleTasks.forEach((displayId, tasks) ->
+                pw.println("  visibleTasks(" + displayId + "): " + tasks));
     }
 
     /**
@@ -337,13 +316,12 @@
     @NonNull
     @UiThread
     public CachedTaskInfo getCachedTopTask(boolean filterOnlyVisibleRecents) {
-        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+        if (enableShellTopTaskTracking()) {
             // TODO(346588978): Currently ignore filterOnlyVisibleRecents, but perhaps make this an
             //  explicit filter For things to ignore (ie. PIP/Bubbles/Assistant/etc/so that this is
             //  explicit)
-            // TODO(346588978): This assumes default display for now (as does all of Launcher)
-            final ArrayList<GroupedTaskInfo> displayTasks = mVisibleTasks.get(DEFAULT_DISPLAY);
-            return new CachedTaskInfo(new ArrayList<>(displayTasks));
+            // TODO(346588978): This assumes default display as gesture nav is only supported there
+            return new CachedTaskInfo(mVisibleTasks.get(DEFAULT_DISPLAY));
         } else {
             if (filterOnlyVisibleRecents) {
                 // Since we only know about the top most task, any filtering may not be applied on
@@ -368,6 +346,11 @@
         }
     }
 
+    private static boolean isHomeTask(TaskInfo task) {
+        return task != null && task.configuration.windowConfiguration
+                .getActivityType() == ACTIVITY_TYPE_HOME;
+    }
+
     private static boolean isRecentsTask(TaskInfo task) {
         return task != null && task.configuration.windowConfiguration
                 .getActivityType() == ACTIVITY_TYPE_RECENTS;
@@ -378,7 +361,6 @@
      * during the lifecycle of the task.
      */
     public static class CachedTaskInfo {
-
         // Only used when enableShellTopTaskTracking() is disabled
         @Nullable
         private final TaskInfo mTopTask;
@@ -387,40 +369,48 @@
 
         // Only used when enableShellTopTaskTracking() is enabled
         @Nullable
-        private final GroupedTaskInfo mTopGroupedTask;
-        @Nullable
-        private final ArrayList<GroupedTaskInfo> mVisibleTasks;
+        private final GroupedTaskInfo mVisibleTasks;
 
 
         // Only used when enableShellTopTaskTracking() is enabled
-        CachedTaskInfo(@NonNull ArrayList<GroupedTaskInfo> visibleTasks) {
+        CachedTaskInfo(@Nullable GroupedTaskInfo visibleTasks) {
             mAllCachedTasks = null;
             mTopTask = null;
             mVisibleTasks = visibleTasks;
-            mTopGroupedTask = !mVisibleTasks.isEmpty() ? mVisibleTasks.getFirst() : null;
 
         }
 
         // Only used when enableShellTopTaskTracking() is disabled
         CachedTaskInfo(@NonNull List<TaskInfo> allCachedTasks) {
             mVisibleTasks = null;
-            mTopGroupedTask = null;
             mAllCachedTasks = allCachedTasks;
             mTopTask = allCachedTasks.isEmpty() ? null : allCachedTasks.get(0);
         }
 
         /**
-         * @return The list of visible tasks
+         * Returns the "base" task that is used the as the representative running task of the set
+         * of tasks initially provided.
+         *
+         * Not for general use, as in other windowing modes (ie. split/desktop) the caller should
+         * not make assumptions about there being a single base task.
+         * TODO(346588978): Try to remove all usage of this if possible
          */
-        public ArrayList<GroupedTaskInfo> getVisibleTasks() {
-            return mVisibleTasks;
+        @Nullable
+        private TaskInfo getLegacyBaseTask() {
+            if (enableShellTopTaskTracking()) {
+                return mVisibleTasks != null
+                        ? mVisibleTasks.getBaseGroupedTask().getTaskInfo1()
+                        : null;
+            } else {
+                return mTopTask;
+            }
         }
 
         /**
-         * @return The top task id
+         * Returns the top task id.
          */
         public int getTaskId() {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+            if (enableShellTopTaskTracking()) {
                 // Callers should use topGroupedTaskContainsTask() instead
                 return INVALID_TASK_ID;
             } else {
@@ -429,29 +419,58 @@
         }
 
         /**
-         * @return Whether the top grouped task contains the given {@param taskId} if
-         *         Flags.enableShellTopTaskTracking() is true, otherwise it checks the top
-         *         task as reported from TaskStackListener.
+         * Returns the top grouped task ids if Flags.enableShellTopTaskTracking() is true, otherwise
+         * an empty array.
+         */
+        public int[] topGroupedTaskIds() {
+            if (enableShellTopTaskTracking()) {
+                if (mVisibleTasks == null) {
+                    return new int[0];
+                }
+                List<TaskInfo> groupedTasks = mVisibleTasks.getTaskInfoList();
+                return groupedTasks.stream().mapToInt(
+                        groupedTask -> groupedTask.taskId).toArray();
+            } else {
+                // Not used
+                return new int[0];
+            }
+        }
+
+        /**
+         * Returns whether the top grouped task contains the given {@param taskId} if
+         * Flags.enableShellTopTaskTracking() is true, otherwise it checks the top task as reported
+         * from TaskStackListener.
          */
         public boolean topGroupedTaskContainsTask(int taskId) {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-                return mTopGroupedTask != null && mTopGroupedTask.containsTask(taskId);
+            if (enableShellTopTaskTracking()) {
+                return mVisibleTasks != null && mVisibleTasks.containsTask(taskId);
             } else {
                 return mTopTask != null && mTopTask.taskId == taskId;
             }
         }
 
         /**
-         * Returns true if the root of the task chooser activity
+         * Returns true if this represents the task chooser activity
          */
         public boolean isRootChooseActivity() {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-                // TODO(346588978): Update this to not make an assumption on a specific task info
-                return mTopGroupedTask != null && ACTION_CHOOSER.equals(
-                        mTopGroupedTask.getTaskInfo1().baseIntent.getAction());
-            } else {
-                return mTopTask != null && ACTION_CHOOSER.equals(mTopTask.baseIntent.getAction());
-            }
+            final TaskInfo baseTask = getLegacyBaseTask();
+            return baseTask != null && ACTION_CHOOSER.equals(baseTask.baseIntent.getAction());
+        }
+
+        /**
+         * Returns true if this represents the HOME activity type task
+         */
+        public boolean isHomeTask() {
+            final TaskInfo baseTask = getLegacyBaseTask();
+            return baseTask != null && TopTaskTracker.isHomeTask(baseTask);
+        }
+
+        /**
+         * Returns true if this represents the RECENTS activity type task
+         */
+        public boolean isRecentsTask() {
+            final TaskInfo baseTask = getLegacyBaseTask();
+            return baseTask != null && TopTaskTracker.isRecentsTask(baseTask);
         }
 
         /**
@@ -459,7 +478,7 @@
          * is another running task that is not excluded from recents, returns that underlying task.
          */
         public @Nullable CachedTaskInfo getVisibleNonExcludedTask() {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+            if (enableShellTopTaskTracking()) {
                 // Callers should not need this when the full set of visible tasks are provided
                 return null;
             }
@@ -479,49 +498,16 @@
         }
 
         /**
-         * Returns true if this represents the HOME activity type task
-         */
-        public boolean isHomeTask() {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-                // TODO(346588978): Update this to not make an assumption on a specific task info
-                return mTopGroupedTask != null
-                        && mTopGroupedTask.getTaskInfo1().getActivityType() == ACTIVITY_TYPE_HOME;
-            } else {
-                return mTopTask != null && mTopTask.configuration.windowConfiguration
-                        .getActivityType() == ACTIVITY_TYPE_HOME;
-            }
-        }
-
-        /**
-         * Returns true if this represents the RECENTS activity type task
-         */
-        public boolean isRecentsTask() {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-                // TODO(346588978): Update this to not make an assumption on a specific task info
-                return mTopGroupedTask != null
-                        && TopTaskTracker.isRecentsTask(mTopGroupedTask.getTaskInfo1());
-            } else {
-                return TopTaskTracker.isRecentsTask(mTopTask);
-            }
-        }
-
-        /**
          * Returns {@link Task} array which can be used as a placeholder until the true object
          * is loaded by the model
          */
         public Task[] getPlaceholderTasks() {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-                // TODO(346588978): Update this to return more than a single task once the callers
-                //  are refactored
-                if (mVisibleTasks.isEmpty()) {
-                    return new Task[0];
-                }
-                final TaskInfo info = mVisibleTasks.getFirst().getTaskInfo1();
-                return new Task[]{Task.from(new TaskKey(info), info, false)};
-            } else {
-                return mTopTask == null ? new Task[0]
-                        : new Task[]{Task.from(new TaskKey(mTopTask), mTopTask, false)};
-            }
+            final TaskInfo baseTask = getLegacyBaseTask();
+            // TODO(346588978): Update this to return more than a single task once the callers
+            //  are refactored
+            return baseTask == null
+                    ? new Task[0]
+                    : new Task[]{Task.from(new TaskKey(baseTask), baseTask, false)};
         }
 
         /**
@@ -529,13 +515,12 @@
          * placeholder until the true object is loaded by the model
          */
         public Task[] getSplitPlaceholderTasks(int[] taskIds) {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-                if (mVisibleTasks.isEmpty()
-                        || mVisibleTasks.getFirst().getType() != TYPE_SPLIT) {
+            if (enableShellTopTaskTracking()) {
+                if (mVisibleTasks == null || !mVisibleTasks.isBaseType(TYPE_SPLIT)) {
                     return new Task[0];
                 }
 
-                GroupedTaskInfo splitTask = mVisibleTasks.getFirst();
+                GroupedTaskInfo splitTask = mVisibleTasks.getBaseGroupedTask();
                 Task[] result = new Task[taskIds.length];
                 for (int i = 0; i < taskIds.length; i++) {
                     TaskInfo info = splitTask.getTaskById(taskIds[i]);
@@ -566,22 +551,11 @@
 
         @Nullable
         public String getPackageName() {
-            if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
-                // TODO(346588978): Update this to not make an assumption on a specific task info
-                if (mTopGroupedTask == null) {
-                    return null;
-                }
-                final TaskInfo info = mTopGroupedTask.getTaskInfo1();
-                if (info.baseActivity == null) {
-                    return null;
-                }
-                return info.baseActivity.getPackageName();
-            } else {
-                if (mTopTask == null || mTopTask.baseActivity == null) {
-                    return null;
-                }
-                return mTopTask.baseActivity.getPackageName();
+            final TaskInfo baseTask = getLegacyBaseTask();
+            if (baseTask == null || baseTask.baseActivity == null) {
+                return null;
             }
+            return baseTask.baseActivity.getPackageName();
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index c8e53ab..741ae7d 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep;
 
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
@@ -40,26 +41,27 @@
 
 import android.app.PendingIntent;
 import android.app.Service;
+import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Region;
-import android.hardware.input.InputManager;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.Looper;
+import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.Trace;
-import android.util.ArraySet;
 import android.util.Log;
 import android.view.Choreographer;
+import android.view.Display;
 import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.MotionEvent;
+import android.window.DesktopModeFlags;
 
 import androidx.annotation.BinderThread;
-import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
@@ -71,7 +73,6 @@
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.desktop.DesktopAppLaunchTransitionManager;
-import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.StatefulActivity;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
@@ -83,14 +84,17 @@
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.LockedUserState;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.NavigationMode;
 import com.android.launcher3.util.PluginManagerWrapper;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.ScreenOnTracker;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.OverviewCommandHelper.CommandType;
-import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.OverviewComponentObserver.OverviewChangeListener;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
+import com.android.quickstep.fallback.window.RecentsDisplayModel.RecentsDisplayResource;
+import com.android.quickstep.fallback.window.RecentsWindowFlags;
 import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler;
 import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
 import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer;
@@ -98,10 +102,12 @@
 import com.android.quickstep.util.ActiveGestureLog;
 import com.android.quickstep.util.ActiveGestureLog.CompoundString;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
+import com.android.quickstep.util.ActiveTrackpadList;
+import com.android.quickstep.util.ActivityPreloadUtil;
 import com.android.quickstep.util.ContextualSearchInvoker;
 import com.android.quickstep.util.ContextualSearchStateManager;
 import com.android.quickstep.views.RecentsViewContainer;
-import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.ILauncherProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 import com.android.systemui.shared.statusbar.phone.BarTransitions;
 import com.android.systemui.shared.system.InputChannelCompat.InputEventReceiver;
@@ -124,7 +130,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
-import java.util.Set;
+import java.util.Locale;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -140,12 +146,15 @@
     private static final ConstantItem<Boolean> HAS_ENABLED_QUICKSTEP_ONCE = backedUpItem(
             "launcher.has_enabled_quickstep_once", false, EncryptionType.ENCRYPTED);
 
+    private static final DesktopModeFlags.DesktopModeFlag ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS =
+            new DesktopModeFlags.DesktopModeFlag(Flags::enableGestureNavOnConnectedDisplays, false);
+
     private final TISBinder mTISBinder = new TISBinder(this);
 
     /**
-     * Local IOverviewProxy implementation with some methods for local components
+     * Local ILauncherProxy implementation with some methods for local components
      */
-    public static class TISBinder extends IOverviewProxy.Stub {
+    public static class TISBinder extends ILauncherProxy.Stub {
 
         private final WeakReference<TouchInteractionService> mTis;
 
@@ -186,9 +195,8 @@
                         recentTasks, launcherUnlockAnimationController, backAnimation, desktopMode,
                         unfoldTransition, dragAndDrop);
                 tis.initInputMonitor("TISBinder#onInitialize()");
-                tis.preloadOverview(true /* fromInit */);
+                ActivityPreloadUtil.preloadOverviewForTIS(tis, true /* fromInit */);
             }));
-            sIsInitialized = true;
         }
 
         @BinderThread
@@ -276,11 +284,12 @@
         }
 
         @BinderThread
-        public void onSystemUiStateChanged(@SystemUiStateFlags long stateFlags) {
+        public void onSystemUiStateChanged(@SystemUiStateFlags long stateFlags, int displayId) {
             MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
-                long lastFlags = tis.mDeviceState.getSystemUiStateFlags();
-                tis.mDeviceState.setSystemUiFlags(stateFlags);
-                tis.onSystemUiFlagsChanged(lastFlags);
+                // Last flags is only used for the default display case.
+                long lastFlags = tis.mDeviceState.getSysuiStateFlag();
+                tis.mDeviceState.setSysUIStateFlagsForDisplay(stateFlags, displayId);
+                tis.onSystemUiFlagsChanged(lastFlags, displayId);
             }));
         }
 
@@ -294,8 +303,9 @@
         @Override
         public void enterStageSplitFromRunningApp(boolean leftOrTop) {
             executeForTouchInteractionService(tis -> {
+                // TODO (b/397942185): support external displays
                 RecentsViewContainer container = tis.mOverviewComponentObserver
-                        .getContainerInterface().getCreatedContainer();
+                        .getContainerInterface(DEFAULT_DISPLAY).getCreatedContainer();
                 if (container != null) {
                     container.enterStageSplitFromRunningApp(leftOrTop);
                 }
@@ -304,64 +314,69 @@
 
         @BinderThread
         @Override
+        public void onDisplayAddSystemDecorations(int displayId) {
+            executeForTaskbarManager(taskbarManager ->
+                            taskbarManager.onDisplayAddSystemDecorations(displayId));
+        }
+
+        @BinderThread
+        @Override
+        public void onDisplayRemoved(int displayId) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.onDisplayRemoved(displayId));
+            executeForTouchInteractionService(tis -> {
+                tis.mDeviceState.clearSysUIStateFlagsForDisplay(displayId);
+            });
+        }
+
+        @BinderThread
+        @Override
+        public void onDisplayRemoveSystemDecorations(int displayId) {
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.onDisplayRemoveSystemDecorations(displayId));
+        }
+
+        @BinderThread
+        @Override
         public void updateWallpaperVisibility(int displayId, boolean visible) {
-            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis ->
-                    executeForTaskbarManager(
-                            taskbarManager -> taskbarManager.setWallpaperVisible(visible))
-            ));
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.setWallpaperVisible(displayId, visible));
         }
 
         @BinderThread
         @Override
         public void checkNavBarModes(int displayId) {
-            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis ->
-                    executeForTaskbarManager(
-                            taskbarManager -> taskbarManager.checkNavBarModes(displayId))));
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.checkNavBarModes(displayId));
         }
 
         @BinderThread
         @Override
         public void finishBarAnimations(int displayId) {
-            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
-                    tis -> executeForTaskbarManager(
-                            taskbarManager -> taskbarManager.finishBarAnimations(displayId))));
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.finishBarAnimations(displayId));
         }
 
         @BinderThread
         @Override
         public void touchAutoDim(int displayId, boolean reset) {
-            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
-                    tis -> executeForTaskbarManager(
-                            taskbarManager -> taskbarManager.touchAutoDim(displayId, reset))));
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.touchAutoDim(displayId, reset));
         }
 
         @BinderThread
         @Override
         public void transitionTo(int displayId, @BarTransitions.TransitionMode int barMode,
                 boolean animate) {
-            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(
-                    tis -> executeForTaskbarManager(
-                            taskbarManager -> taskbarManager.transitionTo(displayId, barMode,
-                                    animate))));
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.transitionTo(displayId, barMode, animate));
         }
 
         @BinderThread
         @Override
         public void appTransitionPending(boolean pending) {
-            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis ->
-                    executeForTaskbarManager(
-                            taskbarManager -> taskbarManager.appTransitionPending(pending))
-            ));
-        }
-
-        /**
-         * Preloads the Overview activity.
-         * <p>
-         * This method should only be used when the All Set page of the SUW is reached to safely
-         * preload the Launcher for the SUW first reveal.
-         */
-        public void preloadOverviewForSUWAllSet() {
-            executeForTouchInteractionService(tis -> tis.preloadOverview(false, true));
+            executeForTaskbarManager(taskbarManager ->
+                    taskbarManager.appTransitionPending(pending));
         }
 
         @Override
@@ -400,6 +415,20 @@
                     taskbarManager.onNavigationBarLumaSamplingEnabled(displayId, enable));
         }
 
+        @Override
+        public void onUnbind(IRemoteCallback reply) {
+            // Run everything in the same main thread block to ensure the cleanup happens before
+            // sending the reply.
+            MAIN_EXECUTOR.execute(() -> {
+                executeForTaskbarManager(TaskbarManager::destroy);
+                try {
+                    reply.sendResult(null);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "onUnbind: Failed to reply to LauncherProxyService", e);
+                }
+            });
+        }
+
         private void executeForTouchInteractionService(
                 @NonNull Consumer<TouchInteractionService> tisConsumer) {
             TouchInteractionService tis = mTis.get();
@@ -428,18 +457,6 @@
             return tis.mTaskbarManager;
         }
 
-        /**
-         * Returns the {@link DesktopVisibilityController}
-         * <p>
-         * Returns {@code null} if TouchInteractionService is not connected
-         */
-        @Nullable
-        public DesktopVisibilityController getDesktopVisibilityController() {
-            TouchInteractionService tis = mTis.get();
-            if (tis == null) return null;
-            return tis.mDesktopVisibilityController;
-        }
-
         @VisibleForTesting
         public void injectFakeTrackpadForTesting() {
             TouchInteractionService tis = mTis.get();
@@ -504,72 +521,8 @@
         }
     }
 
-    private final InputManager.InputDeviceListener mInputDeviceListener =
-            new InputManager.InputDeviceListener() {
-                @Override
-                public void onInputDeviceAdded(int deviceId) {
-                    if (isTrackpadDevice(deviceId)) {
-                        // This updates internal TIS state so it needs to also run on the main
-                        // thread.
-                        MAIN_EXECUTOR.execute(() -> {
-                            boolean wasEmpty = mTrackpadsConnected.isEmpty();
-                            mTrackpadsConnected.add(deviceId);
-                            if (wasEmpty) {
-                                update();
-                            }
-                        });
-                    }
-                }
-
-                @Override
-                public void onInputDeviceChanged(int deviceId) {
-                }
-
-                @Override
-                public void onInputDeviceRemoved(int deviceId) {
-                    // This updates internal TIS state so it needs to also run on the main
-                    // thread.
-                    MAIN_EXECUTOR.execute(() -> {
-                        mTrackpadsConnected.remove(deviceId);
-                        if (mTrackpadsConnected.isEmpty()) {
-                            update();
-                        }
-                    });
-                }
-
-                @MainThread
-                private void update() {
-                    if (mInputMonitorCompat != null && !mTrackpadsConnected.isEmpty()) {
-                        // Don't destroy and reinitialize input monitor due to trackpad
-                        // connecting when it's already set up.
-                        return;
-                    }
-                    initInputMonitor("onTrackpadConnected()");
-                }
-
-                private boolean isTrackpadDevice(int deviceId) {
-                    // This is a blocking binder call that should run on a bg thread.
-                    InputDevice inputDevice = mInputManager.getInputDevice(deviceId);
-                    if (inputDevice == null) {
-                        return false;
-                    }
-                    return inputDevice.getSources() == (InputDevice.SOURCE_MOUSE
-                            | InputDevice.SOURCE_TOUCHPAD);
-                }
-            };
-
-    private static boolean sConnected = false;
-    private static boolean sIsInitialized = false;
     private RotationTouchHelper mRotationTouchHelper;
 
-    public static boolean isConnected() {
-        return sConnected;
-    }
-
-    public static boolean isInitialized() {
-        return sIsInitialized;
-    }
-
     private final AbsSwipeUpHandler.Factory mLauncherSwipeHandlerFactory =
             this::createLauncherSwipeHandler;
     private final AbsSwipeUpHandler.Factory mFallbackSwipeHandlerFactory =
@@ -612,21 +565,23 @@
     private @Nullable ResetGestureInputConsumer mResetGestureInputConsumer;
     private GestureState mGestureState = DEFAULT_STATE;
 
+    private InputMonitorDisplayModel mInputMonitorDisplayModel;
     private InputMonitorCompat mInputMonitorCompat;
     private InputEventReceiver mInputEventReceiver;
 
     private TaskbarManager mTaskbarManager;
-    private RecentsWindowManager mRecentsWindowManager;
     private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = i -> null;
     private AllAppsActionManager mAllAppsActionManager;
-    private InputManager mInputManager;
-    private final Set<Integer> mTrackpadsConnected = new ArraySet<>();
+    private ActiveTrackpadList mTrackpadsConnected;
 
     private NavigationMode mGestureStartNavMode = null;
 
-    private DesktopVisibilityController mDesktopVisibilityController;
     private DesktopAppLaunchTransitionManager mDesktopAppLaunchTransitionManager;
 
+    private DisplayController.DisplayInfoChangeListener mDisplayInfoChangeListener;
+
+    private RecentsDisplayModel mRecentsDisplayModel;
+
     @Override
     public void onCreate() {
         super.onCreate();
@@ -635,39 +590,62 @@
         // Initialize anything here that is needed in direct boot mode.
         // Everything else should be initialized in onUserUnlocked() below.
         mMainChoreographer = Choreographer.getInstance();
-        mDeviceState = new RecentsAnimationDeviceState(this, true);
-        mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
+        mDeviceState = RecentsAnimationDeviceState.INSTANCE.get(this);
+        mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(this);
+        mRecentsDisplayModel = RecentsDisplayModel.getINSTANCE().get(this);
         mAllAppsActionManager = new AllAppsActionManager(
                 this, UI_HELPER_EXECUTOR, this::createAllAppsPendingIntent);
-        mInputManager = getSystemService(InputManager.class);
-        mInputManager.registerInputDeviceListener(mInputDeviceListener,
-                UI_HELPER_EXECUTOR.getHandler());
-        int [] inputDevices = mInputManager.getInputDeviceIds();
-        for (int inputDeviceId : inputDevices) {
-            mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
-        }
-        mDesktopVisibilityController = new DesktopVisibilityController(this);
-        mTaskbarManager = new TaskbarManager(
-                this, mAllAppsActionManager, mNavCallbacks, mDesktopVisibilityController);
+        mTrackpadsConnected = new ActiveTrackpadList(this, () -> {
+            if (mInputMonitorCompat != null && !mTrackpadsConnected.isEmpty()) {
+                // Don't destroy and reinitialize input monitor due to trackpad
+                // connecting when it's already set up.
+                return;
+            }
+            initInputMonitor("onTrackpadConnected()");
+        });
+
+        mTaskbarManager = new TaskbarManager(this, mAllAppsActionManager, mNavCallbacks,
+                mRecentsDisplayModel);
         mDesktopAppLaunchTransitionManager =
                 new DesktopAppLaunchTransitionManager(this, SystemUiProxy.INSTANCE.get(this));
         mDesktopAppLaunchTransitionManager.registerTransitions();
-        if (Flags.enableLauncherOverviewInWindow() || Flags.enableFallbackOverviewInWindow()) {
-            mRecentsWindowManager = new RecentsWindowManager(this);
-        }
         mInputConsumer = InputConsumerController.getRecentsAnimationInputConsumer();
 
         // Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
         LockedUserState.get(this).runOnUserUnlocked(mUserUnlockedRunnable);
-        mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
-        sConnected = true;
-
+        mDisplayInfoChangeListener =
+                mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
         ScreenOnTracker.INSTANCE.get(this).addListener(mScreenOnListener);
     }
 
+    @Nullable
+    private InputEventReceiver getInputEventReceiver(int displayId) {
+        if (ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS.isTrue()) {
+            InputMonitorResource inputMonitorResource = mInputMonitorDisplayModel == null
+                    ? null : mInputMonitorDisplayModel.getDisplayResource(displayId);
+            return inputMonitorResource == null ? null : inputMonitorResource.inputEventReceiver;
+        }
+        return mInputEventReceiver;
+    }
+
+    @Nullable
+    private InputMonitorCompat getInputMonitorCompat(int displayId) {
+        if (ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS.isTrue()) {
+            InputMonitorResource inputMonitorResource = mInputMonitorDisplayModel == null
+                    ? null : mInputMonitorDisplayModel.getDisplayResource(displayId);
+            return inputMonitorResource == null ? null : inputMonitorResource.inputMonitorCompat;
+        }
+        return mInputMonitorCompat;
+    }
+
     private void disposeEventHandlers(String reason) {
         Log.d(TAG, "disposeEventHandlers: Reason: " + reason
                 + " instance=" + System.identityHashCode(this));
+        if (ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS.isTrue()) {
+            if (mInputMonitorDisplayModel == null) return;
+            mInputMonitorDisplayModel.destroy();
+            return;
+        }
         if (mInputEventReceiver != null) {
             mInputEventReceiver.dispose();
             mInputEventReceiver = null;
@@ -686,10 +664,13 @@
                 && (mTrackpadsConnected.isEmpty())) {
             return;
         }
-
-        mInputMonitorCompat = new InputMonitorCompat("swipe-up", mDeviceState.getDisplayId());
-        mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
-                mMainChoreographer, this::onInputEvent);
+        if (ENABLE_GESTURE_NAV_ON_CONNECTED_DISPLAYS.isTrue()) {
+            mInputMonitorDisplayModel = new InputMonitorDisplayModel(this);
+        } else {
+            mInputMonitorCompat = new InputMonitorCompat("swipe-up", Display.DEFAULT_DISPLAY);
+            mInputEventReceiver = mInputMonitorCompat.getInputReceiver(Looper.getMainLooper(),
+                    mMainChoreographer, this::onInputEvent);
+        }
 
         mRotationTouchHelper.updateGestureTouchRegions();
     }
@@ -706,14 +687,17 @@
     public void onUserUnlocked() {
         Log.d(TAG, "onUserUnlocked: userId=" + getUserId()
                 + " instance=" + System.identityHashCode(this));
-        mTaskAnimationManager = new TaskAnimationManager(this, mRecentsWindowManager);
+        mTaskAnimationManager = new TaskAnimationManager(this, mDeviceState);
         mOverviewComponentObserver = OverviewComponentObserver.INSTANCE.get(this);
         mOverviewCommandHelper = new OverviewCommandHelper(this,
-                mOverviewComponentObserver, mTaskAnimationManager);
+                mOverviewComponentObserver, mTaskAnimationManager, mRecentsDisplayModel,
+                SystemUiProxy.INSTANCE.get(this).getFocusState(), mTaskbarManager);
         mResetGestureInputConsumer = new ResetGestureInputConsumer(
                 mTaskAnimationManager, mTaskbarManager::getCurrentActivityContext);
         mInputConsumer.registerInputConsumer();
-        onSystemUiFlagsChanged(mDeviceState.getSystemUiStateFlags());
+        for (int displayId : mDeviceState.getDisplaysWithSysUIState()) {
+            onSystemUiFlagsChanged(mDeviceState.getSystemUiStateFlags(displayId), displayId);
+        }
         onAssistantVisibilityChanged();
 
         // Initialize the task tracker
@@ -751,8 +735,11 @@
 
     private void onOverviewTargetChanged(boolean isHomeAndOverviewSame) {
         mAllAppsActionManager.setHomeAndOverviewSame(isHomeAndOverviewSame);
+        // TODO (b/399089118): how will this work with per-display Taskbars? Is using the
+        //  default-display container ok?
         RecentsViewContainer newOverviewContainer =
-                mOverviewComponentObserver.getContainerInterface().getCreatedContainer();
+                mOverviewComponentObserver.getContainerInterface(
+                        DEFAULT_DISPLAY).getCreatedContainer();
         if (newOverviewContainer != null) {
             if (newOverviewContainer instanceof StatefulActivity activity) {
                 // This will also call setRecentsViewContainer() internally.
@@ -769,26 +756,33 @@
             public void send(int code, Intent intent, String resolvedType,
                     IBinder allowlistToken, IIntentReceiver finishedReceiver,
                     String requiredPermission, Bundle options) {
-                MAIN_EXECUTOR.execute(() -> mTaskbarManager.toggleAllApps());
+                MAIN_EXECUTOR.execute(() -> mTaskbarManager.toggleAllAppsSearch());
             }
         });
     }
 
     @UiThread
-    private void onSystemUiFlagsChanged(@SystemUiStateFlags long lastSysUIFlags) {
+    private void onSystemUiFlagsChanged(@SystemUiStateFlags long lastSysUIFlags, int displayId) {
         if (LockedUserState.get(this).isUserUnlocked()) {
-            long systemUiStateFlags = mDeviceState.getSystemUiStateFlags();
-            SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
-            mOverviewComponentObserver.setHomeDisabled(mDeviceState.isHomeDisabled());
-            mTaskbarManager.onSystemUiFlagsChanged(systemUiStateFlags);
-            mTaskAnimationManager.onSystemUiFlagsChanged(lastSysUIFlags, systemUiStateFlags);
+            long systemUiStateFlags = mDeviceState.getSystemUiStateFlags(displayId);
+            mTaskbarManager.onSystemUiFlagsChanged(systemUiStateFlags, displayId);
+            if (displayId == Display.DEFAULT_DISPLAY) {
+                // The following don't care about non-default displays, at least for now. If they
+                // ever will, they should be taken care of.
+                SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
+                mOverviewComponentObserver.setHomeDisabled(mDeviceState.isHomeDisabled());
+                // TODO b/399371607 - Propagate to taskAnimationManager once overview is multi
+                //  display.
+                mTaskAnimationManager.onSystemUiFlagsChanged(lastSysUIFlags, systemUiStateFlags);
+            }
         }
     }
 
     @UiThread
     private void onAssistantVisibilityChanged() {
         if (LockedUserState.get(this).isUserUnlocked()) {
-            mOverviewComponentObserver.getContainerInterface().onAssistantVisibilityChanged(
+            mOverviewComponentObserver.getContainerInterface(
+                    DEFAULT_DISPLAY).onAssistantVisibilityChanged(
                     mDeviceState.getAssistantVisibility());
         }
     }
@@ -797,33 +791,23 @@
     public void onDestroy() {
         Log.d(TAG, "onDestroy: user=" + getUserId()
                 + " instance=" + System.identityHashCode(this));
-        sIsInitialized = false;
         if (LockedUserState.get(this).isUserUnlocked()) {
             mInputConsumer.unregisterInputConsumer();
             mOverviewComponentObserver.setHomeDisabled(false);
             mOverviewComponentObserver.removeOverviewChangeListener(mOverviewChangeListener);
         }
         disposeEventHandlers("TouchInteractionService onDestroy()");
-        mDeviceState.destroy();
         SystemUiProxy.INSTANCE.get(this).clearProxy();
 
         mAllAppsActionManager.onDestroy();
 
-        mInputManager.unregisterInputDeviceListener(mInputDeviceListener);
-        mTrackpadsConnected.clear();
-
+        mTrackpadsConnected.destroy();
         mTaskbarManager.destroy();
-
-        if (mRecentsWindowManager != null) {
-            mRecentsWindowManager.destroy();
-        }
         if (mDesktopAppLaunchTransitionManager != null) {
             mDesktopAppLaunchTransitionManager.unregisterTransitions();
         }
         mDesktopAppLaunchTransitionManager = null;
-        mDesktopVisibilityController.onDestroy();
-        sConnected = false;
-
+        mDeviceState.removeDisplayInfoChangeListener(mDisplayInfoChangeListener);
         LockedUserState.get(this).removeOnUserUnlockedRunnable(mUserUnlockedRunnable);
         ScreenOnTracker.INSTANCE.get(this).removeListener(mScreenOnListener);
         super.onDestroy();
@@ -848,8 +832,9 @@
     }
 
     private void onInputEvent(InputEvent ev) {
+        int displayId = ev.getDisplayId();
         if (!(ev instanceof MotionEvent)) {
-            ActiveGestureProtoLogProxy.logUnknownInputEvent(ev.toString());
+            ActiveGestureProtoLogProxy.logUnknownInputEvent(displayId, ev.toString());
             return;
         }
         MotionEvent event = (MotionEvent) ev;
@@ -858,19 +843,19 @@
                 TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
 
         if (!LockedUserState.get(this).isUserUnlocked()) {
-            ActiveGestureProtoLogProxy.logOnInputEventUserLocked();
+            ActiveGestureProtoLogProxy.logOnInputEventUserLocked(displayId);
             return;
         }
 
         NavigationMode currentNavMode = mDeviceState.getMode();
         if (mGestureStartNavMode != null && mGestureStartNavMode != currentNavMode) {
             ActiveGestureProtoLogProxy.logOnInputEventNavModeSwitched(
-                    mGestureStartNavMode.name(), currentNavMode.name());
+                    displayId, mGestureStartNavMode.name(), currentNavMode.name());
             event.setAction(ACTION_CANCEL);
         } else if (mDeviceState.isButtonNavMode()
                 && !mDeviceState.supportsAssistantGestureInButtonNav()
                 && !isTrackpadMotionEvent(event)) {
-            ActiveGestureProtoLogProxy.logOnInputEventThreeButtonNav();
+            ActiveGestureProtoLogProxy.logOnInputEventThreeButtonNav(displayId);
             return;
         }
 
@@ -886,12 +871,15 @@
             }
             if (mTaskAnimationManager.shouldIgnoreMotionEvents()) {
                 if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
-                    ActiveGestureProtoLogProxy.logOnInputIgnoringFollowingEvents();
+                    ActiveGestureProtoLogProxy.logOnInputIgnoringFollowingEvents(displayId);
                 }
                 return;
             }
         }
 
+        InputMonitorCompat inputMonitorCompat = getInputMonitorCompat(displayId);
+        InputEventReceiver inputEventReceiver = getInputEventReceiver(displayId);
+
         if (action == ACTION_DOWN || isHoverActionWithoutConsumer) {
             mGestureStartNavMode = currentNavMode;
         } else if (action == ACTION_UP || action == ACTION_CANCEL) {
@@ -918,10 +906,14 @@
                 if (mDeviceState.canTriggerAssistantAction(event)) {
                     reasonString.append(" and event can trigger assistant action, "
                             + "consuming gesture for assistant action");
-                    mGestureState =
-                            createGestureState(mGestureState, getTrackpadGestureType(event));
+                    mGestureState = createGestureState(
+                            displayId, mGestureState, getTrackpadGestureType(event));
                     mUncheckedConsumer = tryCreateAssistantInputConsumer(
-                            this, mDeviceState, mInputMonitorCompat, mGestureState, event);
+                            this,
+                            mDeviceState,
+                            inputMonitorCompat,
+                            mGestureState,
+                            event);
                 } else {
                     reasonString.append(" but event cannot trigger Assistant, "
                             + "consuming gesture as no-op");
@@ -936,12 +928,11 @@
                 // Clone the previous gesture state since onConsumerAboutToBeSwitched might trigger
                 // onConsumerInactive and wipe the previous gesture state
                 GestureState prevGestureState = new GestureState(mGestureState);
-                GestureState newGestureState = createGestureState(mGestureState,
-                        getTrackpadGestureType(event));
+                GestureState newGestureState = createGestureState(
+                        displayId, mGestureState, getTrackpadGestureType(event));
                 mConsumer.onConsumerAboutToBeSwitched();
                 mGestureState = newGestureState;
                 mConsumer = newConsumer(
-                        getBaseContext(),
                         this,
                         mResetGestureInputConsumer,
                         mOverviewComponentObserver,
@@ -949,10 +940,10 @@
                         prevGestureState,
                         mGestureState,
                         mTaskAnimationManager,
-                        mInputMonitorCompat,
+                        inputMonitorCompat,
                         getSwipeUpHandlerFactory(),
                         this::onConsumerInactive,
-                        mInputEventReceiver,
+                        inputEventReceiver,
                         mTaskbarManager,
                         mSwipeUpProxyProvider,
                         mOverviewCommandHelper,
@@ -965,18 +956,19 @@
                                 + "consuming gesture for assistant action"
                         : "event is a trackpad multi-finger swipe and event can trigger assistant "
                                 + "action, consuming gesture for assistant action");
-                mGestureState = createGestureState(mGestureState, getTrackpadGestureType(event));
+                mGestureState = createGestureState(
+                        displayId, mGestureState, getTrackpadGestureType(event));
                 // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we
                 // should not interrupt it. QuickSwitch assumes that interruption can only
                 // happen if the next gesture is also quick switch.
                 mUncheckedConsumer = tryCreateAssistantInputConsumer(
-                        this, mDeviceState, mInputMonitorCompat, mGestureState, event);
+                        this, mDeviceState, inputMonitorCompat, mGestureState, event);
             } else if (mDeviceState.canTriggerOneHandedAction(event)) {
                 reasonString.append("event can trigger one-handed action, "
                         + "consuming gesture for one-handed action");
                 // Consume gesture event for triggering one handed feature.
-                mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState,
-                        InputConsumer.NO_OP, mInputMonitorCompat);
+                mUncheckedConsumer = new OneHandedModeInputConsumer(
+                        this, displayId, mDeviceState, InputConsumer.NO_OP, inputMonitorCompat);
             } else {
                 mUncheckedConsumer = InputConsumer.NO_OP;
             }
@@ -991,25 +983,28 @@
         if (mUncheckedConsumer != InputConsumer.NO_OP) {
             switch (action) {
                 case ACTION_DOWN:
-                    ActiveGestureProtoLogProxy.logOnInputEventActionDown(reasonString);
+                    ActiveGestureProtoLogProxy.logOnInputEventActionDown(displayId, reasonString);
                     // fall through
                 case ACTION_UP:
                     ActiveGestureProtoLogProxy.logOnInputEventActionUp(
                             (int) event.getRawX(),
                             (int) event.getRawY(),
                             action,
-                            MotionEvent.classificationToString(event.getClassification()));
+                            MotionEvent.classificationToString(event.getClassification()),
+                            displayId);
                     break;
                 case ACTION_MOVE:
                     ActiveGestureProtoLogProxy.logOnInputEventActionMove(
                             MotionEvent.actionToString(action),
                             MotionEvent.classificationToString(event.getClassification()),
-                            event.getPointerCount());
+                            event.getPointerCount(),
+                            displayId);
                     break;
                 default: {
                     ActiveGestureProtoLogProxy.logOnInputEventGenericAction(
                             MotionEvent.actionToString(action),
-                            MotionEvent.classificationToString(event.getClassification()));
+                            MotionEvent.classificationToString(event.getClassification()),
+                            displayId);
                 }
             }
         }
@@ -1033,7 +1028,7 @@
         }
 
         if (cleanUpConsumer) {
-            reset();
+            reset(displayId);
         }
         traceToken.close();
     }
@@ -1052,13 +1047,15 @@
         return event.isHoverEvent() && event.getSource() == InputDevice.SOURCE_MOUSE;
     }
 
-    public GestureState createGestureState(GestureState previousGestureState,
+    public GestureState createGestureState(
+            int displayId,
+            GestureState previousGestureState,
             GestureState.TrackpadGestureType trackpadGestureType) {
         final GestureState gestureState;
         TopTaskTracker.CachedTaskInfo taskInfo;
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
-            gestureState = new GestureState(mOverviewComponentObserver,
-                    ActiveGestureLog.INSTANCE.getLogId());
+            gestureState = new GestureState(
+                    mOverviewComponentObserver, displayId, ActiveGestureLog.INSTANCE.getLogId());
             TopTaskTracker.CachedTaskInfo previousTaskInfo = previousGestureState.getRunningTask();
             // previousTaskInfo can be null iff previousGestureState == GestureState.DEFAULT_STATE
             taskInfo = previousTaskInfo != null
@@ -1069,7 +1066,9 @@
             gestureState.updatePreviouslyAppearedTaskIds(
                     previousGestureState.getPreviouslyAppearedTaskIds());
         } else {
-            gestureState = new GestureState(mOverviewComponentObserver,
+            gestureState = new GestureState(
+                    mOverviewComponentObserver,
+                    displayId,
                     ActiveGestureLog.INSTANCE.incrementLogId());
             taskInfo = TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false);
             gestureState.updateRunningTask(taskInfo);
@@ -1083,10 +1082,9 @@
     }
 
     public AbsSwipeUpHandler.Factory getSwipeUpHandlerFactory() {
-        boolean recentsInWindow =
-                Flags.enableFallbackOverviewInWindow() || Flags.enableLauncherOverviewInWindow();
         return mOverviewComponentObserver.isHomeAndOverviewSame()
-                ? mLauncherSwipeHandlerFactory : (recentsInWindow
+                ? mLauncherSwipeHandlerFactory
+                : (RecentsWindowFlags.Companion.getEnableOverviewInWindow()
                 ? mRecentsWindowSwipeHandlerFactory : mFallbackSwipeHandlerFactory);
     }
 
@@ -1097,17 +1095,18 @@
      */
     private void onConsumerInactive(InputConsumer caller) {
         if (mConsumer != null && mConsumer.getActiveConsumerInHierarchy() == caller) {
-            reset();
+            reset(caller.getDisplayId());
         }
     }
 
-    private void reset() {
+    private void reset(int displayId) {
         mConsumer = mUncheckedConsumer = getDefaultInputConsumer();
         mGestureState = DEFAULT_STATE;
         // By default, use batching of the input events, but check receiver before using in the rare
         // case that the monitor was disposed before the swipe settled
-        if (mInputEventReceiver != null) {
-            mInputEventReceiver.setBatchingEnabled(true);
+        InputEventReceiver inputEventReceiver = getInputEventReceiver(displayId);
+        if (inputEventReceiver != null) {
+            inputEventReceiver.setBatchingEnabled(true);
         }
     }
 
@@ -1134,54 +1133,15 @@
         }
     }
 
-    private void preloadOverview(boolean fromInit) {
-        Trace.beginSection("preloadOverview(fromInit=" + fromInit + ")");
-        preloadOverview(fromInit, false);
-        Trace.endSection();
-    }
-
-    private void preloadOverview(boolean fromInit, boolean forSUWAllSet) {
-        if (!LockedUserState.get(this).isUserUnlocked()) {
-            return;
-        }
-
-        if (mDeviceState.isButtonNavMode() && !mOverviewComponentObserver.isHomeAndOverviewSame()) {
-            // Prevent the overview from being started before the real home on first boot.
-            return;
-        }
-
-        if ((RestoreDbTask.isPending(this) && !forSUWAllSet)
-                || !mDeviceState.isUserSetupComplete()) {
-            // Preloading while a restore is pending may cause launcher to start the restore
-            // too early.
-            return;
-        }
-
-        final BaseContainerInterface containerInterface =
-                mOverviewComponentObserver.getContainerInterface();
-        final Intent overviewIntent = new Intent(
-                mOverviewComponentObserver.getOverviewIntentIgnoreSysUiState());
-        if (containerInterface.getCreatedContainer() != null && fromInit) {
-            // The activity has been created before the initialization of overview service. It is
-            // usually happens when booting or launcher is the top activity, so we should already
-            // have the latest state.
-            return;
-        }
-
-        // TODO(b/258022658): Remove temporary logging.
-        Log.i(TAG, "preloadOverview: forSUWAllSet=" + forSUWAllSet
-                + ", isHomeAndOverviewSame=" + mOverviewComponentObserver.isHomeAndOverviewSame());
-        ActiveGestureProtoLogProxy.logPreloadRecentsAnimation();
-        mTaskAnimationManager.preloadRecentsAnimation(overviewIntent);
-    }
-
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         if (!LockedUserState.get(this).isUserUnlocked()) {
             return;
         }
+        // TODO (b/399094853): handle config updates for all connected displays (relevant only for
+        // gestures on external displays)
         final BaseContainerInterface containerInterface =
-                mOverviewComponentObserver.getContainerInterface();
+                mOverviewComponentObserver.getContainerInterface(DEFAULT_DISPLAY);
         final RecentsViewContainer container = containerInterface.getCreatedContainer();
         if (container == null || container.isStarted()) {
             // We only care about the existing background activity.
@@ -1202,7 +1162,7 @@
             return;
         }
 
-        preloadOverview(false /* fromInit */);
+        ActivityPreloadUtil.preloadOverviewForTIS(this, false /* fromInit */);
     }
 
     private static boolean isTablet(Configuration config) {
@@ -1228,25 +1188,35 @@
         pw.println("Input state:");
         pw.println("\tmInputMonitorCompat=" + mInputMonitorCompat);
         pw.println("\tmInputEventReceiver=" + mInputEventReceiver);
+        if (mInputMonitorDisplayModel == null) {
+            pw.println("\tmInputMonitorDisplayModel=null");
+        } else {
+            mInputMonitorDisplayModel.dump("\t", pw);
+        }
         DisplayController.INSTANCE.get(this).dump(pw);
-        pw.println("TouchState:");
-        RecentsViewContainer createdOverviewContainer = mOverviewComponentObserver == null ? null
-                : mOverviewComponentObserver.getContainerInterface().getCreatedContainer();
-        boolean resumed = mOverviewComponentObserver != null
-                && mOverviewComponentObserver.getContainerInterface().isResumed();
-        pw.println("\tcreatedOverviewActivity=" + createdOverviewContainer);
-        pw.println("\tresumed=" + resumed);
+        for (RecentsDisplayResource resource : mRecentsDisplayModel.getActiveDisplayResources()) {
+            int displayId = resource.getDisplayId();
+            pw.println(String.format(Locale.ENGLISH, "TouchState (displayId %d):", displayId));
+            RecentsViewContainer createdOverviewContainer =
+                    mOverviewComponentObserver == null ? null
+                            : mOverviewComponentObserver.getContainerInterface(
+                                    displayId).getCreatedContainer();
+            boolean resumed = mOverviewComponentObserver != null
+                    && mOverviewComponentObserver.getContainerInterface(displayId).isResumed();
+            pw.println("\tcreatedOverviewActivity=" + createdOverviewContainer);
+            pw.println("\tresumed=" + resumed);
+            if (createdOverviewContainer != null) {
+                createdOverviewContainer.getDeviceProfile().dump(this, "", pw);
+            }
+        }
         pw.println("\tmConsumer=" + mConsumer.getName());
         ActiveGestureLog.INSTANCE.dump("", pw);
         RecentsModel.INSTANCE.get(this).dump("", pw);
         if (mTaskAnimationManager != null) {
             mTaskAnimationManager.dump("", pw);
         }
-        if (createdOverviewContainer != null) {
-            createdOverviewContainer.getDeviceProfile().dump(this, "", pw);
-        }
         mTaskbarManager.dumpLogs("", pw);
-        mDesktopVisibilityController.dumpLogs("", pw);
+        DesktopVisibilityController.INSTANCE.get(this).dumpLogs("", pw);
         pw.println("ContextualSearchStateManager:");
         ContextualSearchStateManager.INSTANCE.get(this).dump("\t", pw);
         SystemUiProxy.INSTANCE.get(this).dump(pw);
@@ -1256,22 +1226,71 @@
 
     private AbsSwipeUpHandler createLauncherSwipeHandler(
             GestureState gestureState, long touchTimeMs) {
-        return new LauncherSwipeHandlerV2(this, mDeviceState, mTaskAnimationManager,
+        return new LauncherSwipeHandlerV2(this, mTaskAnimationManager,
                 gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
-                mInputConsumer);
+                mInputConsumer, MSDLPlayerWrapper.INSTANCE.get(this));
     }
 
     private AbsSwipeUpHandler createFallbackSwipeHandler(
             GestureState gestureState, long touchTimeMs) {
-        return new FallbackSwipeHandler(this, mDeviceState, mTaskAnimationManager,
+        return new FallbackSwipeHandler(this, mTaskAnimationManager,
                 gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
-                mInputConsumer);
+                mInputConsumer, MSDLPlayerWrapper.INSTANCE.get(this));
     }
 
     private AbsSwipeUpHandler createRecentsWindowSwipeHandler(
             GestureState gestureState, long touchTimeMs) {
-        return new RecentsWindowSwipeHandler(this, mDeviceState, mTaskAnimationManager,
+        return new RecentsWindowSwipeHandler(this, mTaskAnimationManager,
                 gestureState, touchTimeMs, mTaskAnimationManager.isRecentsAnimationRunning(),
-                mInputConsumer, mRecentsWindowManager);
+                mInputConsumer, MSDLPlayerWrapper.INSTANCE.get(this));
+    }
+
+    /**
+     * Helper class that keeps track of external displays and prepares input monitors for each.
+     */
+    private class InputMonitorDisplayModel extends DisplayModel<InputMonitorResource> {
+
+        private InputMonitorDisplayModel(Context context) {
+            super(context);
+            registerDisplayListener();
+        }
+
+        @NonNull
+        @Override
+        public InputMonitorResource createDisplayResource(@NonNull Display display) {
+            return new InputMonitorResource(display.getDisplayId());
+        }
+    }
+
+    private class InputMonitorResource extends DisplayModel.DisplayResource {
+
+        private final int displayId;
+
+        private final InputMonitorCompat inputMonitorCompat;
+        private final InputEventReceiver inputEventReceiver;
+
+        private InputMonitorResource(int displayId) {
+            this.displayId = displayId;
+            inputMonitorCompat = new InputMonitorCompat("swipe-up", displayId);
+            inputEventReceiver = inputMonitorCompat.getInputReceiver(
+                    Looper.getMainLooper(),
+                    TouchInteractionService.this.mMainChoreographer,
+                    TouchInteractionService.this::onInputEvent);
+        }
+
+        @Override
+        public void cleanup() {
+            inputEventReceiver.dispose();
+            inputMonitorCompat.dispose();
+        }
+
+        @Override
+        public void dump(String prefix , PrintWriter writer) {
+            writer.println(prefix + "InputMonitorResource:");
+
+            writer.println(prefix + "\tdisplayId=" + displayId);
+            writer.println(prefix + "\tinputMonitorCompat=" + inputMonitorCompat);
+            writer.println(prefix + "\tinputEventReceiver=" + inputEventReceiver);
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/contextualeducation/SystemContextualEduStatsManager.java b/quickstep/src/com/android/quickstep/contextualeducation/SystemContextualEduStatsManager.java
deleted file mode 100644
index 6a72537..0000000
--- a/quickstep/src/com/android/quickstep/contextualeducation/SystemContextualEduStatsManager.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 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.contextualeducation;
-
-import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
-import com.android.launcher3.dagger.LauncherAppSingleton;
-import com.android.quickstep.SystemUiProxy;
-import com.android.systemui.contextualeducation.GestureType;
-
-import javax.inject.Inject;
-
-/**
- * A class to update contextual education data via {@link SystemUiProxy}
- */
-@LauncherAppSingleton
-public class SystemContextualEduStatsManager extends ContextualEduStatsManager {
-    private final SystemUiProxy mSystemUiProxy;
-
-    @Inject
-    public SystemContextualEduStatsManager(SystemUiProxy systemUiProxy) {
-        mSystemUiProxy = systemUiProxy;
-    }
-
-    @Override
-    public void updateEduStats(boolean isTrackpadGesture, GestureType gestureType) {
-        mSystemUiProxy.updateContextualEduStats(isTrackpadGesture,
-                gestureType.name());
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java b/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java
deleted file mode 100644
index 9f6360b..0000000
--- a/quickstep/src/com/android/quickstep/dagger/QuickStepModule.java
+++ /dev/null
@@ -1,35 +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.quickstep.dagger;
-
-import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
-import com.android.launcher3.uioverrides.SystemApiWrapper;
-import com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl;
-import com.android.launcher3.util.ApiWrapper;
-import com.android.launcher3.util.PluginManagerWrapper;
-import com.android.quickstep.contextualeducation.SystemContextualEduStatsManager;
-
-import dagger.Binds;
-import dagger.Module;
-
-@Module
-public abstract class QuickStepModule {
-
-    @Binds abstract PluginManagerWrapper bindPluginManagerWrapper(PluginManagerWrapperImpl impl);
-    @Binds abstract ApiWrapper bindApiWrapper(SystemApiWrapper systemApiWrapper);
-    @Binds abstract ContextualEduStatsManager bindContextualEduStatsManager(
-            SystemContextualEduStatsManager manager);
-}
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
index aae9ebe..d79a8ea 100644
--- a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
+++ b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
@@ -19,9 +19,19 @@
 import com.android.launcher3.dagger.LauncherAppComponent;
 import com.android.launcher3.dagger.LauncherBaseAppComponent;
 import com.android.launcher3.model.WellbeingModel;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.quickstep.OverviewComponentObserver;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RecentsModel;
+import com.android.quickstep.RotationTouchHelper;
+import com.android.quickstep.SimpleOrientationTouchTransformer;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.TopTaskTracker;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
+import com.android.quickstep.logging.SettingsChangeLogger;
 import com.android.quickstep.util.AsyncClockEventDelegate;
+import com.android.quickstep.util.ContextualSearchHapticManager;
+import com.android.quickstep.util.ContextualSearchStateManager;
 
 /**
  * Launcher Quickstep base component for Dagger injection.
@@ -39,5 +49,26 @@
 
     SystemUiProxy getSystemUiProxy();
 
+    RecentsDisplayModel getRecentsDisplayModel();
+
     OverviewComponentObserver getOverviewComponentObserver();
+
+    DesktopVisibilityController getDesktopVisibilityController();
+
+    TopTaskTracker getTopTaskTracker();
+
+    RotationTouchHelper getRotationTouchHelper();
+
+    ContextualSearchHapticManager getContextualSearchHapticManager();
+
+    ContextualSearchStateManager getContextualSearchStateManager();
+
+    RecentsAnimationDeviceState getRecentsAnimationDeviceState();
+
+    RecentsModel getRecentsModel();
+
+    SettingsChangeLogger getSettingsChangeLogger();
+
+    SimpleOrientationTouchTransformer getSimpleOrientationTouchTransformer();
+
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
index daac9fb..554cea2 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsStateController.java
@@ -18,6 +18,7 @@
 import static com.android.app.animation.Interpolators.FINAL_FRAME;
 import static com.android.app.animation.Interpolators.INSTANT;
 import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.Flags.enableDesktopExplodedView;
 import static com.android.launcher3.Flags.enableLargeDesktopWindowingTile;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
@@ -36,6 +37,7 @@
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_SPLIT_TRANSLATION;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA;
+import static com.android.quickstep.views.RecentsViewUtils.DESK_EXPLODE_PROGRESS;
 import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
 
 import android.util.FloatProperty;
@@ -49,6 +51,7 @@
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
+import com.android.quickstep.views.AddDesktopButton;
 import com.android.quickstep.views.ClearAllButton;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.RecentsViewContainer;
@@ -95,8 +98,13 @@
     private void setProperties(RecentsState state, StateAnimationConfig config,
             PropertySetter setter) {
         float clearAllButtonAlpha = state.hasClearAllButton() ? 1 : 0;
-        setter.setFloat(mRecentsView.getClearAllButton(), ClearAllButton.VISIBILITY_ALPHA,
-                clearAllButtonAlpha, LINEAR);
+        setter.setFloat(mRecentsView.getClearAllButton(),
+                ClearAllButton.VISIBILITY_ALPHA, clearAllButtonAlpha, LINEAR);
+        if (mRecentsView.getAddDeskButton() != null) {
+            float addDeskButtonAlpha = state.hasAddDeskButton() ? 1 : 0;
+            setter.setFloat(mRecentsView.getAddDeskButton(), AddDesktopButton.VISIBILITY_ALPHA,
+                    addDeskButtonAlpha, LINEAR);
+        }
         float overviewButtonAlpha = state.hasOverviewActions() ? 1 : 0;
         setter.setFloat(mRecentsViewContainer.getActionsView().getVisibilityAlpha(),
                 AnimatedFloat.VALUE, overviewButtonAlpha, LINEAR);
@@ -123,20 +131,24 @@
                     state.detachDesktopCarousel() ? 1f : 0f,
                     getOverviewInterpolator(state));
         }
+        if (enableDesktopExplodedView()) {
+            setter.setFloat(mRecentsView, DESK_EXPLODE_PROGRESS,
+                    state.showExplodedDesktopView() ? 1f : 0f,
+                    getOverviewInterpolator(state));
+        }
 
         setter.setViewBackgroundColor(mRecentsViewContainer.getScrimView(),
                 state.getScrimColor(mRecentsViewContainer.asContext()),
                 config.getInterpolator(ANIM_SCRIM_FADE, LINEAR));
         if (isSplitSelectionState(state)) {
-            int duration =
-                    state.getTransitionDuration(mRecentsViewContainer.asContext(), true);
+            int duration = state.getTransitionDuration(mRecentsViewContainer, true);
             // TODO (b/246851887): Pass in setter as a NO_ANIM PendingAnimation instead
             PendingAnimation pa = new PendingAnimation(duration);
             mRecentsView.createSplitSelectInitAnimation(pa, duration);
             setter.add(pa.buildAnim());
         }
 
-        Pair<FloatProperty<RecentsView>, FloatProperty<RecentsView>> taskViewsFloat =
+        Pair<FloatProperty<RecentsView<?, ?>>, FloatProperty<RecentsView<?, ?>>> taskViewsFloat =
                 mRecentsView.getPagedOrientationHandler().getSplitSelectTaskOffset(
                         TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
                         mRecentsViewContainer.getDeviceProfile());
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index daad6b7..8ec97ed 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -30,12 +30,11 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.Flags;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.desktop.DesktopRecentsTransitionController;
 import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.statemanager.StatefulContainer;
@@ -43,15 +42,17 @@
 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
 import com.android.quickstep.BaseContainerInterface;
 import com.android.quickstep.FallbackActivityInterface;
-import com.android.quickstep.FallbackWindowInterface;
 import com.android.quickstep.GestureState;
-import com.android.quickstep.RotationTouchHelper;
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
+import com.android.quickstep.fallback.window.RecentsWindowFlags;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SingleTask;
 import com.android.quickstep.util.SplitSelectStateController;
-import com.android.quickstep.util.TaskViewSimulator;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.RecentsViewContainer;
+import com.android.quickstep.views.TaskContainer;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
 
@@ -73,13 +74,15 @@
     }
 
     public FallbackRecentsView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr, getContainerInterface());
+        super(context, attrs, defStyleAttr);
         mContainer.getStateManager().addStateListener(this);
     }
 
-    private static BaseContainerInterface<RecentsState, ?> getContainerInterface() {
-        return (Flags.enableFallbackOverviewInWindow() || Flags.enableLauncherOverviewInWindow())
-                ? FallbackWindowInterface.getInstance()
+    @Override
+    public BaseContainerInterface<RecentsState, ?> getContainerInterface(int displayId) {
+        return RecentsWindowFlags.Companion.getEnableOverviewInWindow()
+                ? RecentsDisplayModel.getINSTANCE().get(mContext)
+                        .getFallbackWindowInterface(displayId)
                 : FallbackActivityInterface.INSTANCE;
     }
 
@@ -112,12 +115,11 @@
      * to the home task. This allows us to handle quick-switch similarly to a quick-switching
      * from a foreground task.
      */
-    public void onGestureAnimationStartOnHome(Task[] homeTask,
-            RotationTouchHelper rotationTouchHelper) {
+    public void onGestureAnimationStartOnHome(Task[] homeTask) {
         // TODO(b/195607777) General fallback love, but this might be correct
         //  Home task should be defined as the front-most task info I think?
         mHomeTask = homeTask.length > 0 ? homeTask[0] : null;
-        onGestureAnimationStart(homeTask, rotationTouchHelper);
+        onGestureAnimationStart(homeTask);
     }
 
     /**
@@ -128,14 +130,15 @@
     @Override
     public void onPrepareGestureEndAnimation(
             @Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
-            TaskViewSimulator[] taskViewSimulators) {
-        super.onPrepareGestureEndAnimation(animatorSet, endTarget, taskViewSimulators);
+            RemoteTargetHandle[] remoteTargetHandles) {
+        super.onPrepareGestureEndAnimation(animatorSet, endTarget, remoteTargetHandles);
         if (mHomeTask != null && endTarget == RECENTS && animatorSet != null) {
             TaskView tv = getTaskViewByTaskId(mHomeTask.key.id);
             if (tv != null) {
                 PendingAnimation pa = new PendingAnimation(TASK_DISMISS_DURATION);
                 createTaskDismissAnimation(pa, tv, true, false,
-                        TASK_DISMISS_DURATION, false /* dismissingForSplitSelection*/);
+                        TASK_DISMISS_DURATION, false /* dismissingForSplitSelection*/,
+                        false /* isExpressiveDismiss */);
                 pa.addEndListener(e -> setCurrentTask(-1));
                 AnimatorPlaybackController controller = pa.createPlaybackController();
                 controller.dispatchOnStart();
@@ -180,7 +183,7 @@
         Task runningTask = runningTasks[0];
         if (mHomeTask != null && runningTask != null
                 && mHomeTask.key.id == runningTask.key.id
-                && getTaskViewCount() == 0 && mLoadPlanEverApplied) {
+                && !hasTaskViews() && mLoadPlanEverApplied) {
             // Do not add a stub task if we are running over home with empty recents, so that we
             // show the empty recents message instead of showing a stub task and later removing it.
             // Ignore empty task signal if applyLoadPlan has never run.
@@ -210,7 +213,7 @@
             if (!found) {
                 ArrayList<GroupTask> newList = new ArrayList<>(taskGroups.size() + 1);
                 newList.addAll(taskGroups);
-                newList.add(new GroupTask(mHomeTask, null, null));
+                newList.add(new SingleTask(mHomeTask));
                 taskGroups = newList;
             }
         }
@@ -239,10 +242,10 @@
     }
 
     @Override
-    public void initiateSplitSelect(TaskView taskView,
+    public void initiateSplitSelect(TaskContainer taskContainer,
             @SplitConfigurationOptions.StagePosition int stagePosition,
             StatsLogManager.EventEnum splitEvent) {
-        super.initiateSplitSelect(taskView, stagePosition, splitEvent);
+        super.initiateSplitSelect(taskContainer, stagePosition, splitEvent);
         mContainer.getStateManager().goToState(OVERVIEW_SPLIT_SELECT);
     }
 
@@ -263,13 +266,11 @@
         }
 
         setFreezeViewVisibility(true);
-        if (mContainer.getDesktopVisibilityController() != null) {
-            mContainer.getDesktopVisibilityController().onLauncherStateChanged(toState);
-        }
     }
 
     @Override
     public void onStateTransitionComplete(RecentsState finalState) {
+        DesktopVisibilityController.INSTANCE.get(mContainer).onLauncherStateChanged(finalState);
         if (!finalState.isRecentsViewVisible()) {
             // Clean-up logic that occurs when recents is no longer in use/visible.
             reset();
@@ -286,16 +287,11 @@
         }
 
         if (finalState != OVERVIEW_SPLIT_SELECT) {
-            if (FeatureFlags.enableSplitContextually()) {
-                mSplitSelectStateController.resetState();
-            } else {
-                resetFromSplitSelectionState();
-            }
+            mSplitSelectStateController.resetState();
         }
 
         // disabling this so app icons aren't drawn on top of recent tasks.
-        if (isOverlayEnabled && !(Flags.enableFallbackOverviewInWindow()
-                || Flags.enableLauncherOverviewInWindow())) {
+        if (isOverlayEnabled && !RecentsWindowFlags.Companion.getEnableOverviewInWindow()) {
             runActionOnRemoteHandles(remoteTargetHandle ->
                     remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true));
         }
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java b/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
index a2884b6..5d4f1db 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsDragLayer.java
@@ -15,9 +15,16 @@
  */
 package com.android.quickstep.fallback;
 
+import static com.android.launcher3.Flags.enableExpressiveDismissTaskMotion;
+
 import android.content.Context;
 import android.util.AttributeSet;
 
+import com.android.launcher3.statemanager.StatefulContainer;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewDismissTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewLaunchTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewRecentsTouchContext;
+import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchControllerDeprecated;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.quickstep.views.RecentsViewContainer;
@@ -25,16 +32,41 @@
 /**
  * Drag layer for fallback recents activity
  */
-public class RecentsDragLayer<T extends Context & RecentsViewContainer> extends BaseDragLayer<T> {
+public class RecentsDragLayer<T extends Context & RecentsViewContainer
+        & StatefulContainer<RecentsState>> extends BaseDragLayer<T> {
+
+    private final TaskViewRecentsTouchContext mTaskViewRecentsTouchContext =
+            new TaskViewRecentsTouchContext() {
+                @Override
+                public boolean isRecentsInteractive() {
+                    return mContainer.getRootView().hasWindowFocus()
+                            || mContainer.getStateManager().getState().hasLiveTile();
+                }
+
+                @Override
+                public boolean isRecentsModal() {
+                    return false;
+                }
+            };
+
     public RecentsDragLayer(Context context, AttributeSet attrs) {
         super(context, attrs, 1 /* alphaChannelCount */);
     }
 
     @Override
     public void recreateControllers() {
-        mControllers = new TouchController[] {
-                new RecentsTaskController(mContainer),
-                new FallbackNavBarTouchController(mContainer),
-        };
+        mControllers = enableExpressiveDismissTaskMotion()
+                ? new TouchController[]{
+                        new TaskViewLaunchTouchController<>(mContainer,
+                                mTaskViewRecentsTouchContext),
+                        new TaskViewDismissTouchController<>(mContainer,
+                                mTaskViewRecentsTouchContext),
+                        new FallbackNavBarTouchController(mContainer)
+                }
+                : new TouchController[]{
+                        new TaskViewTouchControllerDeprecated<>(mContainer,
+                                mTaskViewRecentsTouchContext),
+                        new FallbackNavBarTouchController(mContainer)
+                };
     }
 }
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index c2e7536..2c1a4eb 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -15,6 +15,7 @@
  */
 package com.android.quickstep.fallback;
 
+import static com.android.launcher3.Flags.enableDesktopExplodedView;
 import static com.android.launcher3.Flags.enableDesktopWindowingCarouselDetach;
 import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
 import static com.android.launcher3.uioverrides.states.BackgroundAppState.getOverviewScaleAndOffsetForBackgroundState;
@@ -27,6 +28,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.views.RecentsViewContainer;
 
 /**
@@ -44,15 +46,19 @@
     private static final int FLAG_RECENTS_VIEW_VISIBLE = BaseState.getFlag(7);
     private static final int FLAG_TASK_THUMBNAIL_SPLASH = BaseState.getFlag(8);
     private static final int FLAG_DETACH_DESKTOP_CAROUSEL = BaseState.getFlag(9);
+    private static final int FLAG_ADD_DESK_BUTTON = BaseState.getFlag(10);
+    private static final int FLAG_SHOW_EXPLODED_DESKTOP_VIEW = BaseState.getFlag(11);
 
     private static final RecentsState[] sAllStates = new RecentsState[6];
 
     public static final RecentsState DEFAULT = new RecentsState(0,
             FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID
-                    | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE);
+                    | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE
+                    | FLAG_ADD_DESK_BUTTON | FLAG_SHOW_EXPLODED_DESKTOP_VIEW);
     public static final RecentsState MODAL_TASK = new ModalState(1,
-            FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
-                    | FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE);
+            FLAG_DISABLE_RESTORE | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
+                    | FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_RECENTS_VIEW_VISIBLE
+                    | FLAG_SHOW_EXPLODED_DESKTOP_VIEW);
     public static final RecentsState BACKGROUND_APP = new BackgroundAppState(2,
             FLAG_DISABLE_RESTORE | FLAG_NON_INTERACTIVE | FLAG_FULL_SCREEN
                     | FLAG_RECENTS_VIEW_VISIBLE | FLAG_TASK_THUMBNAIL_SPLASH
@@ -61,7 +67,7 @@
     public static final RecentsState BG_LAUNCHER = new LauncherState(4, 0);
     public static final RecentsState OVERVIEW_SPLIT_SELECT = new RecentsState(5,
             FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_RECENTS_VIEW_VISIBLE | FLAG_CLOSE_POPUPS
-                    | FLAG_DISABLE_RESTORE);
+                    | FLAG_DISABLE_RESTORE | FLAG_SHOW_EXPLODED_DESKTOP_VIEW);
 
     /** Returns the corresponding RecentsState from ordinal provided */
     public static RecentsState stateFromOrdinal(int ordinal) {
@@ -92,7 +98,7 @@
     }
 
     @Override
-    public int getTransitionDuration(Context context, boolean isToState) {
+    public int getTransitionDuration(ActivityContext context, boolean isToState) {
         return 250;
     }
 
@@ -121,6 +127,13 @@
     }
 
     /**
+     * For this state, whether add desk button should be shown.
+     */
+    public boolean hasAddDeskButton() {
+        return hasFlag(FLAG_ADD_DESK_BUTTON);
+    }
+
+    /**
      * For this state, whether overview actions should be shown.
      */
     public boolean hasOverviewActions() {
@@ -164,6 +177,11 @@
         return hasFlag(FLAG_DETACH_DESKTOP_CAROUSEL) && enableDesktopWindowingCarouselDetach();
     }
 
+    @Override
+    public boolean showExplodedDesktopView() {
+        return hasFlag(FLAG_SHOW_EXPLODED_DESKTOP_VIEW) && enableDesktopExplodedView();
+    }
+
     /**
      * True if the state has overview panel visible.
      */
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java b/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
deleted file mode 100644
index 07da379..0000000
--- a/quickstep/src/com/android/quickstep/fallback/RecentsTaskController.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.quickstep.fallback;
-
-import android.content.Context;
-
-import com.android.launcher3.statemanager.StatefulContainer;
-import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
-import com.android.quickstep.views.RecentsViewContainer;
-
-public class RecentsTaskController<T extends Context & RecentsViewContainer &
-        StatefulContainer<RecentsState>> extends TaskViewTouchController<T> {
-    public RecentsTaskController(T container) {
-        super(container);
-    }
-
-    @Override
-    protected boolean isRecentsInteractive() {
-        return mContainer.getRootView().hasWindowFocus()
-                || mContainer.getStateManager().getState().hasLiveTile();
-    }
-
-    @Override
-    protected boolean isRecentsModal() {
-        return false;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
new file mode 100644
index 0000000..12dc177
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsDisplayModel.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.fallback.window
+
+import android.content.Context
+import android.view.Display
+import androidx.core.util.valueIterator
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.DaggerSingletonObject
+import com.android.launcher3.util.DaggerSingletonTracker
+import com.android.launcher3.util.WallpaperColorHints
+import com.android.quickstep.DisplayModel
+import com.android.quickstep.FallbackWindowInterface
+import com.android.quickstep.dagger.QuickstepBaseAppComponent
+import com.android.quickstep.fallback.window.RecentsDisplayModel.RecentsDisplayResource
+import com.android.quickstep.fallback.window.RecentsWindowFlags.Companion.enableOverviewInWindow
+import java.io.PrintWriter
+import javax.inject.Inject
+
+@LauncherAppSingleton
+class RecentsDisplayModel
+@Inject
+constructor(
+    @ApplicationContext context: Context,
+    private val wallpaperColorHints: WallpaperColorHints,
+    tracker: DaggerSingletonTracker,
+) : DisplayModel<RecentsDisplayResource>(context) {
+
+    companion object {
+        private const val TAG = "RecentsDisplayModel"
+        private const val DEBUG = false
+
+        @JvmStatic
+        val INSTANCE: DaggerSingletonObject<RecentsDisplayModel> =
+            DaggerSingletonObject<RecentsDisplayModel>(
+                QuickstepBaseAppComponent::getRecentsDisplayModel
+            )
+    }
+
+    init {
+        if (enableOverviewInWindow) {
+            registerDisplayListener()
+            tracker.addCloseable { destroy() }
+        }
+    }
+
+    override fun createDisplayResource(display: Display): RecentsDisplayResource {
+        return RecentsDisplayResource(
+            display.displayId,
+            context.createDisplayContext(display),
+            wallpaperColorHints.hints,
+        )
+    }
+
+    fun getRecentsWindowManager(displayId: Int): RecentsWindowManager? {
+        return getDisplayResource(displayId)?.recentsWindowManager
+    }
+
+    fun getFallbackWindowInterface(displayId: Int): FallbackWindowInterface? {
+        return getDisplayResource(displayId)?.fallbackWindowInterface
+    }
+
+    val activeDisplayResources: Iterable<RecentsDisplayResource>
+        get() =
+            object : Iterable<RecentsDisplayResource> {
+                override fun iterator() = displayResourceArray.valueIterator()
+            }
+
+    data class RecentsDisplayResource(
+        val displayId: Int,
+        val displayContext: Context,
+        val wallpaperColorHints: Int,
+    ) : DisplayResource() {
+        val recentsWindowManager = RecentsWindowManager(displayContext, wallpaperColorHints)
+        val fallbackWindowInterface: FallbackWindowInterface =
+            FallbackWindowInterface(recentsWindowManager)
+
+        override fun cleanup() {
+            recentsWindowManager.destroy()
+        }
+
+        override fun dump(prefix: String, writer: PrintWriter) {
+            writer.println("${prefix}RecentsDisplayResource:")
+
+            writer.println("${prefix}\tdisplayId=${displayId}")
+            writer.println("${prefix}\tdisplayContext=${displayContext}")
+            writer.println("${prefix}\twallpaperColorHints=${wallpaperColorHints}")
+            writer.println("${prefix}\trecentsWindowManager=${recentsWindowManager}")
+            writer.println("${prefix}\tfallbackWindowInterface=${fallbackWindowInterface}")
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
index 52a7682..d70d7eb 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
@@ -18,62 +18,63 @@
 
 import android.content.Context
 import android.graphics.PixelFormat
-import android.view.ContextThemeWrapper
+import android.view.Display
 import android.view.ViewGroup
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
 import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS
 import com.android.launcher3.DeviceProfile
 import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.util.BaseContext
 import com.android.launcher3.util.Themes
-import com.android.launcher3.views.ActivityContext
-import com.android.launcher3.views.BaseDragLayer
-import com.android.quickstep.fallback.RecentsDragLayer
 
 /**
  * Window context for the Overview overlays.
+ *
  * <p>
  * Overlays have their own window and need a window context.
  */
-open class RecentsWindowContext(windowContext: Context) :
-    ContextThemeWrapper(windowContext, Themes.getActivityThemeRes(windowContext)), ActivityContext {
+abstract class RecentsWindowContext(windowContext: Context, wallpaperColorHints: Int) :
+    BaseContext(
+        base = windowContext,
+        themeResId = Themes.getActivityThemeRes(windowContext, wallpaperColorHints),
+        destroyOnDetach = false,
+    ) {
 
     private var deviceProfile: DeviceProfile? = null
-    private var dragLayer: RecentsDragLayer<RecentsWindowManager> = RecentsDragLayer(this, null)
-    private val deviceProfileChangeListeners:
-        MutableList<DeviceProfile.OnDeviceProfileChangeListener> =
-        ArrayList()
 
     private val windowTitle: String = "RecentsWindow"
 
     protected var windowLayoutParams: WindowManager.LayoutParams? =
         createDefaultWindowLayoutParams(
-            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, windowTitle)
+            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+            windowTitle,
+        )
 
-    override fun getDragLayer(): BaseDragLayer<RecentsWindowManager> {
-        return dragLayer
+    fun initDeviceProfile() {
+        deviceProfile =
+            if (displayId == Display.DEFAULT_DISPLAY)
+                InvariantDeviceProfile.INSTANCE[this].getDeviceProfile(this)
+            else InvariantDeviceProfile.INSTANCE[this].createDeviceProfileForSecondaryDisplay(this)
     }
 
     override fun getDeviceProfile(): DeviceProfile {
         if (deviceProfile == null) {
-            deviceProfile = InvariantDeviceProfile.INSTANCE[this].getDeviceProfile(this)
-                .copy(this)
+            initDeviceProfile()
         }
         return deviceProfile!!
     }
 
-    override fun getOnDeviceProfileChangeListeners():
-        List<DeviceProfile.OnDeviceProfileChangeListener> {
-        return deviceProfileChangeListeners
-    }
-
     /**
      * Creates LayoutParams for adding a view directly to WindowManager as a new window.
      *
      * @param type The window type to pass to the created WindowManager.LayoutParams.
      * @param title The window title to pass to the created WindowManager.LayoutParams.
      */
-    fun createDefaultWindowLayoutParams(type: Int, title: String): WindowManager.LayoutParams {
+    private fun createDefaultWindowLayoutParams(
+        type: Int,
+        title: String,
+    ): WindowManager.LayoutParams {
         var windowFlags =
             (WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
                 WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS or
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowFlags.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowFlags.kt
new file mode 100644
index 0000000..9953154
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowFlags.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.fallback.window
+
+import android.window.DesktopModeFlags.DesktopModeFlag
+import com.android.launcher3.Flags
+
+class RecentsWindowFlags {
+    companion object {
+        @JvmField
+        val enableLauncherOverviewInWindow: DesktopModeFlag =
+            DesktopModeFlag(Flags::enableLauncherOverviewInWindow, false)
+
+        @JvmField
+        val enableFallbackOverviewInWindow: DesktopModeFlag =
+            DesktopModeFlag(Flags::enableFallbackOverviewInWindow, false)
+
+        @JvmStatic
+        val enableOverviewInWindow
+            get() = enableLauncherOverviewInWindow.isTrue || enableFallbackOverviewInWindow.isTrue
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
index da0c517..1a3a2e3 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowManager.kt
@@ -21,6 +21,7 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.LocusId
+import android.content.res.Configuration
 import android.os.Bundle
 import android.view.KeyEvent
 import android.view.LayoutInflater
@@ -32,24 +33,28 @@
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
 import android.window.RemoteTransition
+import com.android.launcher3.AbstractFloatingView
 import com.android.launcher3.BaseActivity
 import com.android.launcher3.LauncherAnimationRunner
 import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory
 import com.android.launcher3.R
 import com.android.launcher3.compat.AccessibilityManagerCompat
-import com.android.launcher3.statehandlers.DesktopVisibilityController
 import com.android.launcher3.statemanager.StateManager
 import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory
 import com.android.launcher3.statemanager.StatefulContainer
 import com.android.launcher3.taskbar.TaskbarUIController
+import com.android.launcher3.testing.TestLogging
+import com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL
+import com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_SPLIT_SELECT_ORDINAL
 import com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_STATE_ORDINAL
+import com.android.launcher3.testing.shared.TestProtocol.SEQUENCE_MAIN
 import com.android.launcher3.util.ContextTracker
 import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.Executors
 import com.android.launcher3.util.RunnableList
 import com.android.launcher3.util.SystemUiController
 import com.android.launcher3.views.BaseDragLayer
 import com.android.launcher3.views.ScrimView
-import com.android.quickstep.FallbackWindowInterface
 import com.android.quickstep.OverviewComponentObserver
 import com.android.quickstep.RecentsAnimationCallbacks
 import com.android.quickstep.RecentsAnimationCallbacks.RecentsAnimationListener
@@ -77,7 +82,6 @@
 import com.android.systemui.shared.recents.model.ThumbnailData
 import com.android.systemui.shared.system.TaskStackChangeListener
 import com.android.systemui.shared.system.TaskStackChangeListeners
-import java.util.function.Predicate
 
 /**
  * Class that will manage RecentsView lifecycle within a window and interface correctly where
@@ -88,8 +92,10 @@
  * To add new protologs, see [RecentsWindowProtoLogProxy]. To enable logging to logcat, see
  * [QuickstepProtoLogGroup.Constants.DEBUG_RECENTS_WINDOW]
  */
-class RecentsWindowManager(context: Context) :
-    RecentsWindowContext(context), RecentsViewContainer, StatefulContainer<RecentsState> {
+class RecentsWindowManager(context: Context, wallpaperColorHints: Int) :
+    RecentsWindowContext(context, wallpaperColorHints),
+    RecentsViewContainer,
+    StatefulContainer<RecentsState> {
 
     companion object {
         private const val HOME_APPEAR_DURATION: Long = 250
@@ -97,6 +103,9 @@
 
         class RecentsWindowTracker : ContextTracker<RecentsWindowManager?>() {
             override fun isHomeStarted(context: RecentsWindowManager?): Boolean {
+                // if we need to change this block to use context in some way, we will need to
+                // refactor RecentsWindowTracker to be an instance (instead of a singleton) managed
+                // by RecentsDisplayModel. Otherwise bad things will occur.
                 return true
             }
         }
@@ -111,7 +120,7 @@
     private var layoutInflater: LayoutInflater = LayoutInflater.from(this).cloneInContext(this)
     private var stateManager: StateManager<RecentsState, RecentsWindowManager> =
         StateManager<RecentsState, RecentsWindowManager>(this, RecentsState.BG_LAUNCHER)
-    private var mSystemUiController: SystemUiController? = null
+    private var systemUiController: SystemUiController? = null
 
     private var dragLayer: RecentsDragLayer<RecentsWindowManager>? = null
     private var windowView: View? = null
@@ -124,79 +133,10 @@
     private var tisBindHelper: TISBindHelper = TISBindHelper(this) {}
 
     // Callback array that corresponds to events defined in @ActivityEvent
-    private val mEventCallbacks =
+    private val eventCallbacks =
         listOf(RunnableList(), RunnableList(), RunnableList(), RunnableList())
-    private var onInitListener: Predicate<Boolean>? = null
 
-    private val taskStackChangeListener =
-        object : TaskStackChangeListener {
-            override fun onTaskMovedToFront(taskId: Int) {
-                if ((isShowing() && isInState(DEFAULT))) {
-                    // handling state where we end recents animation by swiping livetile away
-                    // TODO: animate this switch.
-                    cleanupRecentsWindow()
-                }
-            }
-        }
-
-    private val recentsAnimationListener =
-        object : RecentsAnimationListener {
-            override fun onRecentsAnimationCanceled(thumbnailDatas: HashMap<Int, ThumbnailData>) {
-                recentAnimationStopped()
-            }
-
-            override fun onRecentsAnimationFinished(controller: RecentsAnimationController) {
-                recentAnimationStopped()
-            }
-        }
-
-    init {
-        FallbackWindowInterface.init(this)
-        TaskStackChangeListeners.getInstance().registerTaskStackListener(taskStackChangeListener)
-    }
-
-    override fun destroy() {
-        super.destroy()
-        cleanupRecentsWindow()
-        FallbackWindowInterface.getInstance()?.destroy()
-        TaskStackChangeListeners.getInstance().unregisterTaskStackListener(taskStackChangeListener)
-        callbacks?.removeListener(recentsAnimationListener)
-        recentsWindowTracker.onContextDestroyed(this)
-    }
-
-    override fun startHome() {
-        startHome(/* finishRecentsAnimation= */ true)
-    }
-
-    fun startHome(finishRecentsAnimation: Boolean) {
-        val recentsView: RecentsView<*, *> = getOverviewPanel()
-
-        if (!finishRecentsAnimation) {
-            recentsView.switchToScreenshot(/* onFinishRunnable= */ null)
-            startHomeInternal()
-            return
-        }
-        recentsView.switchToScreenshot {
-            recentsView.finishRecentsAnimation(/* toRecents= */ true) { startHomeInternal() }
-        }
-    }
-
-    private fun startHomeInternal() {
-        val runner = LauncherAnimationRunner(mainThreadHandler, mAnimationToHomeFactory, true)
-        val options =
-            ActivityOptions.makeRemoteAnimation(
-                RemoteAnimationAdapter(runner, HOME_APPEAR_DURATION, 0),
-                RemoteTransition(
-                    runner.toRemoteTransition(),
-                    iApplicationThread,
-                    "StartHomeFromRecents",
-                ),
-            )
-        OverviewComponentObserver.startHomeIntentSafely(this, options.toBundle(), TAG)
-        stateManager.moveToRestState()
-    }
-
-    private val mAnimationToHomeFactory =
+    private val animationToHomeFactory =
         RemoteAnimationFactory {
             _: Int,
             appTargets: Array<RemoteAnimationTarget>?,
@@ -223,7 +163,7 @@
                 anim,
                 this@RecentsWindowManager,
                 {
-                    getStateManager().goToState(BG_LAUNCHER, false)
+                    getStateManager().goToState(BG_LAUNCHER, true)
                     cleanupRecentsWindow()
                 },
                 true, /* skipFirstFrame */
@@ -233,19 +173,53 @@
     private val onBackInvokedCallback: () -> Unit = {
         // If we are in live tile mode, launch the live task, otherwise return home
         recentsView?.runningTaskView?.launchWithAnimation() ?: startHome()
+        TestLogging.recordEvent(SEQUENCE_MAIN, "onBackInvoked")
     }
 
-    private fun cleanupRecentsWindow() {
-        RecentsWindowProtoLogProxy.logCleanup(isShowing())
-        if (isShowing()) {
-            windowManager.removeViewImmediate(windowView)
+    private val taskStackChangeListener =
+        object : TaskStackChangeListener {
+            override fun onTaskMovedToFront(taskId: Int) {
+                if ((isShowing() && isInState(DEFAULT))) {
+                    // handling state where we end recents animation by swiping livetile away
+                    // TODO: animate this switch.
+                    cleanupRecentsWindow()
+                }
+            }
         }
-        stateManager.moveToRestState()
-        callbacks?.removeListener(recentsAnimationListener)
+
+    private val recentsAnimationListener =
+        object : RecentsAnimationListener {
+            override fun onRecentsAnimationCanceled(thumbnailDatas: HashMap<Int, ThumbnailData>) {
+                recentAnimationStopped()
+            }
+
+            override fun onRecentsAnimationFinished(controller: RecentsAnimationController) {
+                recentAnimationStopped()
+            }
+        }
+
+    init {
+        TaskStackChangeListeners.getInstance().registerTaskStackListener(taskStackChangeListener)
     }
 
-    private fun isShowing(): Boolean {
-        return windowView?.parent != null
+    override fun handleConfigurationChanged(configuration: Configuration?) {
+        initDeviceProfile()
+        AbstractFloatingView.closeOpenViews(
+            this,
+            true,
+            AbstractFloatingView.TYPE_ALL and AbstractFloatingView.TYPE_REBIND_SAFE.inv(),
+        )
+        dispatchDeviceProfileChanged()
+    }
+
+    override fun destroy() {
+        super.destroy()
+        Executors.MAIN_EXECUTOR.execute { onViewDestroyed() }
+        cleanupRecentsWindow()
+        TaskStackChangeListeners.getInstance().unregisterTaskStackListener(taskStackChangeListener)
+        callbacks?.removeListener(recentsAnimationListener)
+        recentsWindowTracker.onContextDestroyed(this)
+        recentsView?.destroy()
     }
 
     fun startRecentsWindow(callbacks: RecentsAnimationCallbacks? = null) {
@@ -287,11 +261,57 @@
         actionsView?.updateDimension(getDeviceProfile(), recentsView?.lastComputedTaskSize)
         actionsView?.updateVerticalMargin(DisplayController.getNavigationMode(this))
 
-        mSystemUiController = SystemUiController(windowView)
+        systemUiController = SystemUiController(windowView)
         recentsWindowTracker.handleCreate(this)
 
         this.callbacks = callbacks
         callbacks?.addListener(recentsAnimationListener)
+        onViewCreated()
+    }
+
+    override fun startHome() {
+        startHome(/* finishRecentsAnimation= */ true)
+    }
+
+    fun startHome(finishRecentsAnimation: Boolean) {
+        val recentsView: RecentsView<*, *> = getOverviewPanel()
+
+        if (!finishRecentsAnimation) {
+            recentsView.switchToScreenshot /* onFinishRunnable= */ {}
+            startHomeInternal()
+            return
+        }
+        recentsView.switchToScreenshot {
+            recentsView.finishRecentsAnimation(/* toRecents= */ true) { startHomeInternal() }
+        }
+    }
+
+    private fun startHomeInternal() {
+        val runner = LauncherAnimationRunner(mainThreadHandler, animationToHomeFactory, true)
+        val options =
+            ActivityOptions.makeRemoteAnimation(
+                RemoteAnimationAdapter(runner, HOME_APPEAR_DURATION, 0),
+                RemoteTransition(
+                    runner.toRemoteTransition(),
+                    iApplicationThread,
+                    "StartHomeFromRecents",
+                ),
+            )
+        OverviewComponentObserver.startHomeIntentSafely(this, options.toBundle(), TAG)
+        stateManager.moveToRestState()
+    }
+
+    private fun cleanupRecentsWindow() {
+        RecentsWindowProtoLogProxy.logCleanup(isShowing())
+        if (isShowing()) {
+            windowManager.removeViewImmediate(windowView)
+        }
+        stateManager.moveToRestState()
+        callbacks?.removeListener(recentsAnimationListener)
+    }
+
+    private fun isShowing(): Boolean {
+        return windowView?.parent != null
     }
 
     private fun recentAnimationStopped() {
@@ -309,10 +329,6 @@
         return overviewCommandHelper == null || overviewCommandHelper.canStartHomeSafely()
     }
 
-    override fun getDesktopVisibilityController(): DesktopVisibilityController? {
-        return tisBindHelper.desktopVisibilityController
-    }
-
     override fun setTaskbarUIController(taskbarUIController: TaskbarUIController?) {
         this.taskbarUIController = taskbarUIController
     }
@@ -321,10 +337,6 @@
         return taskbarUIController
     }
 
-    fun registerInitListener(onInitListener: Predicate<Boolean>) {
-        this.onInitListener = onInitListener
-    }
-
     override fun collectStateHandlers(out: MutableList<StateManager.StateHandler<RecentsState?>>?) {
         out!!.add(FallbackRecentsStateController(this))
     }
@@ -349,19 +361,27 @@
     override fun onStateSetEnd(state: RecentsState) {
         super.onStateSetEnd(state)
         RecentsWindowProtoLogProxy.logOnStateSetEnd(getStateName(state))
-
         if (state == HOME || state == BG_LAUNCHER) {
             cleanupRecentsWindow()
         }
-        if (state === DEFAULT) {
-            AccessibilityManagerCompat.sendStateEventToTest(baseContext, OVERVIEW_STATE_ORDINAL)
+        when (state) {
+            HOME,
+            BG_LAUNCHER ->
+                AccessibilityManagerCompat.sendStateEventToTest(baseContext, NORMAL_STATE_ORDINAL)
+            DEFAULT ->
+                AccessibilityManagerCompat.sendStateEventToTest(baseContext, OVERVIEW_STATE_ORDINAL)
+            OVERVIEW_SPLIT_SELECT ->
+                AccessibilityManagerCompat.sendStateEventToTest(
+                    baseContext,
+                    OVERVIEW_SPLIT_SELECT_ORDINAL,
+                )
         }
     }
 
     private fun getStateName(state: RecentsState?): String {
         return when (state) {
             null -> "NULL"
-            DEFAULT -> "default"
+            DEFAULT -> "DEFAULT"
             MODAL_TASK -> "MODAL_TASK"
             BACKGROUND_APP -> "BACKGROUND_APP"
             HOME -> "HOME"
@@ -372,14 +392,10 @@
     }
 
     override fun getSystemUiController(): SystemUiController? {
-        if (mSystemUiController == null) {
-            mSystemUiController = SystemUiController(rootView)
+        if (systemUiController == null) {
+            systemUiController = SystemUiController(rootView)
         }
-        return mSystemUiController
-    }
-
-    override fun getContext(): Context {
-        return this
+        return systemUiController
     }
 
     override fun getScrimView(): ScrimView? {
@@ -426,12 +442,12 @@
 
     /** Adds a callback for the provided activity event */
     override fun addEventCallback(@BaseActivity.ActivityEvent event: Int, callback: Runnable?) {
-        mEventCallbacks[event].add(callback)
+        eventCallbacks[event].add(callback)
     }
 
     /** Removes a previously added callback */
     override fun removeEventCallback(@BaseActivity.ActivityEvent event: Int, callback: Runnable?) {
-        mEventCallbacks[event].remove(callback)
+        eventCallbacks[event].remove(callback)
     }
 
     override fun runOnBindToTouchInteractionService(r: Runnable?) {
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
index be71385..1d85feb 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowSwipeHandler.java
@@ -49,6 +49,7 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.animation.Interpolator;
+import android.window.TransitionInfo;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -62,10 +63,10 @@
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.quickstep.AbsSwipeUpHandler;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.RecentsAnimationController;
-import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.fallback.FallbackRecentsView;
@@ -100,6 +101,8 @@
     private static StaticMessageReceiver sMessageReceiver = null;
 
     private FallbackHomeAnimationFactory mActiveAnimationFactory;
+    private final RecentsDisplayModel mRecentsDisplayModel;
+
     private final boolean mRunningOverHome;
 
     private final Matrix mTmpMatrix = new Matrix();
@@ -107,13 +110,13 @@
 
     private boolean mAppCanEnterPip;
 
-    public RecentsWindowSwipeHandler(Context context, RecentsAnimationDeviceState deviceState,
-            TaskAnimationManager taskAnimationManager, GestureState gestureState, long touchTimeMs,
-            boolean continuingLastGesture, InputConsumerController inputConsumer,
-            RecentsWindowManager recentsWindowManager) {
-        super(context, deviceState, taskAnimationManager, gestureState, touchTimeMs,
-                continuingLastGesture, inputConsumer, recentsWindowManager);
+    public RecentsWindowSwipeHandler(Context context, TaskAnimationManager taskAnimationManager,
+            GestureState gestureState, long touchTimeMs, boolean continuingLastGesture,
+            InputConsumerController inputConsumer, MSDLPlayerWrapper msdlPlayerWrapper) {
+        super(context, taskAnimationManager, gestureState, touchTimeMs,
+                continuingLastGesture, inputConsumer, msdlPlayerWrapper);
 
+        mRecentsDisplayModel = RecentsDisplayModel.getINSTANCE().get(context);
         mRunningOverHome = mGestureState.getRunningTask() != null
                 && mGestureState.getRunningTask().isHomeTask();
 
@@ -122,8 +125,8 @@
 
     @Override
     public void onRecentsAnimationStart(RecentsAnimationController controller,
-            RecentsAnimationTargets targets) {
-        super.onRecentsAnimationStart(controller, targets);
+            RecentsAnimationTargets targets, @Nullable TransitionInfo transitionInfo) {
+        super.onRecentsAnimationStart(controller, targets, transitionInfo);
         initTransformParams();
     }
 
@@ -159,7 +162,11 @@
         boolean fromHomeToHome = mRunningOverHome
                 && endTarget == GestureState.GestureEndTarget.HOME;
         if (fromHomeToHome) {
-            mRecentsWindowManager.startHome(/* finishRecentsAnimation= */ false);
+            RecentsWindowManager manager =
+                    mRecentsDisplayModel.getRecentsWindowManager(mGestureState.getDisplayId());
+            if (manager != null) {
+                manager.startHome(/* finishRecentsAnimation= */ false);
+            }
         }
         super.animateGestureEnd(
                 startShift,
@@ -220,7 +227,11 @@
             // the PiP task appearing.
             recentsCallback = () -> {
                 callback.run();
-                mRecentsWindowManager.startHome();
+                RecentsWindowManager manager =
+                        mRecentsDisplayModel.getRecentsWindowManager(mGestureState.getDisplayId());
+                if (manager != null) {
+                    manager.startHome();
+                }
             };
         } else {
             recentsCallback = callback;
@@ -245,8 +256,7 @@
         if (mRunningOverHome) {
             if (DisplayController.getNavigationMode(mContext).hasGestures) {
                 mRecentsView.onGestureAnimationStartOnHome(
-                        mGestureState.getRunningTask().getPlaceholderTasks(),
-                        mDeviceState.getRotationTouchHelper());
+                        mGestureState.getRunningTask().getPlaceholderTasks());
             }
         } else {
             super.notifyGestureAnimationStartToRecents();
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
index ec6efcb..365c80c 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/AccessibilityInputConsumer.java
@@ -29,9 +29,9 @@
 import android.view.ViewConfiguration;
 
 import com.android.launcher3.R;
-import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RotationTouchHelper;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.systemui.shared.system.InputMonitorCompat;
@@ -47,7 +47,7 @@
     private final VelocityTracker mVelocityTracker;
     private final MotionPauseDetector mMotionPauseDetector;
     private final RecentsAnimationDeviceState mDeviceState;
-    private final GestureState mGestureState;
+    private final RotationTouchHelper mRotationHelper;
 
     private final float mMinGestureDistance;
     private final float mMinFlingVelocity;
@@ -56,16 +56,20 @@
     private float mDownY;
     private float mTotalY;
 
-    public AccessibilityInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
-            GestureState gestureState, InputConsumer delegate, InputMonitorCompat inputMonitor) {
-        super(delegate, inputMonitor);
+    public AccessibilityInputConsumer(
+            Context context,
+            int displayId,
+            RecentsAnimationDeviceState deviceState,
+            InputConsumer delegate,
+            InputMonitorCompat inputMonitor) {
+        super(displayId, delegate, inputMonitor);
         mContext = context;
         mVelocityTracker = VelocityTracker.obtain();
         mMinGestureDistance = context.getResources()
                 .getDimension(R.dimen.accessibility_gesture_min_swipe_distance);
         mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
         mDeviceState = deviceState;
-        mGestureState = gestureState;
+        mRotationHelper = RotationTouchHelper.INSTANCE.get(context);
 
         mMotionPauseDetector = new MotionPauseDetector(context);
     }
@@ -102,8 +106,8 @@
             case ACTION_POINTER_DOWN: {
                 if (mState == STATE_INACTIVE) {
                     int pointerIndex = ev.getActionIndex();
-                    if (mDeviceState.getRotationTouchHelper().isInSwipeUpTouchRegion(ev,
-                            pointerIndex) && mDelegate.allowInterceptByParent()) {
+                    if (mRotationHelper.isInSwipeUpTouchRegion(ev, pointerIndex)
+                            && mDelegate.allowInterceptByParent()) {
                         setActive(ev);
 
                         mActivePointerId = ev.getPointerId(pointerIndex);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
index 222ccd3..365014d 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
@@ -95,7 +95,7 @@
             InputMonitorCompat inputMonitor,
             RecentsAnimationDeviceState deviceState,
             MotionEvent startEvent) {
-        super(delegate, inputMonitor);
+        super(gestureState.getDisplayId(), delegate, inputMonitor);
         final Resources res = context.getResources();
         mContext = context;
         mDragDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
index 6b61298..86d7190 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/BubbleBarInputConsumer.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.graphics.PointF;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
@@ -38,8 +39,11 @@
 /**
  * Listens for touch events on the bubble bar.
  */
+// TODO(b/385928447): remove debug logs with Log.d
 public class BubbleBarInputConsumer implements InputConsumer {
 
+    private static final String TAG = "BubbleBarInputConsumer";
+
     private final BubbleStashController mBubbleStashController;
     private final BubbleBarViewController mBubbleBarViewController;
     @Nullable
@@ -53,18 +57,26 @@
     private final int mTouchSlop;
     private final PointF mDownPos = new PointF();
     private final PointF mLastPos = new PointF();
-    private final long mTimeForTap;
+
+    private final int mDisplayId;
+
+    private long mDownTime;
+    private final long mTimeForLongPress;
     private int mActivePointerId = INVALID_POINTER_ID;
 
-    public BubbleBarInputConsumer(Context context, BubbleControllers bubbleControllers,
+    public BubbleBarInputConsumer(
+            Context context,
+            int displayId,
+            BubbleControllers bubbleControllers,
             InputMonitorCompat inputMonitorCompat) {
+        mDisplayId = displayId;
         mBubbleStashController = bubbleControllers.bubbleStashController;
         mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
         mBubbleBarSwipeController = bubbleControllers.bubbleBarSwipeController.orElse(null);
 
         mInputMonitorCompat = inputMonitorCompat;
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
-        mTimeForTap = ViewConfiguration.getTapTimeout();
+        mTimeForLongPress = ViewConfiguration.getLongPressTimeout();
     }
 
     @Override
@@ -73,14 +85,23 @@
     }
 
     @Override
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent ev) {
         final int action = ev.getAction();
         switch (action) {
             case MotionEvent.ACTION_DOWN:
+                mDownTime = System.currentTimeMillis();
                 mActivePointerId = ev.getPointerId(0);
                 mDownPos.set(ev.getX(), ev.getY());
                 mLastPos.set(mDownPos);
                 mStashedOrCollapsedOnDown = mBubbleStashController.isStashed() || isCollapsed();
+                Log.d(TAG,
+                        "ACTION_DOWN stashedOrCollapsed=" + mStashedOrCollapsedOnDown + " downPos="
+                                + mDownPos);
                 if (mBubbleBarSwipeController != null) {
                     mBubbleBarSwipeController.start();
                 }
@@ -88,6 +109,7 @@
             case MotionEvent.ACTION_MOVE:
                 int pointerIndex = ev.findPointerIndex(mActivePointerId);
                 if (pointerIndex == INVALID_POINTER_ID) {
+                    Log.d(TAG, "ACTION_MOVE skip, invalid pointer id");
                     break;
                 }
                 mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
@@ -96,10 +118,14 @@
                 float dY = mLastPos.y - mDownPos.y;
                 if (!mPassedTouchSlop) {
                     mPassedTouchSlop = Math.abs(dY) > mTouchSlop || Math.abs(dX) > mTouchSlop;
+                    if (mPassedTouchSlop) {
+                        Log.d(TAG, "ACTION_MOVE passed touch slop pos=" + mLastPos);
+                    }
                 }
                 if (mBubbleBarSwipeController != null) {
                     mBubbleBarSwipeController.swipeTo(dY);
                     if (!mPilfered && mBubbleBarSwipeController.isSwipeGesture()) {
+                        Log.d(TAG, "ACTION_MOVE swipe gesture, pilfering");
                         mPilfered = true;
                         // Bubbles is handling the swipe so make sure no one else gets it.
                         TestLogging.recordEvent(TestProtocol.SEQUENCE_PILFER, "pilferPointers");
@@ -108,16 +134,28 @@
                 }
                 break;
             case MotionEvent.ACTION_UP:
+                long tapTime = System.currentTimeMillis() - mDownTime;
                 boolean swipeUpOnBubbleHandle = mBubbleBarSwipeController != null
                         && mBubbleBarSwipeController.isSwipeGesture();
-                boolean isWithinTapTime = ev.getEventTime() - ev.getDownTime() <= mTimeForTap;
+                // Anything less than a long-press is a tap
+                boolean isWithinTapTime = tapTime <= mTimeForLongPress;
+                Log.d(TAG, "ACTION_UP swipeUp=" + swipeUpOnBubbleHandle + " isInTapTime="
+                        + isWithinTapTime + " tapTime=" + tapTime + " passedTouchSlop="
+                        + mPassedTouchSlop + " stashedOrCollapsedOnDown="
+                        + mStashedOrCollapsedOnDown);
                 if (isWithinTapTime && !swipeUpOnBubbleHandle && !mPassedTouchSlop
                         && mStashedOrCollapsedOnDown) {
+                    Log.d(TAG, "ACTION_UP showing bubble bar");
                     // Taps on the handle / collapsed state should open the bar
                     mBubbleStashController.showBubbleBar(
                             /* expandBubbles= */ true, /* bubbleBarGesture= */ true);
+                } else {
+                    Log.d(TAG, "ACTION_UP nothing to do");
                 }
                 break;
+            case MotionEvent.ACTION_CANCEL:
+                Log.d(TAG, "ACTION_CANCEL");
+                break;
         }
         if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
             cleanupAfterMotionEvent();
@@ -125,11 +163,13 @@
     }
 
     private void cleanupAfterMotionEvent() {
+        Log.d(TAG, "cleaning up passedSlop=" + mPassedTouchSlop + " pilfered=" + mPilfered);
         if (mBubbleBarSwipeController != null) {
             mBubbleBarSwipeController.finish();
         }
         mPassedTouchSlop = false;
         mPilfered = false;
+        mDownTime = 0;
     }
 
     private boolean isCollapsed() {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 4afd92a..0b1a6c4 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -17,15 +17,24 @@
     protected final InputConsumer mDelegate;
     protected final InputMonitorCompat mInputMonitor;
 
+    private final int mDisplayId;
+
     protected int mState;
 
-    public DelegateInputConsumer(InputConsumer delegate, InputMonitorCompat inputMonitor) {
+    public DelegateInputConsumer(
+            int displayId, InputConsumer delegate, InputMonitorCompat inputMonitor) {
+        mDisplayId = displayId;
         mDelegate = delegate;
         mInputMonitor = inputMonitor;
         mState = STATE_INACTIVE;
     }
 
     @Override
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    @Override
     public InputConsumer getActiveConsumerInHierarchy() {
         if (mState == STATE_ACTIVE) {
             return this;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
index b66d4cb..e192702 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/DeviceLockedInputConsumer.java
@@ -37,6 +37,7 @@
 import android.view.MotionEvent;
 import android.view.RemoteAnimationTarget;
 import android.view.VelocityTracker;
+import android.window.TransitionInfo;
 
 import com.android.app.animation.Interpolators;
 import com.android.launcher3.R;
@@ -52,6 +53,7 @@
 import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.RemoteAnimationTargets;
+import com.android.quickstep.RotationTouchHelper;
 import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
 import com.android.quickstep.util.TransformParams;
@@ -82,7 +84,7 @@
             getFlagForIndex(1, "STATE_HANDLER_INVALIDATED");
 
     private final Context mContext;
-    private final RecentsAnimationDeviceState mDeviceState;
+    private final RotationTouchHelper mRotationTouchHelper;
     private final TaskAnimationManager mTaskAnimationManager;
     private final GestureState mGestureState;
     private final float mTouchSlopSquared;
@@ -106,18 +108,21 @@
 
     private RecentsAnimationController mRecentsAnimationController;
 
-    public DeviceLockedInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
-            TaskAnimationManager taskAnimationManager, GestureState gestureState,
+    public DeviceLockedInputConsumer(
+            Context context,
+            RecentsAnimationDeviceState deviceState,
+            TaskAnimationManager taskAnimationManager,
+            GestureState gestureState,
             InputMonitorCompat inputMonitorCompat) {
         mContext = context;
-        mDeviceState = deviceState;
         mTaskAnimationManager = taskAnimationManager;
         mGestureState = gestureState;
-        mTouchSlopSquared = mDeviceState.getSquaredTouchSlop();
+        mTouchSlopSquared = deviceState.getSquaredTouchSlop();
         mTransformParams = new TransformParams();
         mInputMonitorCompat = inputMonitorCompat;
         mMaxTranslationY = context.getResources().getDimensionPixelSize(
                 R.dimen.device_locked_y_offset);
+        mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(mContext);
 
         // Do not use DeviceProfile as the user data might be locked
         mDisplaySize = DisplayController.INSTANCE.get(context).getInfo().currentSize;
@@ -136,6 +141,11 @@
     }
 
     @Override
+    public int getDisplayId() {
+        return mGestureState.getDisplayId();
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent ev) {
         if (mVelocityTracker == null) {
             return;
@@ -152,7 +162,7 @@
                 if (!mThresholdCrossed) {
                     // Cancel interaction in case of multi-touch interaction
                     int ptrIdx = ev.getActionIndex();
-                    if (!mDeviceState.getRotationTouchHelper().isInSwipeUpTouchRegion(ev, ptrIdx)) {
+                    if (!mRotationTouchHelper.isInSwipeUpTouchRegion(ev, ptrIdx)) {
                         int action = ev.getAction();
                         ev.setAction(ACTION_CANCEL);
                         finishTouchTracking(ev);
@@ -248,7 +258,7 @@
 
     @Override
     public void onRecentsAnimationStart(RecentsAnimationController controller,
-            RecentsAnimationTargets targets) {
+            RecentsAnimationTargets targets, TransitionInfo transitionInfo) {
         mRecentsAnimationController = controller;
         mTransformParams.setTargetSet(targets);
         applyTransform();
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index f5bef05e..baabde8 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_DEEP_PRESS_STASHED_TASKBAR;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LONG_PRESS_NAVBAR;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LONG_PRESS_STASHED_TASKBAR;
+import static com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.LogConfig.NAV_HANDLE_LONG_PRESS;
 
@@ -30,6 +31,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.Utilities;
+import com.android.launcher3.logging.InstanceIdSequence;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.util.DisplayController;
 import com.android.quickstep.DeviceConfigWrapper;
@@ -49,6 +51,8 @@
     private static final String TAG = "NavHandleLongPressIC";
     private static final boolean DEBUG_NAV_HANDLE = Utilities.isPropertyEnabled(
             NAV_HANDLE_LONG_PRESS);
+    // Minimum time between touch down and abandon to log.
+    @VisibleForTesting static final long MIN_TIME_TO_LOG_ABANDON_MS = 200;
 
     private NavHandleLongPressHandler mNavHandleLongPressHandler;
     private final float mNavHandleWidth;
@@ -62,17 +66,22 @@
     private final int mOuterLongPressTimeout;
     private final boolean mDeepPressEnabled;
     private final NavHandle mNavHandle;
-    private final StatsLogManager mStatsLogManager;
+    private StatsLogManager mStatsLogManager;
     private final TopTaskTracker mTopTaskTracker;
     private final GestureState mGestureState;
 
-    private MotionEvent mCurrentDownEvent;
+    private MotionEvent mCurrentDownEvent;  // Down event that started the current gesture.
+    private MotionEvent mCurrentMotionEvent;  // Most recent motion event.
     private boolean mDeepPressLogged;  // Whether deep press has been logged for the current touch.
 
-    public NavHandleLongPressInputConsumer(Context context, InputConsumer delegate,
-            InputMonitorCompat inputMonitor, RecentsAnimationDeviceState deviceState,
-            NavHandle navHandle, GestureState gestureState) {
-        super(delegate, inputMonitor);
+    public NavHandleLongPressInputConsumer(
+            Context context,
+            InputConsumer delegate,
+            InputMonitorCompat inputMonitor,
+            RecentsAnimationDeviceState deviceState,
+            NavHandle navHandle,
+            GestureState gestureState) {
+        super(gestureState.getDisplayId(), delegate, inputMonitor);
         mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
         mDeepPressEnabled = DeviceConfigWrapper.get().getEnableLpnhDeepPress();
         ContextualSearchStateManager contextualSearchStateManager =
@@ -125,6 +134,10 @@
 
     @Override
     public void onMotionEvent(MotionEvent ev) {
+        if (mCurrentMotionEvent != null) {
+            mCurrentMotionEvent.recycle();
+        }
+        mCurrentMotionEvent = MotionEvent.obtain(ev);
         if (mDelegate.allowInterceptByParent()) {
             handleMotionEvent(ev);
         } else if (MAIN_EXECUTOR.getHandler().hasCallbacks(mTriggerLongPress)) {
@@ -242,7 +255,18 @@
 
     private void cancelLongPress(String reason) {
         if (DEBUG_NAV_HANDLE) {
-            Log.d(TAG, "cancelLongPress");
+            Log.d(TAG, "cancelLongPress: " + reason);
+        }
+        // Log LPNH abandon latency if we didn't trigger but were still prepared to.
+        if (mCurrentMotionEvent != null && mCurrentDownEvent != null) {
+            long latencyMs = mCurrentMotionEvent.getEventTime() - mCurrentDownEvent.getEventTime();
+            if (mState != STATE_ACTIVE && MAIN_EXECUTOR.getHandler().hasCallbacks(mTriggerLongPress)
+                    && latencyMs >= MIN_TIME_TO_LOG_ABANDON_MS) {
+                mStatsLogManager.latencyLogger()
+                        .withInstanceId(new InstanceIdSequence().newInstanceId())
+                        .withLatency(latencyMs)
+                        .log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
+            }
         }
         mGestureState.setIsInExtendedSlopRegion(false);
         MAIN_EXECUTOR.getHandler().removeCallbacks(mTriggerLongPress);
@@ -274,4 +298,9 @@
     void setNavHandleLongPressHandler(NavHandleLongPressHandler navHandleLongPressHandler) {
         mNavHandleLongPressHandler = navHandleLongPressHandler;
     }
+
+    @VisibleForTesting
+    void setStatsLogManager(StatsLogManager statsLogManager) {
+        mStatsLogManager = statsLogManager;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
index 83b556d..67cb992 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
@@ -61,9 +61,13 @@
     private boolean mPassedSlop;
     private boolean mIsStopGesture;
 
-    public OneHandedModeInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
-            InputConsumer delegate, InputMonitorCompat inputMonitor) {
-        super(delegate, inputMonitor);
+    public OneHandedModeInputConsumer(
+            Context context,
+            int displayId,
+            RecentsAnimationDeviceState deviceState,
+            InputConsumer delegate,
+            InputMonitorCompat inputMonitor) {
+        super(displayId, delegate, inputMonitor);
         mContext = context;
         mDeviceState = deviceState;
         mDragDistThreshold = context.getResources().getDimensionPixelSize(
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index c4198db..c8cf58c 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -38,10 +38,10 @@
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
+import android.window.TransitionInfo;
 
 import androidx.annotation.UiThread;
 
-import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestLogging;
@@ -58,6 +58,7 @@
 import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.RotationTouchHelper;
 import com.android.quickstep.TaskAnimationManager;
+import com.android.quickstep.fallback.window.RecentsWindowFlags;
 import com.android.quickstep.util.CachedEventDispatcher;
 import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.util.NavBarPosition;
@@ -125,11 +126,17 @@
     // The callback called upon finishing the recents transition if it was force-canceled
     private Runnable mForceFinishRecentsTransitionCallback;
 
-    public OtherActivityInputConsumer(Context base, RecentsAnimationDeviceState deviceState,
-            TaskAnimationManager taskAnimationManager, GestureState gestureState,
-            boolean isDeferredDownTarget, Consumer<OtherActivityInputConsumer> onCompleteCallback,
-            InputMonitorCompat inputMonitorCompat, InputEventReceiver inputEventReceiver,
-            boolean disableHorizontalSwipe, Factory handlerFactory) {
+    public OtherActivityInputConsumer(
+            Context base,
+            RecentsAnimationDeviceState deviceState,
+            TaskAnimationManager taskAnimationManager,
+            GestureState gestureState,
+            boolean isDeferredDownTarget,
+            Consumer<OtherActivityInputConsumer> onCompleteCallback,
+            InputMonitorCompat inputMonitorCompat,
+            InputEventReceiver inputEventReceiver,
+            boolean disableHorizontalSwipe,
+            Factory handlerFactory) {
         super(base);
         mDeviceState = deviceState;
         mNavBarPosition = mDeviceState.getNavBarPosition();
@@ -156,8 +163,7 @@
         mPassedPilferInputSlop = mPassedWindowMoveSlop = continuingPreviousGesture;
         mStartDisplacement = continuingPreviousGesture ? 0 : -mTouchSlop;
         mDisableHorizontalSwipe = !mPassedPilferInputSlop && disableHorizontalSwipe;
-        mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
-
+        mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(this);
     }
 
     @Override
@@ -166,6 +172,11 @@
     }
 
     @Override
+    public int getDisplayId() {
+        return mGestureState.getDisplayId();
+    }
+
+    @Override
     public boolean isConsumerDetachedFromGesture() {
         return true;
     }
@@ -294,15 +305,15 @@
                 float upDist = -displacement;
                 boolean isTrackpadGesture = mGestureState.isTrackpadGesture();
                 float squaredHypot = squaredHypot(displacementX, displacementY);
-                boolean isInExtendedSlopRegion = !mGestureState.isInExtendedSlopRegion();
+                boolean isInExtendedSlopRegion = mGestureState.isInExtendedSlopRegion();
                 boolean passedSlop = isTrackpadGesture
                         || (squaredHypot >= mSquaredTouchSlop
-                        && isInExtendedSlopRegion);
+                        && !isInExtendedSlopRegion);
                 if (DEBUG) {
                     Log.d(TAG, "ACTION_MOVE: passedSlop=" + passedSlop
                             + " ( " + isTrackpadGesture
                             + " || (" + squaredHypot + " >= " + mSquaredTouchSlop
-                            + " && " + isInExtendedSlopRegion + " ))");
+                            + " && " + !isInExtendedSlopRegion + " ))");
                 }
 
                 if (!mPassedSlopOnThisGesture && passedSlop) {
@@ -427,10 +438,8 @@
             notifyGestureStarted(true /*isLikelyToStartNewTask*/);
         } else {
             // todo differentiate intent based on if we are on home or in app for overview in window
-            boolean useHomeIntentForWindow = Flags.enableFallbackOverviewInWindow()
-                    || Flags.enableLauncherOverviewInWindow();
-            Intent intent = new Intent(useHomeIntentForWindow ? mInteractionHandler.getHomeIntent()
-                : mInteractionHandler.getLaunchIntent());
+            Intent intent = new Intent(RecentsWindowFlags.Companion.getEnableOverviewInWindow()
+                    ? mInteractionHandler.getHomeIntent() : mInteractionHandler.getLaunchIntent());
             intent.putExtra(INTENT_EXTRA_LOG_TRACE_ID, mGestureState.getGestureId());
             mActiveCallbacks = mTaskAnimationManager.startRecentsAnimation(mGestureState, intent,
                     mInteractionHandler);
@@ -472,7 +481,8 @@
                                 : velocityYPxPerMs;
                 mInteractionHandler.updateDisplacement(getDisplacement(ev) - mStartDisplacement);
                 mInteractionHandler.onGestureEnded(velocityPxPerMs,
-                        new PointF(velocityXPxPerMs, velocityYPxPerMs));
+                        new PointF(velocityXPxPerMs, velocityYPxPerMs),
+                        Math.abs(mDownPos.x - mLastPos.x) > mTouchSlop);
             }
         } else {
             // Since we start touch tracking on DOWN, we may reach this state without actually
@@ -574,8 +584,9 @@
     private static class FinishImmediatelyHandler
             implements RecentsAnimationCallbacks.RecentsAnimationListener {
 
+        @Override
         public void onRecentsAnimationStart(RecentsAnimationController controller,
-                RecentsAnimationTargets targets) {
+                RecentsAnimationTargets targets, TransitionInfo transitionInfo) {
             if (DEBUG) {
                 Log.d(TAG, "FinishImmediatelyHandler: queuing callback");
             }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
index a236eca..4658cb0 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewInputConsumer.java
@@ -55,12 +55,16 @@
     private final int[] mLocationOnScreen = new int[2];
 
     private final boolean mStartingInActivityBounds;
+
     private boolean mTargetHandledTouch;
     private boolean mHasSetTouchModeForFirstDPadEvent;
     private boolean mIsWaitingForAttachToWindow;
 
-    public OverviewInputConsumer(GestureState gestureState, T container,
-            @Nullable InputMonitorCompat inputMonitor, boolean startingInActivityBounds) {
+    public OverviewInputConsumer(
+            GestureState gestureState,
+            T container,
+            @Nullable InputMonitorCompat inputMonitor,
+            boolean startingInActivityBounds) {
         mContainer = container;
         mInputMonitor = inputMonitor;
         mStartingInActivityBounds = startingInActivityBounds;
@@ -77,6 +81,11 @@
     }
 
     @Override
+    public int getDisplayId() {
+        return mGestureState.getDisplayId();
+    }
+
+    @Override
     public boolean allowInterceptByParent() {
         return !mTargetHandledTouch;
     }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
index 42e8694..7838e86 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OverviewWithoutFocusInputConsumer.java
@@ -24,11 +24,10 @@
 import android.graphics.PointF;
 import android.view.MotionEvent;
 
-import com.android.launcher3.BaseActivity;
-import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
+import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationDeviceState;
@@ -44,9 +43,12 @@
     private final TriggerSwipeUpTouchTracker mTriggerSwipeUpTracker;
     private final GestureState mGestureState;
 
-    public OverviewWithoutFocusInputConsumer(Context context,
-            RecentsAnimationDeviceState deviceState, GestureState gestureState,
-            InputMonitorCompat inputMonitor, boolean disableHorizontalSwipe) {
+    public OverviewWithoutFocusInputConsumer(
+            Context context,
+            RecentsAnimationDeviceState deviceState,
+            GestureState gestureState,
+            InputMonitorCompat inputMonitor,
+            boolean disableHorizontalSwipe) {
         mContext = context;
         mGestureState = gestureState;
         mInputMonitor = inputMonitor;
@@ -60,6 +62,11 @@
     }
 
     @Override
+    public int getDisplayId() {
+        return mGestureState.getDisplayId();
+    }
+
+    @Override
     public boolean allowInterceptByParent() {
         return !mTriggerSwipeUpTracker.interceptedTouch();
     }
@@ -80,7 +87,7 @@
     @Override
     public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
         startHomeIntentSafely(mContext, mGestureState.getHomeIntent(), null, TAG);
-        BaseActivity activity = BaseDraggingActivity.fromContext(mContext);
+        ActivityContext activity = ActivityContext.lookupContext(mContext);
         int state = (mGestureState != null && mGestureState.getEndTarget() != null)
                 ? mGestureState.getEndTarget().containerType
                 : LAUNCHER_STATE_HOME;
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
index 6dcb7bc..52aaa03 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
@@ -28,6 +28,7 @@
 import android.content.Intent;
 import android.graphics.Point;
 import android.view.MotionEvent;
+import android.window.TransitionInfo;
 
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.AnimatorListeners;
@@ -88,9 +89,12 @@
     private RecentsAnimationController mRecentsAnimationController;
     private Boolean mFlingEndsOnHome;
 
-    public ProgressDelegateInputConsumer(Context context,
-            TaskAnimationManager taskAnimationManager, GestureState gestureState,
-            InputMonitorCompat inputMonitorCompat, AnimatedFloat progress) {
+    public ProgressDelegateInputConsumer(
+            Context context,
+            TaskAnimationManager taskAnimationManager,
+            GestureState gestureState,
+            InputMonitorCompat inputMonitorCompat,
+            AnimatedFloat progress) {
         mContext = context;
         mTaskAnimationManager = taskAnimationManager;
         mGestureState = gestureState;
@@ -117,6 +121,11 @@
     }
 
     @Override
+    public int getDisplayId() {
+        return mGestureState.getDisplayId();
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent ev) {
         if (mFlingEndsOnHome == null) {
             mSwipeDetector.onTouchEvent(ev);
@@ -172,7 +181,7 @@
 
     @Override
     public void onRecentsAnimationStart(RecentsAnimationController controller,
-            RecentsAnimationTargets targets) {
+            RecentsAnimationTargets targets, TransitionInfo transitionInfo) {
         mRecentsAnimationController = controller;
         mStateCallback.setState(STATE_TARGET_RECEIVED);
     }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
index d73c23f..9dc27de 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/ScreenPinnedInputConsumer.java
@@ -36,9 +36,12 @@
     private final float mMotionPauseMinDisplacement;
     private final MotionPauseDetector mMotionPauseDetector;
 
+    private final int mDisplayId;
+
     private float mTouchDownY;
 
     public ScreenPinnedInputConsumer(Context context, GestureState gestureState) {
+        mDisplayId = gestureState.getDisplayId();
         mMotionPauseMinDisplacement = context.getResources().getDimension(
                 R.dimen.motion_pause_detector_min_displacement_from_app);
         mMotionPauseDetector = new MotionPauseDetector(context, true /* makePauseHarderToTrigger*/);
@@ -61,6 +64,11 @@
     }
 
     @Override
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    @Override
     public void onMotionEvent(MotionEvent ev) {
         float y = ev.getY();
         switch (ev.getAction()) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
index 871d075..ad1a01b 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
@@ -47,11 +47,15 @@
     private final InputMonitorCompat mInputMonitor;
     private final TriggerSwipeUpTouchTracker mTriggerSwipeUpTracker;
 
+    private final int mDisplayId;
+
     public SysUiOverlayInputConsumer(
             Context context,
+            int displayId,
             RecentsAnimationDeviceState deviceState,
             InputMonitorCompat inputMonitor) {
         mContext = context;
+        mDisplayId = displayId;
         mInputMonitor = inputMonitor;
         mTriggerSwipeUpTracker = new TriggerSwipeUpTouchTracker(context, true,
                 deviceState.getNavBarPosition(), this);
@@ -63,6 +67,11 @@
     }
 
     @Override
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    @Override
     public boolean allowInterceptByParent() {
         return !mTriggerSwipeUpTracker.interceptedTouch();
     }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
index 49bff8d..dbe6e14 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TaskbarUnstashInputConsumer.java
@@ -89,10 +89,14 @@
     // Velocity defined as dp per s
     private float mTaskbarSlowVelocityYThreshold;
 
-    public TaskbarUnstashInputConsumer(Context context, InputConsumer delegate,
-            InputMonitorCompat inputMonitor, TaskbarActivityContext taskbarActivityContext,
-            OverviewCommandHelper overviewCommandHelper, GestureState gestureState) {
-        super(delegate, inputMonitor);
+    public TaskbarUnstashInputConsumer(
+            Context context,
+            InputConsumer delegate,
+            InputMonitorCompat inputMonitor,
+            TaskbarActivityContext taskbarActivityContext,
+            OverviewCommandHelper overviewCommandHelper,
+            GestureState gestureState) {
+        super(gestureState.getDisplayId(), delegate, inputMonitor);
         mTaskbarActivityContext = taskbarActivityContext;
         mOverviewCommandHelper = overviewCommandHelper;
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/TrackpadStatusBarInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/TrackpadStatusBarInputConsumer.java
index f3e21e1..a53a395 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/TrackpadStatusBarInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/TrackpadStatusBarInputConsumer.java
@@ -35,9 +35,12 @@
     private final PointF mDown = new PointF();
     private boolean mHasPassedTouchSlop;
 
-    public TrackpadStatusBarInputConsumer(Context context, InputConsumer delegate,
+    public TrackpadStatusBarInputConsumer(
+            Context context,
+            int displayId,
+            InputConsumer delegate,
             InputMonitorCompat inputMonitor) {
-        super(delegate, inputMonitor);
+        super(displayId, delegate, inputMonitor);
 
         mSystemUiProxy = SystemUiProxy.INSTANCE.get(context);
         mTouchSlop = 2 * ViewConfiguration.get(context).getScaledTouchSlop();
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 4995e77..c1bb250 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -64,6 +64,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
+import com.android.launcher3.RemoveAnimationSettingsTracker;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.AnimatorPlaybackController;
@@ -73,6 +74,7 @@
 import com.android.quickstep.OverviewComponentObserver;
 import com.android.quickstep.OverviewComponentObserver.OverviewChangeListener;
 import com.android.quickstep.TouchInteractionService.TISBinder;
+import com.android.quickstep.util.ActivityPreloadUtil;
 import com.android.quickstep.util.LottieAnimationColorUtils;
 import com.android.quickstep.util.TISBindHelper;
 
@@ -91,6 +93,8 @@
     private static final String LOG_TAG = "AllSetActivity";
     private static final String URI_SYSTEM_NAVIGATION_SETTING =
             "#Intent;action=com.android.settings.SEARCH_RESULT_TRAMPOLINE;S.:settings:fragment_args_key=gesture_system_navigation_input_summary;S.:settings:show_fragment=com.android.settings.gestures.SystemNavigationGestureSettings;end";
+    private static final String INTENT_ACTION_ACTIVITY_CLOSED =
+            "com.android.quickstep.interaction.ACTION_ALL_SET_ACTIVITY_CLOSED";
     private static final String EXTRA_ACCENT_COLOR_DARK_MODE = "suwColorAccentDark";
     private static final String EXTRA_ACCENT_COLOR_LIGHT_MODE = "suwColorAccentLight";
     private static final String EXTRA_DEVICE_NAME = "suwDeviceName";
@@ -104,6 +108,9 @@
 
     private static final float ANIMATION_PAUSE_ALPHA_THRESHOLD = 0.1f;
 
+    private static final String KEY_BACKGROUND_ANIMATION_TOGGLED_ON =
+            "background_animation_toggled_on";
+
     private final AnimatedFloat mSwipeProgress = new AnimatedFloat(this::onSwipeProgressUpdate);
 
     private final InvariantDeviceProfile.OnIDPChangeListener mOnIDPChangeListener =
@@ -121,6 +128,9 @@
 
     private AnimatorPlaybackController mLauncherStartAnim = null;
 
+    // Auto play background animation by default
+    private boolean mBackgroundAnimationToggledOn = true;
+
     private TextView mHintView;
 
     private final OverviewChangeListener mOverviewChangeListener = this::onOverviewTargetChange;
@@ -197,11 +207,27 @@
                         LOTTIE_TERTIARY_COLOR_TOKEN, R.color.all_set_bg_tertiary),
                 getTheme());
 
+        mBackgroundAnimationToggledOn = savedInstanceState == null
+                || savedInstanceState.getBoolean(KEY_BACKGROUND_ANIMATION_TOGGLED_ON, true);
+        // The animated background is behind a scroll view, which intercepts all input.
+        // However, the content view also covers the full screen
+        requireViewById(R.id.content).setOnClickListener(v -> {
+            mBackgroundAnimationToggledOn = !mBackgroundAnimationToggledOn;
+            maybeResumeOrPauseBackgroundAnimation();
+        });
+
         setUpBackgroundAnimation(getDP().isTablet);
         getIDP().addOnChangeListener(mOnIDPChangeListener);
 
         OverviewComponentObserver.INSTANCE.get(this)
                 .addOverviewChangeListener(mOverviewChangeListener);
+        ActivityPreloadUtil.preloadOverviewForSUWAllSet(this);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putBoolean(KEY_BACKGROUND_ANIMATION_TOGGLED_ON, mBackgroundAnimationToggledOn);
     }
 
     private InvariantDeviceProfile getIDP() {
@@ -291,7 +317,6 @@
     private void onTISConnected(TISBinder binder) {
         setSetupUIVisible(isResumed());
         binder.setSwipeUpProxy(isResumed() ? this::createSwipeUpProxy : null);
-        binder.preloadOverviewForSUWAllSet();
         TaskbarManager taskbarManager = binder.getTaskbarManager();
         if (taskbarManager != null) {
             mLauncherStartAnim = taskbarManager.createLauncherStartFromSuwAnim(MAX_SWIPE_DURATION);
@@ -299,10 +324,7 @@
     }
 
     private void onOverviewTargetChange(boolean isHomeAndOverviewSame) {
-        TISBinder binder = mTISBindHelper.getBinder();
-        if (binder != null) {
-            binder.preloadOverviewForSUWAllSet();
-        }
+        ActivityPreloadUtil.preloadOverviewForSUWAllSet(this);
     }
 
     @Override
@@ -334,6 +356,7 @@
             mLauncherStartAnim.dispatchOnEnd();
             mLauncherStartAnim = null;
         }
+        sendBroadcast(new Intent(INTENT_ACTION_ACTIVITY_CLOSED));
     }
 
     @Override
@@ -367,8 +390,10 @@
 
     private void maybeResumeOrPauseBackgroundAnimation() {
         boolean shouldPlayAnimation =
-                getContentViewAlphaForSwipeProgress() > ANIMATION_PAUSE_ALPHA_THRESHOLD
-                        && isResumed();
+                !RemoveAnimationSettingsTracker.INSTANCE.get(this).isRemoveAnimationEnabled()
+                        && getContentViewAlphaForSwipeProgress() > ANIMATION_PAUSE_ALPHA_THRESHOLD
+                        && isResumed()
+                        && mBackgroundAnimationToggledOn;
         if (mAnimatedBackground.isAnimating() && !shouldPlayAnimation) {
             mAnimatedBackground.pauseAnimation();
         } else if (!mAnimatedBackground.isAnimating() && shouldPlayAnimation) {
diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
index be7f8e5..7fe4278 100644
--- a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java
@@ -65,11 +65,6 @@
     }
 
     @Override
-    public int getSpokenIntroductionSubtitle() {
-        return R.string.back_gesture_spoken_intro_subtitle;
-    }
-
-    @Override
     public int getSuccessFeedbackTitle() {
         return R.string.gesture_tutorial_nice;
     }
diff --git a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
index 700fbf8..72bff7f 100644
--- a/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/EdgeBackGestureHandler.java
@@ -29,9 +29,9 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.shared.ResourceUtils;
-import com.android.launcher3.util.DisplayController;
 
 /**
  * Utility class to handle edge swipes for back gestures.
@@ -45,6 +45,7 @@
             "gestures.back_timeout", 250);
 
     private final Context mContext;
+    private final DeviceProfile mDeviceProfile;
 
     private final Point mDisplaySize = new Point();
 
@@ -89,9 +90,10 @@
                 }
             };
 
-    EdgeBackGestureHandler(Context context) {
+    EdgeBackGestureHandler(Context context, DeviceProfile deviceProfile) {
         final Resources res = context.getResources();
         mContext = context;
+        mDeviceProfile = deviceProfile;
 
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         mLongPressTimeout = Math.min(MAX_LONG_PRESS_TIMEOUT,
@@ -116,8 +118,7 @@
             // Add a nav bar panel window.
             mEdgeBackPanel = new EdgeBackGesturePanel(mContext, parent, createLayoutParams());
             mEdgeBackPanel.setBackCallback(mBackCallback);
-            Point currentSize = DisplayController.INSTANCE.get(mContext).getInfo().currentSize;
-            mDisplaySize.set(currentSize.x, currentSize.y);
+            mDisplaySize.set(mDeviceProfile.widthPx, mDeviceProfile.heightPx);
             mEdgeBackPanel.setDisplaySize(mDisplaySize);
         }
     }
diff --git a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
index bc5cc15..0365f89 100644
--- a/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/GestureSandboxActivity.java
@@ -115,22 +115,13 @@
 
     private void initWindowInsets() {
         View root = findViewById(android.R.id.content);
-        root.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
-            @Override
-            public void onLayoutChange(View v, int left, int top, int right, int bottom,
-                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
-                updateExclusionRects(root);
-            }
-        });
+        root.addOnLayoutChangeListener(
+                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
+                        updateExclusionRects(root));
 
         // Return CONSUMED if you don't want want the window insets to keep being
         // passed down to descendant views.
-        root.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
-            @Override
-            public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
-                return WindowInsets.CONSUMED;
-            }
-        });
+        root.setOnApplyWindowInsetsListener((v, insets) -> WindowInsets.CONSUMED);
     }
 
     private void updateExclusionRects(View rootView) {
diff --git a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
index bf4eaf2..b059695 100644
--- a/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/HomeGestureTutorialController.java
@@ -58,11 +58,6 @@
     }
 
     @Override
-    public int getSpokenIntroductionSubtitle() {
-        return R.string.home_gesture_spoken_intro_subtitle;
-    }
-
-    @Override
     public int getSuccessFeedbackTitle() {
         return R.string.home_gesture_tutorial_success;
     }
diff --git a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
index c00f508..fdbd509 100644
--- a/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
+++ b/quickstep/src/com/android/quickstep/interaction/NavBarGestureHandler.java
@@ -33,6 +33,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.testing.shared.ResourceUtils;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.NavigationMode;
@@ -57,15 +58,16 @@
     @Nullable
     private NavBarGestureAttemptCallback mGestureCallback;
 
-    NavBarGestureHandler(Context context) {
+    NavBarGestureHandler(Context context, DeviceProfile deviceProfile) {
         mContext = context;
-        DisplayController.Info displayInfo = DisplayController.INSTANCE.get(mContext).getInfo();
-        Point currentSize = displayInfo.currentSize;
-        mDisplaySize.set(currentSize.x, currentSize.y);
-        mSwipeUpTouchTracker =
-                new TriggerSwipeUpTouchTracker(context, true /*disableHorizontalSwipe*/,
-                        new NavBarPosition(NavigationMode.NO_BUTTON, displayInfo),
-                        this);
+        mDisplaySize.set(deviceProfile.widthPx, deviceProfile.heightPx);
+        mSwipeUpTouchTracker = new TriggerSwipeUpTouchTracker(
+                context,
+                /* disableHorizontalSwipe= */ true,
+                new NavBarPosition(
+                        NavigationMode.NO_BUTTON,
+                        DisplayController.INSTANCE.get(mContext).getInfo()),
+                /* onSwipeUp= */ this);
         mMotionPauseDetector = new MotionPauseDetector(context);
 
         final Resources resources = context.getResources();
diff --git a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
index e45f8d8..ff0d6d1 100644
--- a/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/OverviewGestureTutorialController.java
@@ -73,11 +73,6 @@
     }
 
     @Override
-    public int getSpokenIntroductionSubtitle() {
-        return R.string.overview_gesture_spoken_intro_subtitle;
-    }
-
-    @Override
     public int getSuccessFeedbackTitle() {
         return R.string.overview_gesture_tutorial_success;
     }
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index 1c4e7a7..c63cddf 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -34,6 +34,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.view.Display;
 import android.view.View;
 import android.view.ViewOutlineProvider;
 
@@ -50,7 +51,6 @@
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.OverviewComponentObserver;
-import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.RemoteTargetGluer;
 import com.android.quickstep.SwipeUpAnimationLogic;
 import com.android.quickstep.SwipeUpAnimationLogic.RunningWindowAnim;
@@ -85,10 +85,8 @@
 
     SwipeUpGestureTutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) {
         super(tutorialFragment, tutorialType);
-        RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(mContext);
-        mTaskViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, deviceState,
-                new GestureState(OverviewComponentObserver.INSTANCE.get(mContext), -1));
-        deviceState.destroy();
+        mTaskViewSwipeUpAnimation = new ViewSwipeUpAnimation(mContext, new GestureState(
+                OverviewComponentObserver.INSTANCE.get(mContext), Display.DEFAULT_DISPLAY, -1));
 
         DeviceProfile dp = InvariantDeviceProfile.INSTANCE.get(mContext)
                 .getDeviceProfile(mContext)
@@ -311,9 +309,8 @@
 
     class ViewSwipeUpAnimation extends SwipeUpAnimationLogic {
 
-        ViewSwipeUpAnimation(Context context, RecentsAnimationDeviceState deviceState,
-                             GestureState gestureState) {
-            super(context, deviceState, gestureState);
+        ViewSwipeUpAnimation(Context context, GestureState gestureState) {
+            super(context, gestureState);
             mRemoteTargetHandles[0] = new RemoteTargetGluer.RemoteTargetHandle(
                     mRemoteTargetHandles[0].getTaskViewSimulator(), new FakeTransformParams());
 
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 9510a05..5995ca2 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -36,7 +36,6 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewOutlineProvider;
-import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Button;
 import android.widget.FrameLayout;
@@ -61,6 +60,8 @@
 import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
 import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback;
 import com.android.systemui.shared.system.QuickStepContract;
+import com.android.wm.shell.shared.TypefaceUtils;
+import com.android.wm.shell.shared.TypefaceUtils.FontFamily;
 
 import com.airbnb.lottie.LottieAnimationView;
 import com.airbnb.lottie.LottieComposition;
@@ -80,8 +81,8 @@
     private static final CharSequence DEFAULT_PIXEL_TIPS_APP_NAME = "Pixel Tips";
 
     private static final int FEEDBACK_ANIMATION_MS = 133;
-    private static final int RIPPLE_VISIBLE_MS = 300;
-    private static final int GESTURE_ANIMATION_DELAY_MS = 1500;
+    private static final int SUBTITLE_ANNOUNCE_DELAY_MS = 3000;
+    private static final int DONE_BUTTON_ANNOUNCE_DELAY_MS = 4000;
     private static final int ADVANCE_TUTORIAL_TIMEOUT_MS = 3000;
     private static final long GESTURE_ANIMATION_PAUSE_DURATION_MILLIS = 1000;
     protected float mExitingAppEndingCornerRadius;
@@ -109,7 +110,6 @@
     final AnimatedTaskView mFakePreviousTaskView;
     final View mRippleView;
     final RippleDrawable mRippleDrawable;
-    final TutorialStepIndicator mTutorialStepView;
     final ImageView mFingerDotView;
     private final Rect mExitingAppRect = new Rect();
     protected View mExitingAppView;
@@ -123,7 +123,6 @@
 
     // These runnables  should be used when posting callbacks to their views and cleared from their
     // views before posting new callbacks.
-    private final Runnable mTitleViewCallback;
     @Nullable private Runnable mFeedbackViewCallback;
     @Nullable private Runnable mFakeTaskViewCallback;
     @Nullable private Runnable mFakeTaskbarViewCallback;
@@ -153,8 +152,6 @@
         mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view);
         mRippleDrawable = (RippleDrawable) mRippleView.getBackground();
         mDoneButton = rootView.findViewById(R.id.gesture_tutorial_fragment_action_button);
-        mTutorialStepView =
-                rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_tutorial_step);
         mFingerDotView = rootView.findViewById(R.id.gesture_tutorial_finger_dot);
         mSkipTutorialDialog = createSkipTutorialDialog();
 
@@ -175,6 +172,8 @@
 
         mFeedbackTitleView.setText(getIntroductionTitle());
         mFeedbackSubtitleView.setText(getIntroductionSubtitle());
+        setTitleTypefaces();
+
         mExitingAppView.setClipToOutline(true);
         mExitingAppView.setOutlineProvider(new ViewOutlineProvider() {
             @Override
@@ -182,9 +181,6 @@
                 outline.setRoundRect(mExitingAppRect, mExitingAppRadius);
             }
         });
-
-        mTitleViewCallback = () -> mFeedbackTitleView.sendAccessibilityEvent(
-                AccessibilityEvent.TYPE_VIEW_FOCUSED);
         mShowFeedbackRunnable = () -> {
             mFeedbackView.setAlpha(0f);
             mFeedbackView.setScaleX(0.95f);
@@ -206,11 +202,11 @@
                                     AccessibilityManager.getInstance(mContext)
                                             .getRecommendedTimeoutMillis(
                                                     ADVANCE_TUTORIAL_TIMEOUT_MS,
-                                                    AccessibilityManager.FLAG_CONTENT_TEXT));
+                                                    AccessibilityManager.FLAG_CONTENT_TEXT
+                                                    | AccessibilityManager.FLAG_CONTENT_CONTROLS));
                         }
                     })
                     .start();
-            mFeedbackTitleView.postDelayed(mTitleViewCallback, FEEDBACK_ANIMATION_MS);
         };
     }
 
@@ -318,11 +314,6 @@
     }
 
     @StringRes
-    public int getSpokenIntroductionSubtitle() {
-        return NO_ID;
-    }
-
-    @StringRes
     public int getSuccessFeedbackSubtitle() {
         return NO_ID;
     }
@@ -401,18 +392,13 @@
                 isGestureSuccessful
                         ? getSuccessFeedbackTitle() : R.string.gesture_tutorial_try_again,
                 subtitleResId,
-                NO_ID,
-                isGestureSuccessful,
-                false);
+                isGestureSuccessful);
     }
 
     void showFeedback(
             int titleResId,
             int subtitleResId,
-            int spokenSubtitleResId,
-            boolean isGestureSuccessful,
-            boolean useGestureAnimationDelay) {
-        mFeedbackTitleView.removeCallbacks(mTitleViewCallback);
+            boolean isGestureSuccessful) {
         if (mFeedbackViewCallback != null) {
             mFeedbackView.removeCallbacks(mFeedbackViewCallback);
             mFeedbackViewCallback = null;
@@ -422,6 +408,10 @@
         mFeedbackSubtitleView.setText(subtitleResId);
         if (isGestureSuccessful) {
             if (mTutorialFragment.isAtFinalStep()) {
+                TypefaceUtils.setTypeface(
+                        mDoneButton,
+                        FontFamily.GSF_LABEL_LARGE
+                );
                 showActionButton();
             }
 
@@ -446,7 +436,8 @@
         pauseAndHideLottieAnimation();
         mCheckmarkAnimation.setVisibility(View.VISIBLE);
         mCheckmarkAnimation.playAnimation();
-        mFeedbackTitleView.setTextAppearance(mContext, getSuccessTitleTextAppearance());
+        mFeedbackTitleView.setTextAppearance(getSuccessTitleTextAppearance());
+        setTitleTypefaces();
     }
 
     public boolean isGestureCompleted() {
@@ -475,7 +466,6 @@
             mFakeTaskbarView.removeCallbacks(mFakeTaskbarViewCallback);
             mFakeTaskbarViewCallback = null;
         }
-        mFeedbackTitleView.removeCallbacks(mTitleViewCallback);
     }
 
     private void playFeedbackAnimation() {
@@ -496,12 +486,13 @@
     @CallSuper
     void transitToController() {
         updateCloseButton();
-        updateSubtext();
         updateDrawables();
         updateLayout();
 
-        mFeedbackTitleView.setTextAppearance(mContext, getTitleTextAppearance());
-        mDoneButton.setTextAppearance(mContext, getDoneButtonTextAppearance());
+        mFeedbackTitleView.setTextAppearance(getTitleTextAppearance());
+        mDoneButton.setTextAppearance(getDoneButtonTextAppearance());
+
+        setTitleTypefaces();
         mDoneButton.getBackground().setTint(getDoneButtonColor());
         mCheckmarkAnimation.setAnimation(mTutorialFragment.isAtFinalStep()
                 ? R.raw.checkmark_animation_end
@@ -512,7 +503,6 @@
             if (mTutorialType == TutorialType.BACK_NAVIGATION) {
                 resetViewsForBackGesture();
             }
-
         }
 
         mGestureCompleted = false;
@@ -521,6 +511,21 @@
         }
     }
 
+    /**
+     * Apply expressive typefaces to the feedback title and subtitle views.
+     */
+    private void setTitleTypefaces() {
+        TypefaceUtils.setTypeface(
+                mFeedbackTitleView,
+                mTutorialFragment.isLargeScreen()
+                        ? FontFamily.GSF_DISPLAY_MEDIUM_EMPHASIZED
+                        : FontFamily.GSF_DISPLAY_SMALL_EMPHASIZED);
+        TypefaceUtils.setTypeface(
+                mFeedbackSubtitleView,
+                FontFamily.GSF_BODY_LARGE
+        );
+    }
+
     protected void resetViewsForBackGesture() {
         mFakeTaskView.setVisibility(View.VISIBLE);
         mFakeTaskView.setBackgroundColor(getFakeTaskViewColor());
@@ -597,11 +602,6 @@
         }
     }
 
-    private void updateSubtext() {
-        mTutorialStepView.setTutorialProgress(
-                mTutorialFragment.getCurrentStep(), mTutorialFragment.getNumSteps());
-    }
-
     private void updateHotseatChildViewColor(@Nullable View child) {
         if (child == null) return;
         child.getBackground().setTint(getHotseatIconColor());
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
index 2ff2c83..8174e13 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialFragment.java
@@ -48,6 +48,7 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.quickstep.interaction.TutorialController.TutorialType;
@@ -175,8 +176,10 @@
         Bundle args = savedInstanceState != null ? savedInstanceState : getArguments();
         mTutorialType = (TutorialType) args.getSerializable(KEY_TUTORIAL_TYPE);
         mGestureComplete = args.getBoolean(KEY_GESTURE_COMPLETE, false);
-        mEdgeBackGestureHandler = new EdgeBackGestureHandler(getContext());
-        mNavBarGestureHandler = new NavBarGestureHandler(getContext());
+        DeviceProfile deviceProfile = LauncherAppState.getInstance(getContext())
+                .getInvariantDeviceProfile().getDeviceProfile(getContext());
+        mEdgeBackGestureHandler = new EdgeBackGestureHandler(getContext(), deviceProfile);
+        mNavBarGestureHandler = new NavBarGestureHandler(getContext(), deviceProfile);
 
         mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(getContext())
                 .getDeviceProfile(getContext());
@@ -212,7 +215,6 @@
     public View onCreateView(
             @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         super.onCreateView(inflater, container, savedInstanceState);
-
         mRootView = (RootSandboxLayout) inflater.inflate(
                 R.layout.redesigned_gesture_tutorial_fragment,
                 container,
@@ -268,9 +270,7 @@
             mTutorialController.showFeedback(
                     introTitleResId,
                     introSubtitleResId,
-                    mTutorialController.getSpokenIntroductionSubtitle(),
-                    false,
-                    true);
+                    /* isGestureSuccessful= */ false);
             mIntroductionShown = true;
         }
     }
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java b/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java
deleted file mode 100644
index f1fc179..0000000
--- a/quickstep/src/com/android/quickstep/interaction/TutorialStepIndicator.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2021 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.interaction;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.icons.GraphicsUtils;
-
-/** Indicator displaying the current progress through the gesture navigation tutorial. */
-public class TutorialStepIndicator extends LinearLayout {
-
-    private static final String LOG_TAG = "TutorialStepIndicator";
-
-    private int mCurrentStep = -1;
-    private int mTotalSteps = -1;
-
-    public TutorialStepIndicator(Context context) {
-        super(context);
-    }
-
-    public TutorialStepIndicator(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public TutorialStepIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public TutorialStepIndicator(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    /**
-     * Updates this indicator to display totalSteps indicator pills, with the first currentStep
-     * pills highlighted.
-     */
-    public void setTutorialProgress(int currentStep, int totalSteps) {
-        if (currentStep <= 0) {
-            Log.w(LOG_TAG, "Current step number invalid: " + currentStep + ". Assuming step 1.");
-            currentStep = 1;
-        }
-        if (totalSteps <= 0) {
-            Log.w(LOG_TAG, "Total number of steps invalid: " + totalSteps + ". Assuming 1 step.");
-            totalSteps = 1;
-        }
-        if (currentStep > totalSteps) {
-            Log.w(LOG_TAG, "Current step number greater than the total number of steps. Assuming"
-                    + " final step.");
-            currentStep = totalSteps;
-        }
-        if (totalSteps < 2) {
-            setVisibility(GONE);
-            return;
-        }
-        setVisibility(VISIBLE);
-        mCurrentStep = currentStep;
-        mTotalSteps = totalSteps;
-
-        initializeStepIndicators();
-    }
-
-    private void initializeStepIndicators() {
-        for (int i = mTotalSteps; i < getChildCount(); i++) {
-            removeViewAt(i);
-        }
-        int activeStepIndicatorColor = GraphicsUtils.getAttrColor(
-                getContext(), android.R.attr.textColorPrimary);
-        int inactiveStepIndicatorColor = GraphicsUtils.getAttrColor(
-                getContext(), android.R.attr.textColorSecondaryInverse);
-        for (int i = 0; i < mTotalSteps; i++) {
-            Drawable pageIndicatorPillDrawable =
-                    getContext().getDrawable(R.drawable.tutorial_step_indicator_pill);
-            if (i >= getChildCount()) {
-                ImageView pageIndicatorPill = new ImageView(getContext());
-                pageIndicatorPill.setImageDrawable(pageIndicatorPillDrawable);
-
-                LinearLayout.LayoutParams lp = new LayoutParams(
-                        LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
-
-                lp.setMarginStart(Utilities.dpToPx(3));
-                lp.setMarginEnd(Utilities.dpToPx(3));
-
-                addView(pageIndicatorPill, lp);
-            }
-            if (pageIndicatorPillDrawable != null) {
-                if (i < mCurrentStep) {
-                    pageIndicatorPillDrawable.setTint(activeStepIndicatorColor);
-                } else {
-                    pageIndicatorPillDrawable.setTint(inactiveStepIndicatorColor);
-                }
-            }
-        }
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index dd721e1..0cc349d 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -16,9 +16,10 @@
 
 package com.android.quickstep.logging;
 
-import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
 import static com.android.launcher3.LauncherPrefs.getDevicePrefs;
 import static com.android.launcher3.LauncherPrefs.getPrefs;
+import static com.android.launcher3.graphics.ThemeManager.KEY_THEMED_ICONS;
+import static com.android.launcher3.graphics.ThemeManager.THEMED_ICONS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_SCREEN_SUGGESTIONS_DISABLED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_SCREEN_SUGGESTIONS_ENABLED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_DISABLED;
@@ -29,7 +30,6 @@
 import static com.android.launcher3.model.PredictionUpdateTask.LAST_PREDICTION_ENABLED;
 import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
 import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
-import static com.android.launcher3.util.Themes.KEY_THEMED_ICONS;
 
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -44,16 +44,18 @@
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.logging.StatsLogManager.StatsLogger;
 import com.android.launcher3.model.DeviceGridState;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.NavigationMode;
-import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SettingsCache;
+import com.android.quickstep.dagger.QuickstepBaseAppComponent;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -61,18 +63,20 @@
 import java.io.IOException;
 import java.util.Optional;
 
+import javax.inject.Inject;
+
 /**
  * Utility class to log launcher settings changes
  */
+@LauncherAppSingleton
 public class SettingsChangeLogger implements
-        DisplayController.DisplayInfoChangeListener, OnSharedPreferenceChangeListener,
-        SafeCloseable {
+        DisplayController.DisplayInfoChangeListener, OnSharedPreferenceChangeListener {
 
     /**
      * Singleton instance
      */
-    public static MainThreadInitializedObject<SettingsChangeLogger> INSTANCE =
-            new MainThreadInitializedObject<>(SettingsChangeLogger::new);
+    public static DaggerSingletonObject<SettingsChangeLogger> INSTANCE =
+            new DaggerSingletonObject<>(QuickstepBaseAppComponent::getSettingsChangeLogger);
 
     private static final String TAG = "SettingsChangeLogger";
     private static final String BOOLEAN_PREF = "SwitchPreference";
@@ -85,26 +89,43 @@
     private StatsLogManager.LauncherEvent mNotificationDotsEvent;
     private StatsLogManager.LauncherEvent mHomeScreenSuggestionEvent;
 
-    SettingsChangeLogger(@ApplicationContext Context context) {
-        this(context, StatsLogManager.newInstance(context));
+    private final SettingsCache.OnChangeListener mListener = this::onNotificationDotsChanged;
+
+    @Inject
+    SettingsChangeLogger(@ApplicationContext Context context,
+            DaggerSingletonTracker tracker,
+            DisplayController displayController,
+            SettingsCache settingsCache) {
+        this(context, StatsLogManager.newInstance(context), tracker, displayController,
+                settingsCache);
     }
 
     @VisibleForTesting
-    SettingsChangeLogger(Context context, StatsLogManager statsLogManager) {
+    SettingsChangeLogger(@ApplicationContext Context context,
+            StatsLogManager statsLogManager,
+            DaggerSingletonTracker tracker,
+            DisplayController displayController,
+            SettingsCache settingsCache) {
         mContext = context;
         mStatsLogManager = statsLogManager;
         mLoggablePrefs = loadPrefKeys(context);
 
-        DisplayController.INSTANCE.get(context).addChangeListener(this);
-        mNavMode = DisplayController.getNavigationMode(context);
+        displayController.addChangeListener(this);
+        mNavMode = displayController.getInfo().getNavigationMode();
+        tracker.addCloseable(() -> displayController.removeChangeListener(this));
 
         getPrefs(context).registerOnSharedPreferenceChangeListener(this);
         getDevicePrefs(context).registerOnSharedPreferenceChangeListener(this);
+        tracker.addCloseable(() -> {
+            getPrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
+            getDevicePrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
+        });
 
-        SettingsCache settingsCache = SettingsCache.INSTANCE.get(context);
-        settingsCache.register(NOTIFICATION_BADGING_URI,
-                this::onNotificationDotsChanged);
+        settingsCache.register(NOTIFICATION_BADGING_URI, mListener);
         onNotificationDotsChanged(settingsCache.getValue(NOTIFICATION_BADGING_URI));
+        tracker.addCloseable(() -> {
+            settingsCache.unregister(NOTIFICATION_BADGING_URI, mListener);
+        });
     }
 
     private static ArrayMap<String, LoggablePref> loadPrefKeys(Context context) {
@@ -207,12 +228,6 @@
         return mLoggablePrefs;
     }
 
-    @Override
-    public void close() {
-        getPrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
-        getDevicePrefs(mContext).unregisterOnSharedPreferenceChangeListener(this);
-    }
-
     @VisibleForTesting
     static class LoggablePref {
         public boolean defaultValue;
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index 9bfe71f..58e54cf 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -31,15 +31,15 @@
 import static com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers.ContainerCase.DEVICE_SEARCH_RESULT_CONTAINER;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORKSPACE_SNAPSHOT;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_0;
-import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_90;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_180;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_270;
+import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DISPLAY_ROTATION__ROTATION_90;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__ALLAPPS;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__BACKGROUND;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__HOME;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__DST_STATE__OVERVIEW;
-import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__PORTRAIT;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__LANDSCAPE;
+import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__PORTRAIT;
 import static com.android.systemui.shared.system.SysUiStatsLog.LAUNCHER_UICHANGED__RECENTS_ORIENTATION_HANDLER__SEASCAPE;
 
 import android.content.Context;
@@ -69,6 +69,7 @@
 import com.android.launcher3.logger.LauncherAtomExtensions.ExtendedContainers;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.Executors;
@@ -381,17 +382,15 @@
                 return;
             }
 
-            if (mItemInfo.container < 0 || !LauncherAppState.INSTANCE.executeIfCreated(app -> {
-                // Item is inside a collection, fetch collection info in a BG thread
-                // and then write to StatsLog.
-                app.getModel().enqueueModelUpdateTask((taskController, dataModel, apps) ->
-                        write(event, applyOverwrites(mItemInfo.buildProto(
-                                dataModel.collections.get(mItemInfo.container), mContext))));
-            })) {
-                // Write log on the model thread so that logs do not go out of order
-                // (for eg: drop comes after drag)
-                Executors.MODEL_EXECUTOR.execute(
-                        () -> write(event, applyOverwrites(mItemInfo.buildProto(mContext))));
+            // Item is inside a collection, fetch collection info in a BG thread
+            // and then write to StatsLog.
+            if (mItemInfo.container < 0) {
+                LauncherAppState.INSTANCE.get(mContext).getModel().enqueueModelUpdateTask(
+                        (taskController, dataModel, apps) -> write(event, applyOverwrites(
+                                mItemInfo.buildProto(
+                                        (CollectionInfo) dataModel.itemsIdMap
+                                                .get(mItemInfo.container),
+                                        mContext))));
             }
         }
 
@@ -423,6 +422,22 @@
                 case LAUNCHER_PRIVATE_SPACE_UNLOCK_ANIMATION_END:
                     InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK);
                     break;
+                case LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_BEGIN:
+                    InteractionJankMonitorWrapper.begin(
+                            view,
+                            Cuj.CUJ_LAUNCHER_WORK_UTILITY_VIEW_EXPAND);
+                    break;
+                case LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_END:
+                    InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_WORK_UTILITY_VIEW_EXPAND);
+                    break;
+                case LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_BEGIN:
+                    InteractionJankMonitorWrapper.begin(
+                            view,
+                            Cuj.CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK);
+                    break;
+                case LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_END:
+                    InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_WORK_UTILITY_VIEW_SHRINK);
+                    break;
                 default:
                     break;
             }
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
index 88ef0a8..c4e343e 100644
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
@@ -103,7 +103,7 @@
         target: T,
         action: Int2DAction<T>,
         primaryParam: Int,
-        secondaryParam: Int
+        secondaryParam: Int,
     ) = action.call(target, secondaryParam, primaryParam)
 
     override fun getPrimaryDirection(event: MotionEvent, pointerIndex: Int): Float =
@@ -171,7 +171,7 @@
 
     override fun getSplitTranslationDirectionFactor(
         stagePosition: Int,
-        deviceProfile: DeviceProfile
+        deviceProfile: DeviceProfile,
     ): Int = if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) -1 else 1
 
     override fun getTaskMenuX(
@@ -179,7 +179,7 @@
         thumbnailView: View,
         deviceProfile: DeviceProfile,
         taskInsetMargin: Float,
-        taskViewIcon: View
+        taskViewIcon: View,
     ): Float = thumbnailView.measuredWidth + x - taskInsetMargin
 
     override fun getTaskMenuY(
@@ -188,7 +188,7 @@
         stagePosition: Int,
         taskMenuView: View,
         taskInsetMargin: Float,
-        taskViewIcon: View
+        taskViewIcon: View,
     ): Float {
         val layoutParams = taskMenuView.layoutParams as BaseDragLayer.LayoutParams
         var taskMenuY = y + taskInsetMargin
@@ -203,7 +203,7 @@
     override fun getTaskMenuWidth(
         thumbnailView: View,
         deviceProfile: DeviceProfile,
-        @StagePosition stagePosition: Int
+        @StagePosition stagePosition: Int,
     ): Int =
         when {
             Flags.enableOverviewIconMenu() ->
@@ -218,14 +218,14 @@
         taskInsetMargin: Float,
         deviceProfile: DeviceProfile,
         taskMenuX: Float,
-        taskMenuY: Float
+        taskMenuY: Float,
     ): Int = (taskMenuX - taskInsetMargin).toInt()
 
     override fun setTaskOptionsMenuLayoutOrientation(
         deviceProfile: DeviceProfile,
         taskMenuLayout: LinearLayout,
         dividerSpacing: Int,
-        dividerDrawable: ShapeDrawable
+        dividerDrawable: ShapeDrawable,
     ) {
         taskMenuLayout.orientation = LinearLayout.VERTICAL
         dividerDrawable.intrinsicHeight = dividerSpacing
@@ -235,7 +235,7 @@
     override fun setLayoutParamsForTaskMenuOptionItem(
         lp: LinearLayout.LayoutParams,
         viewGroup: LinearLayout,
-        deviceProfile: DeviceProfile
+        deviceProfile: DeviceProfile,
     ) {
         // Phone fake landscape
         viewGroup.orientation = LinearLayout.HORIZONTAL
@@ -250,7 +250,7 @@
         deviceProfile: DeviceProfile,
         snapshotViewWidth: Int,
         snapshotViewHeight: Int,
-        banner: View
+        banner: View,
     ) {
         banner.pivotX = 0f
         banner.pivotY = 0f
@@ -273,9 +273,9 @@
         deviceProfile: DeviceProfile,
         thumbnailViews: Array<View>,
         desiredTaskId: Int,
-        banner: View
+        banner: View,
     ): Pair<Float, Float> {
-        val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
+        val snapshotParams = thumbnailViews[0].layoutParams as LinearLayout.LayoutParams
         val translationX = banner.height.toFloat()
         val translationY: Float
         if (splitBounds == null) {
@@ -285,11 +285,7 @@
                 translationY = snapshotParams.topMargin.toFloat()
             } else {
                 val topLeftTaskPlusDividerPercent =
-                    if (splitBounds.appsStackedVertically) {
-                        splitBounds.topTaskPercent + splitBounds.dividerHeightPercent
-                    } else {
-                        splitBounds.leftTaskPercent + splitBounds.dividerWidthPercent
-                    }
+                    splitBounds.leftTopTaskPercent + splitBounds.dividerPercent
                 translationY =
                     snapshotParams.topMargin +
                         (taskViewHeight - snapshotParams.topMargin) * topLeftTaskPlusDividerPercent
@@ -306,18 +302,30 @@
         if (isRtl) SingleAxisSwipeDetector.DIRECTION_NEGATIVE
         else SingleAxisSwipeDetector.DIRECTION_POSITIVE
 
+    override fun getDownDirection(isRtl: Boolean): Int =
+        if (isRtl) SingleAxisSwipeDetector.DIRECTION_POSITIVE
+        else SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+
     override fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean =
         if (isRtl) displacement < 0 else displacement > 0
 
     override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) 1 else -1
 
+    override fun getTaskDismissVerticalDirection(): Int = 1
+
+    override fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+        secondaryDimension - taskThumbnailBounds.left
+
+    override fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+        taskThumbnailBounds.left
+
     /* -------------------- */
 
     override fun getChildBounds(
         child: View,
         childStart: Int,
         pageCenter: Int,
-        layoutChild: Boolean
+        layoutChild: Boolean,
     ): ChildBounds {
         val childHeight = child.measuredHeight
         val childWidth = child.measuredWidth
@@ -338,7 +346,7 @@
                 R.drawable.ic_split_horizontal,
                 R.string.recent_task_option_split_screen,
                 STAGE_POSITION_TOP_OR_LEFT,
-                STAGE_TYPE_MAIN
+                STAGE_TYPE_MAIN,
             )
         )
 
@@ -347,7 +355,7 @@
         placeholderInset: Int,
         dp: DeviceProfile,
         @StagePosition stagePosition: Int,
-        out: Rect
+        out: Rect,
     ) {
         // In fake land/seascape, the placeholder always needs to go to the "top" of the device,
         // which is the same bounds as 0 rotation.
@@ -374,7 +382,7 @@
         drawableWidth: Int,
         drawableHeight: Int,
         dp: DeviceProfile,
-        @StagePosition stagePosition: Int
+        @StagePosition stagePosition: Int,
     ) {
         val insetAdjustment = getPlaceholderSizeAdjustment(dp) / 2f
         out.x = (onScreenRectCenterX / fullscreenScaleX - 1.0f * drawableWidth / 2)
@@ -393,7 +401,7 @@
         out: View,
         dp: DeviceProfile,
         splitInstructionsHeight: Int,
-        splitInstructionsWidth: Int
+        splitInstructionsWidth: Int,
     ) {
         out.pivotX = 0f
         out.pivotY = splitInstructionsHeight.toFloat()
@@ -421,7 +429,7 @@
         dp: DeviceProfile,
         @StagePosition stagePosition: Int,
         out1: Rect,
-        out2: Rect
+        out2: Rect,
     ) {
         // In fake land/seascape, the window bounds are always top and bottom half
         val screenHeight = dp.heightPx
@@ -434,17 +442,10 @@
         dp: DeviceProfile,
         outRect: Rect,
         splitInfo: SplitBounds,
-        desiredStagePosition: Int
+        desiredStagePosition: Int,
     ) {
-        val topLeftTaskPercent: Float
-        val dividerBarPercent: Float
-        if (splitInfo.appsStackedVertically) {
-            topLeftTaskPercent = splitInfo.topTaskPercent
-            dividerBarPercent = splitInfo.dividerHeightPercent
-        } else {
-            topLeftTaskPercent = splitInfo.leftTaskPercent
-            dividerBarPercent = splitInfo.dividerWidthPercent
-        }
+        val topLeftTaskPercent = splitInfo.leftTopTaskPercent
+        val dividerBarPercent = splitInfo.dividerPercent
 
         if (desiredStagePosition == STAGE_POSITION_TOP_OR_LEFT) {
             outRect.bottom = outRect.top + (outRect.height() * topLeftTaskPercent).toInt()
@@ -455,7 +456,7 @@
 
     /**
      * @param inSplitSelection Whether user currently has a task from this task group staged for
-     * split screen. Currently this state is not reachable in fake landscape.
+     *   split screen. Currently this state is not reachable in fake landscape.
      */
     override fun measureGroupedTaskViewThumbnailBounds(
         primarySnapshot: View,
@@ -465,7 +466,7 @@
         splitBoundsConfig: SplitBounds,
         dp: DeviceProfile,
         isRtl: Boolean,
-        inSplitSelection: Boolean
+        inSplitSelection: Boolean,
     ) {
         val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
         val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
@@ -486,13 +487,13 @@
         primarySnapshot.translationY = spaceAboveSnapshot.toFloat()
         primarySnapshot.measure(
             MeasureSpec.makeMeasureSpec(taskViewFirst.x, MeasureSpec.EXACTLY),
-            MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY)
+            MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY),
         )
         val translationY = taskViewFirst.y + spaceAboveSnapshot + dividerBar
         secondarySnapshot.translationY = (translationY - spaceAboveSnapshot).toFloat()
         secondarySnapshot.measure(
             MeasureSpec.makeMeasureSpec(taskViewSecond.x, MeasureSpec.EXACTLY),
-            MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY)
+            MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY),
         )
     }
 
@@ -500,18 +501,13 @@
         dp: DeviceProfile,
         splitBoundsConfig: SplitBounds,
         parentWidth: Int,
-        parentHeight: Int
+        parentHeight: Int,
     ): Pair<Point, Point> {
         val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
         val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
         val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig)
 
-        val taskPercent =
-            if (splitBoundsConfig.appsStackedVertically) {
-                splitBoundsConfig.topTaskPercent
-            } else {
-                splitBoundsConfig.leftTaskPercent
-            }
+        val taskPercent = splitBoundsConfig.leftTopTaskPercent
         val firstTaskViewSize = Point(parentWidth, (totalThumbnailHeight * taskPercent).toInt())
         val secondTaskViewSize =
             Point(parentWidth, totalThumbnailHeight - firstTaskViewSize.y - dividerBar)
@@ -523,7 +519,7 @@
         taskIconMargin: Int,
         taskIconHeight: Int,
         thumbnailTopMargin: Int,
-        isRtl: Boolean
+        isRtl: Boolean,
     ) {
         iconParams.gravity =
             if (isRtl) {
@@ -539,7 +535,7 @@
 
     override fun setIconAppChipChildrenParams(
         iconParams: FrameLayout.LayoutParams,
-        chipChildMarginStart: Int
+        chipChildMarginStart: Int,
     ) {
         iconParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
         iconParams.marginStart = chipChildMarginStart
@@ -550,7 +546,7 @@
         iconAppChipView: IconAppChipView,
         iconMenuParams: FrameLayout.LayoutParams,
         iconMenuMargin: Int,
-        thumbnailTopMargin: Int
+        thumbnailTopMargin: Int,
     ) {
         val isRtl = iconAppChipView.layoutDirection == View.LAYOUT_DIRECTION_RTL
 
@@ -576,7 +572,7 @@
 
     /**
      * @param inSplitSelection Whether user currently has a task from this task group staged for
-     * split screen. Currently this state is not reachable in fake landscape.
+     *   split screen. Currently this state is not reachable in fake landscape.
      */
     override fun setSplitIconParams(
         primaryIconView: View,
@@ -589,7 +585,8 @@
         isRtl: Boolean,
         deviceProfile: DeviceProfile,
         splitConfig: SplitBounds,
-        inSplitSelection: Boolean
+        inSplitSelection: Boolean,
+        oneIconHiddenDueToSmallWidth: Boolean,
     ) {
         val spaceAboveSnapshot = deviceProfile.overviewTaskThumbnailTopMarginPx
         val totalThumbnailHeight = groupedTaskViewHeight - spaceAboveSnapshot
@@ -602,7 +599,8 @@
                 totalThumbnailHeight,
                 isRtl,
                 deviceProfile.overviewTaskMarginPx,
-                dividerBar
+                dividerBar,
+                oneIconHiddenDueToSmallWidth,
             )
 
         updateSplitIconsPosition(primaryIconView, topLeftY, isRtl)
@@ -616,20 +614,20 @@
     override fun <T> getSplitSelectTaskOffset(
         primary: FloatProperty<T>,
         secondary: FloatProperty<T>,
-        deviceProfile: DeviceProfile
+        deviceProfile: DeviceProfile,
     ): Pair<FloatProperty<T>, FloatProperty<T>> = Pair(primary, secondary)
 
     override fun getFloatingTaskOffscreenTranslationTarget(
         floatingTask: View,
         onScreenRect: RectF,
         @StagePosition stagePosition: Int,
-        dp: DeviceProfile
+        dp: DeviceProfile,
     ): Float = floatingTask.translationY - onScreenRect.height()
 
     override fun setFloatingTaskPrimaryTranslation(
         floatingTask: View,
         translation: Float,
-        dp: DeviceProfile
+        dp: DeviceProfile,
     ) {
         floatingTask.translationY = translation
     }
@@ -659,6 +657,7 @@
         isRtl: Boolean,
         overviewTaskMarginPx: Int,
         dividerSize: Int,
+        oneIconHiddenDueToSmallWidth: Boolean,
     ): SplitIconPositions {
         return if (Flags.enableOverviewIconMenu()) {
             if (isRtl) {
@@ -667,11 +666,20 @@
                 SplitIconPositions(0, primarySnapshotHeight + dividerSize)
             }
         } else {
-            val topLeftY = primarySnapshotHeight + overviewTaskMarginPx
-            SplitIconPositions(
-                topLeftY = topLeftY,
-                bottomRightY = topLeftY + dividerSize + taskIconHeight
-            )
+            if (oneIconHiddenDueToSmallWidth) {
+                // Center both icons
+                val centerY =
+                    primarySnapshotHeight +
+                        overviewTaskMarginPx +
+                        ((taskIconHeight + dividerSize) / 2)
+                SplitIconPositions(topLeftY = centerY, bottomRightY = centerY)
+            } else {
+                val topLeftY = primarySnapshotHeight + overviewTaskMarginPx
+                SplitIconPositions(
+                    topLeftY = topLeftY,
+                    bottomRightY = topLeftY + dividerSize + taskIconHeight,
+                )
+            }
         }
     }
 
@@ -711,11 +719,7 @@
      * @return The divider size for the group task view.
      */
     protected fun getDividerBarSize(totalThumbnailHeight: Int, splitConfig: SplitBounds): Int {
-        return Math.round(
-            totalThumbnailHeight *
-                if (splitConfig.appsStackedVertically) splitConfig.dividerHeightPercent
-                else splitConfig.dividerWidthPercent
-        )
+        return Math.round(totalThumbnailHeight * splitConfig.dividerPercent)
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
deleted file mode 100644
index c0b697d..0000000
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.java
+++ /dev/null
@@ -1,845 +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.quickstep.orientation;
-
-import static android.view.Gravity.BOTTOM;
-import static android.view.Gravity.CENTER_HORIZONTAL;
-import static android.view.Gravity.END;
-import static android.view.Gravity.START;
-import static android.view.Gravity.TOP;
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import static com.android.launcher3.Flags.enableOverviewIconMenu;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
-import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
-import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
-
-import android.graphics.Matrix;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.drawable.ShapeDrawable;
-import android.util.FloatProperty;
-import android.util.Pair;
-import android.view.Gravity;
-import android.view.Surface;
-import android.view.View;
-import android.widget.FrameLayout;
-import android.widget.LinearLayout;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.logger.LauncherAtom;
-import com.android.launcher3.touch.DefaultPagedViewHandler;
-import com.android.launcher3.touch.SingleAxisSwipeDetector;
-import com.android.launcher3.util.SplitConfigurationOptions;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import com.android.quickstep.views.IconAppChipView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class PortraitPagedViewHandler extends DefaultPagedViewHandler implements
-        RecentsPagedOrientationHandler {
-
-    private final Matrix mTmpMatrix = new Matrix();
-    private final RectF mTmpRectF = new RectF();
-
-    @Override
-    public <T> T getPrimaryValue(T x, T y) {
-        return x;
-    }
-
-    @Override
-    public <T> T getSecondaryValue(T x, T y) {
-        return y;
-    }
-
-    @Override
-    public boolean isLayoutNaturalToLauncher() {
-        return true;
-    }
-
-    @Override
-    public void adjustFloatingIconStartVelocity(PointF velocity) {
-        //no-op
-    }
-
-    @Override
-    public void fixBoundsForHomeAnimStartRect(RectF outStartRect, DeviceProfile deviceProfile) {
-        if (outStartRect.left > deviceProfile.widthPx) {
-            outStartRect.offsetTo(0, outStartRect.top);
-        } else if (outStartRect.left < -deviceProfile.widthPx) {
-            outStartRect.offsetTo(0, outStartRect.top);
-        }
-    }
-
-    @Override
-    public <T> void setSecondary(T target, Float2DAction<T> action, float param) {
-        action.call(target, 0, param);
-    }
-
-    @Override
-    public <T> void set(T target, Int2DAction<T> action, int primaryParam,
-            int secondaryParam) {
-        action.call(target, primaryParam, secondaryParam);
-    }
-
-    @Override
-    public int getPrimarySize(View view) {
-        return view.getWidth();
-    }
-
-    @Override
-    public float getPrimarySize(RectF rect) {
-        return rect.width();
-    }
-
-    @Override
-    public float getStart(RectF rect) {
-        return rect.left;
-    }
-
-    @Override
-    public float getEnd(RectF rect) {
-        return rect.right;
-    }
-
-    @Override
-    public void rotateInsets(@NonNull Rect insets, @NonNull Rect outInsets) {
-        outInsets.set(insets);
-    }
-
-    @Override
-    public int getClearAllSidePadding(View view, boolean isRtl) {
-        return (isRtl ? view.getPaddingRight() : - view.getPaddingLeft()) / 2;
-    }
-
-    @Override
-    public int getSecondaryDimension(View view) {
-        return view.getHeight();
-    }
-
-    @Override
-    public FloatProperty<View> getPrimaryViewTranslate() {
-        return VIEW_TRANSLATE_X;
-    }
-
-    @Override
-    public FloatProperty<View> getSecondaryViewTranslate() {
-        return VIEW_TRANSLATE_Y;
-    }
-
-    @Override
-    public float getDegreesRotated() {
-        return 0;
-    }
-
-    @Override
-    public int getRotation() {
-        return Surface.ROTATION_0;
-    }
-
-    @Override
-    public void setPrimaryScale(View view, float scale) {
-        view.setScaleX(scale);
-    }
-
-    @Override
-    public void setSecondaryScale(View view, float scale) {
-        view.setScaleY(scale);
-    }
-
-    public int getSecondaryTranslationDirectionFactor() {
-        return -1;
-    }
-
-    @Override
-    public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) {
-        if (deviceProfile.isLeftRightSplit && stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) {
-            return -1;
-        } else {
-            return 1;
-        }
-    }
-
-    @Override
-    public float getTaskMenuX(float x, View thumbnailView,
-            DeviceProfile deviceProfile, float taskInsetMargin, View taskViewIcon) {
-        if (deviceProfile.isLandscape) {
-            return x + taskInsetMargin
-                    + (thumbnailView.getMeasuredWidth() - thumbnailView.getMeasuredHeight()) / 2f;
-        } else {
-            return x + taskInsetMargin;
-        }
-    }
-
-    @Override
-    public float getTaskMenuY(float y, View thumbnailView, int stagePosition,
-            View taskMenuView, float taskInsetMargin, View taskViewIcon) {
-        return y + taskInsetMargin;
-    }
-
-    @Override
-    public int getTaskMenuWidth(View thumbnailView, DeviceProfile deviceProfile,
-            @StagePosition int stagePosition) {
-        if (enableOverviewIconMenu()) {
-            return thumbnailView.getResources().getDimensionPixelSize(
-                    R.dimen.task_thumbnail_icon_menu_expanded_width);
-        }
-        int padding = thumbnailView.getResources()
-                .getDimensionPixelSize(R.dimen.task_menu_edge_padding);
-        return (deviceProfile.isLandscape && !deviceProfile.isTablet
-                ? thumbnailView.getMeasuredHeight()
-                : thumbnailView.getMeasuredWidth()) - (2 * padding);
-    }
-
-    @Override
-    public int getTaskMenuHeight(float taskInsetMargin, DeviceProfile deviceProfile,
-            float taskMenuX, float taskMenuY) {
-        return (int) (deviceProfile.heightPx - deviceProfile.getInsets().top - taskMenuY
-                    - deviceProfile.getOverviewActionsClaimedSpaceBelow());
-    }
-
-    @Override
-    public void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile,
-            LinearLayout taskMenuLayout, int dividerSpacing,
-            ShapeDrawable dividerDrawable) {
-        taskMenuLayout.setOrientation(LinearLayout.VERTICAL);
-        dividerDrawable.setIntrinsicHeight(dividerSpacing);
-        taskMenuLayout.setDividerDrawable(dividerDrawable);
-    }
-
-    @Override
-    public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp,
-            LinearLayout viewGroup, DeviceProfile deviceProfile) {
-        viewGroup.setOrientation(LinearLayout.HORIZONTAL);
-        lp.width = LinearLayout.LayoutParams.MATCH_PARENT;
-        lp.height = WRAP_CONTENT;
-    }
-
-    @Override
-    public void updateDwbBannerLayout(int taskViewWidth, int taskViewHeight,
-            boolean isGroupedTaskView, @NonNull DeviceProfile deviceProfile,
-            int snapshotViewWidth, int snapshotViewHeight, @NonNull View banner) {
-        FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams();
-        banner.setPivotX(0);
-        banner.setPivotY(0);
-        banner.setRotation(getDegreesRotated());
-        if (isGroupedTaskView) {
-            bannerParams.gravity =
-                    BOTTOM | (deviceProfile.isLeftRightSplit ? START : CENTER_HORIZONTAL);
-            bannerParams.width = snapshotViewWidth;
-        } else {
-            bannerParams.width = MATCH_PARENT;
-            bannerParams.gravity = BOTTOM | CENTER_HORIZONTAL;
-        }
-        banner.setLayoutParams(bannerParams);
-    }
-
-    @NonNull
-    @Override
-    public Pair<Float, Float> getDwbBannerTranslations(int taskViewWidth,
-            int taskViewHeight, SplitBounds splitBounds, @NonNull DeviceProfile deviceProfile,
-            @NonNull View[] thumbnailViews, int desiredTaskId, @NonNull View banner) {
-        float translationX = 0;
-        float translationY = 0;
-        if (splitBounds != null) {
-            if (deviceProfile.isLeftRightSplit) {
-                if (desiredTaskId == splitBounds.rightBottomTaskId) {
-                    float leftTopTaskPercent = splitBounds.appsStackedVertically
-                            ? splitBounds.topTaskPercent
-                            : splitBounds.leftTaskPercent;
-                    float dividerThicknessPercent = splitBounds.appsStackedVertically
-                            ? splitBounds.dividerHeightPercent
-                            : splitBounds.dividerWidthPercent;
-                    translationX = ((taskViewWidth * leftTopTaskPercent)
-                            + (taskViewWidth * dividerThicknessPercent));
-                }
-            } else {
-                if (desiredTaskId == splitBounds.leftTopTaskId) {
-                    FrameLayout.LayoutParams snapshotParams =
-                            (FrameLayout.LayoutParams) thumbnailViews[0]
-                                    .getLayoutParams();
-                    float bottomRightTaskPlusDividerPercent = splitBounds.appsStackedVertically
-                            ? (1f - splitBounds.topTaskPercent)
-                            : (1f - splitBounds.leftTaskPercent);
-                    translationY = -((taskViewHeight - snapshotParams.topMargin)
-                            * bottomRightTaskPlusDividerPercent);
-                }
-            }
-        }
-        return new Pair<>(translationX, translationY);
-    }
-
-    /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
-
-    @Override
-    public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() {
-        return VERTICAL;
-    }
-
-    @Override
-    public int getUpDirection(boolean isRtl) {
-        // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
-        return SingleAxisSwipeDetector.DIRECTION_POSITIVE;
-    }
-
-    @Override
-    public boolean isGoingUp(float displacement, boolean isRtl) {
-        // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
-        return displacement < 0;
-    }
-
-    @Override
-    public int getTaskDragDisplacementFactor(boolean isRtl) {
-        // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
-        return 1;
-    }
-
-    /* -------------------- */
-    @Override
-    public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) {
-        return dp.heightPx - rect.bottom;
-    }
-
-    @Override
-    public List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp) {
-        if (dp.isTablet) {
-            return Utilities.getSplitPositionOptions(dp);
-        }
-
-        List<SplitPositionOption> options = new ArrayList<>();
-        if (dp.isSeascape()) {
-            options.add(new SplitPositionOption(
-                    R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
-                    STAGE_POSITION_BOTTOM_OR_RIGHT, STAGE_TYPE_MAIN));
-        } else if (dp.isLeftRightSplit) {
-            options.add(new SplitPositionOption(
-                    R.drawable.ic_split_horizontal, R.string.recent_task_option_split_screen,
-                    STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
-        } else {
-            // Only add top option
-            options.add(new SplitPositionOption(
-                    R.drawable.ic_split_vertical, R.string.recent_task_option_split_screen,
-                    STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN));
-        }
-        return options;
-    }
-
-    @Override
-    public void getInitialSplitPlaceholderBounds(int placeholderHeight, int placeholderInset,
-            DeviceProfile dp, @StagePosition int stagePosition, Rect out) {
-        int screenWidth = dp.widthPx;
-        int screenHeight = dp.heightPx;
-        boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
-        int insetSizeAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight);
-
-        out.set(0, 0, screenWidth, placeholderHeight + insetSizeAdjustment);
-        if (!dp.isLeftRightSplit) {
-            // portrait, phone or tablet - spans width of screen, nothing else to do
-            out.inset(placeholderInset, 0);
-
-            // Adjust the top to account for content off screen. This will help to animate the view
-            // in with rounded corners.
-            int totalHeight = (int) (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset)
-                    / screenWidth);
-            out.top -= (totalHeight - placeholderHeight);
-            return;
-        }
-
-        // Now we rotate the portrait rect depending on what side we want pinned
-
-        float postRotateScale = (float) screenHeight / screenWidth;
-        mTmpMatrix.reset();
-        mTmpMatrix.postRotate(pinToRight ? 90 : 270);
-        mTmpMatrix.postTranslate(pinToRight ? screenWidth : 0, pinToRight ? 0 : screenWidth);
-        // The placeholder height stays constant after rotation, so we don't change width scale
-        mTmpMatrix.postScale(1, postRotateScale);
-
-        mTmpRectF.set(out);
-        mTmpMatrix.mapRect(mTmpRectF);
-        mTmpRectF.inset(0, placeholderInset);
-        mTmpRectF.roundOut(out);
-
-        // Adjust the top to account for content off screen. This will help to animate the view in
-        // with rounded corners.
-        int totalWidth = (int) (1.0f * screenWidth / 2 * (screenHeight - 2 * placeholderInset)
-                / screenHeight);
-        int width = out.width();
-        if (pinToRight) {
-            out.right += totalWidth - width;
-        } else {
-            out.left -= totalWidth - width;
-        }
-    }
-
-    @Override
-    public void updateSplitIconParams(View out, float onScreenRectCenterX,
-            float onScreenRectCenterY, float fullscreenScaleX, float fullscreenScaleY,
-            int drawableWidth, int drawableHeight, DeviceProfile dp,
-            @StagePosition int stagePosition) {
-        boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
-        float insetAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight) / 2f;
-        if (!dp.isLeftRightSplit) {
-            out.setX(onScreenRectCenterX / fullscreenScaleX
-                    - 1.0f * drawableWidth / 2);
-            out.setY((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY
-                    - 1.0f * drawableHeight / 2);
-        } else {
-            if (pinToRight) {
-                out.setX((onScreenRectCenterX - insetAdjustment) / fullscreenScaleX
-                        - 1.0f * drawableWidth / 2);
-            } else {
-                out.setX((onScreenRectCenterX + insetAdjustment) / fullscreenScaleX
-                        - 1.0f * drawableWidth / 2);
-            }
-            out.setY(onScreenRectCenterY / fullscreenScaleY
-                    - 1.0f * drawableHeight / 2);
-        }
-    }
-
-    /**
-     * The split placeholder comes with a default inset to buffer the icon from the top of the
-     * screen. But if the device already has a large inset (from cutouts etc), use that instead.
-     */
-    private int getPlaceholderSizeAdjustment(DeviceProfile dp, boolean pinToRight) {
-        int insetThickness;
-        if (!dp.isLandscape) {
-            insetThickness = dp.getInsets().top;
-        } else {
-            insetThickness = pinToRight ? dp.getInsets().right : dp.getInsets().left;
-        }
-        return Math.max(insetThickness - dp.splitPlaceholderInset, 0);
-    }
-
-    @Override
-    public void setSplitInstructionsParams(View out, DeviceProfile dp, int splitInstructionsHeight,
-            int splitInstructionsWidth) {
-        out.setPivotX(0);
-        out.setPivotY(splitInstructionsHeight);
-        out.setRotation(getDegreesRotated());
-        int distanceToEdge;
-        if (dp.isPhone) {
-            if (dp.isLandscape) {
-                distanceToEdge = out.getResources().getDimensionPixelSize(
-                        R.dimen.split_instructions_bottom_margin_phone_landscape);
-            } else {
-                distanceToEdge = out.getResources().getDimensionPixelSize(
-                        R.dimen.split_instructions_bottom_margin_phone_portrait);
-            }
-        } else {
-            distanceToEdge = dp.getOverviewActionsClaimedSpaceBelow();
-        }
-
-        // Center the view in case of unbalanced insets on left or right of screen
-        int insetCorrectionX = (dp.getInsets().right - dp.getInsets().left) / 2;
-        // Adjust for any insets on the bottom edge
-        int insetCorrectionY = dp.getInsets().bottom;
-        out.setTranslationX(insetCorrectionX);
-        out.setTranslationY(-distanceToEdge + insetCorrectionY);
-        FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) out.getLayoutParams();
-        lp.gravity = CENTER_HORIZONTAL | BOTTOM;
-        out.setLayoutParams(lp);
-    }
-
-    @Override
-    public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
-            @StagePosition int stagePosition, Rect out1, Rect out2) {
-        int screenHeight = dp.heightPx;
-        int screenWidth = dp.widthPx;
-        out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize);
-        out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight);
-        if (!dp.isLeftRightSplit) {
-            // Portrait - the window bounds are always top and bottom half
-            return;
-        }
-
-        // Now we rotate the portrait rect depending on what side we want pinned
-        boolean pinToRight = stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
-        float postRotateScale = (float) screenHeight / screenWidth;
-
-        mTmpMatrix.reset();
-        mTmpMatrix.postRotate(pinToRight ? 90 : 270);
-        mTmpMatrix.postTranslate(pinToRight ? screenHeight : 0, pinToRight ? 0 : screenWidth);
-        mTmpMatrix.postScale(1 / postRotateScale, postRotateScale);
-
-        mTmpRectF.set(out1);
-        mTmpMatrix.mapRect(mTmpRectF);
-        mTmpRectF.roundOut(out1);
-
-        mTmpRectF.set(out2);
-        mTmpMatrix.mapRect(mTmpRectF);
-        mTmpRectF.roundOut(out2);
-    }
-
-    @Override
-    public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
-            SplitBounds splitInfo, int desiredStagePosition) {
-        float topLeftTaskPercent = splitInfo.appsStackedVertically
-                ? splitInfo.topTaskPercent
-                : splitInfo.leftTaskPercent;
-        float dividerBarPercent = splitInfo.appsStackedVertically
-                ? splitInfo.dividerHeightPercent
-                : splitInfo.dividerWidthPercent;
-
-        int taskbarHeight = dp.isTransientTaskbar ? 0 : dp.taskbarHeight;
-        float scale = (float) outRect.height() / (dp.availableHeightPx - taskbarHeight);
-        float topTaskHeight = dp.availableHeightPx * topLeftTaskPercent;
-        float scaledTopTaskHeight = topTaskHeight * scale;
-        float dividerHeight = dp.availableHeightPx * dividerBarPercent;
-        float scaledDividerHeight = dividerHeight * scale;
-
-        if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
-            if (dp.isLeftRightSplit) {
-                outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent);
-            } else {
-                outRect.bottom = Math.round(outRect.top + scaledTopTaskHeight);
-            }
-        } else {
-            if (dp.isLeftRightSplit) {
-                outRect.left += Math.round(outRect.width()
-                        * (topLeftTaskPercent + dividerBarPercent));
-            } else {
-                outRect.top += Math.round(scaledTopTaskHeight + scaledDividerHeight);
-            }
-        }
-    }
-
-    /**
-     * @param inSplitSelection Whether user currently has a task from this task group staged for
-     *                         split screen. If true, we have custom translations/scaling in place
-     *                         for the remaining snapshot, so we'll skip setting translation/scale
-     *                         here.
-     */
-    @Override
-    public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
-            int parentWidth, int parentHeight, SplitBounds splitBoundsConfig,
-            DeviceProfile dp, boolean isRtl, boolean inSplitSelection) {
-        int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
-
-        FrameLayout.LayoutParams primaryParams =
-                (FrameLayout.LayoutParams) primarySnapshot.getLayoutParams();
-        FrameLayout.LayoutParams secondaryParams =
-                (FrameLayout.LayoutParams) secondarySnapshot.getLayoutParams();
-
-        // Reset margins that aren't used in this method, but are used in other
-        // `RecentsPagedOrientationHandler` variants.
-        secondaryParams.topMargin = 0;
-        primaryParams.topMargin = spaceAboveSnapshot;
-
-        int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
-        float dividerScale = splitBoundsConfig.appsStackedVertically
-                ? splitBoundsConfig.dividerHeightPercent
-                : splitBoundsConfig.dividerWidthPercent;
-        Pair<Point, Point> taskViewSizes =
-                getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight);
-        if (!inSplitSelection) {
-            // Reset translations that aren't used in this method, but are used in other
-            // `RecentsPagedOrientationHandler` variants.
-            primarySnapshot.setTranslationY(0);
-
-            if (dp.isLeftRightSplit) {
-                int scaledDividerBar = Math.round(parentWidth * dividerScale);
-                if (isRtl) {
-                    int translationX = taskViewSizes.second.x + scaledDividerBar;
-                    primarySnapshot.setTranslationX(-translationX);
-                    secondarySnapshot.setTranslationX(0);
-                } else {
-                    int translationX = taskViewSizes.first.x + scaledDividerBar;
-                    secondarySnapshot.setTranslationX(translationX);
-                    primarySnapshot.setTranslationX(0);
-                }
-                secondarySnapshot.setTranslationY(spaceAboveSnapshot);
-            } else {
-                float finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale);
-                float translationY =
-                        taskViewSizes.first.y + spaceAboveSnapshot + finalDividerHeight;
-                secondarySnapshot.setTranslationY(translationY);
-
-                // Reset unused translations.
-                secondarySnapshot.setTranslationX(0);
-                primarySnapshot.setTranslationX(0);
-            }
-        }
-
-        primarySnapshot.measure(
-                View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.x, View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.y, View.MeasureSpec.EXACTLY));
-        secondarySnapshot.measure(
-                View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.x, View.MeasureSpec.EXACTLY),
-                View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.y,
-                        View.MeasureSpec.EXACTLY));
-    }
-
-    @Override
-    public Pair<Point, Point> getGroupedTaskViewSizes(
-            DeviceProfile dp,
-            SplitBounds splitBoundsConfig,
-            int parentWidth,
-            int parentHeight) {
-        int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
-        int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
-        float dividerScale = splitBoundsConfig.appsStackedVertically
-                ? splitBoundsConfig.dividerHeightPercent
-                : splitBoundsConfig.dividerWidthPercent;
-        float taskPercent = splitBoundsConfig.appsStackedVertically
-                ? splitBoundsConfig.topTaskPercent
-                : splitBoundsConfig.leftTaskPercent;
-
-        Point firstTaskViewSize = new Point();
-        Point secondTaskViewSize = new Point();
-
-        if (dp.isLeftRightSplit) {
-            int scaledDividerBar = Math.round(parentWidth * dividerScale);
-            firstTaskViewSize.x = Math.round(parentWidth * taskPercent);
-            firstTaskViewSize.y = totalThumbnailHeight;
-
-            secondTaskViewSize.x = parentWidth - firstTaskViewSize.x - scaledDividerBar;
-            secondTaskViewSize.y = totalThumbnailHeight;
-        } else {
-            int taskbarHeight = dp.isTransientTaskbar ? 0 : dp.taskbarHeight;
-            float scale = (float) totalThumbnailHeight / (dp.availableHeightPx - taskbarHeight);
-            float topTaskHeight = dp.availableHeightPx * taskPercent;
-            float finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale);
-            float scaledTopTaskHeight = topTaskHeight * scale;
-            firstTaskViewSize.x = parentWidth;
-            firstTaskViewSize.y = Math.round(scaledTopTaskHeight);
-
-            secondTaskViewSize.x = parentWidth;
-            secondTaskViewSize.y = Math.round(totalThumbnailHeight - firstTaskViewSize.y
-                    - finalDividerHeight);
-        }
-
-        return new Pair<>(firstTaskViewSize, secondTaskViewSize);
-    }
-
-    @Override
-    public void setTaskIconParams(FrameLayout.LayoutParams iconParams, int taskIconMargin,
-            int taskIconHeight, int thumbnailTopMargin, boolean isRtl) {
-        iconParams.gravity = TOP | CENTER_HORIZONTAL;
-        // Reset margins, since they may have been set on rotation
-        iconParams.leftMargin = iconParams.rightMargin = 0;
-        iconParams.topMargin = iconParams.bottomMargin = 0;
-    }
-
-    @Override
-    public void setIconAppChipChildrenParams(FrameLayout.LayoutParams iconParams,
-            int chipChildMarginStart) {
-        iconParams.setMarginStart(chipChildMarginStart);
-        iconParams.gravity = Gravity.START | Gravity.CENTER_VERTICAL;
-        iconParams.topMargin = 0;
-    }
-
-    @Override
-    public void setIconAppChipMenuParams(IconAppChipView iconAppChipView,
-            FrameLayout.LayoutParams iconMenuParams, int iconMenuMargin, int thumbnailTopMargin) {
-        iconMenuParams.gravity = TOP | START;
-        iconMenuParams.setMarginStart(iconMenuMargin);
-        iconMenuParams.topMargin = thumbnailTopMargin;
-        iconMenuParams.bottomMargin = 0;
-        iconMenuParams.setMarginEnd(0);
-
-        iconAppChipView.setPivotX(0);
-        iconAppChipView.setPivotY(0);
-        iconAppChipView.setSplitTranslationY(0);
-        iconAppChipView.setRotation(getDegreesRotated());
-    }
-
-    /**
-     * @param inSplitSelection Whether user currently has a task from this task group staged for
-     *                         split screen. If true, we have custom translations in place for the
-     *                         remaining icon, so we'll skip setting translations here.
-     */
-    @Override
-    public void setSplitIconParams(View primaryIconView, View secondaryIconView,
-            int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight,
-            int groupedTaskViewHeight, int groupedTaskViewWidth, boolean isRtl,
-            DeviceProfile deviceProfile, SplitBounds splitConfig, boolean inSplitSelection) {
-        FrameLayout.LayoutParams primaryIconParams =
-                (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
-        FrameLayout.LayoutParams secondaryIconParams = enableOverviewIconMenu()
-                ? (FrameLayout.LayoutParams) secondaryIconView.getLayoutParams()
-                : new FrameLayout.LayoutParams(primaryIconParams);
-
-        if (enableOverviewIconMenu()) {
-            IconAppChipView primaryAppChipView = (IconAppChipView) primaryIconView;
-            IconAppChipView secondaryAppChipView = (IconAppChipView) secondaryIconView;
-            primaryIconParams.gravity = TOP | START;
-            secondaryIconParams.gravity = TOP | START;
-            secondaryIconParams.topMargin = primaryIconParams.topMargin;
-            secondaryIconParams.setMarginStart(primaryIconParams.getMarginStart());
-            if (!inSplitSelection) {
-                if (deviceProfile.isLeftRightSplit) {
-                    if (isRtl) {
-                        int secondarySnapshotWidth = groupedTaskViewWidth - primarySnapshotWidth;
-                        primaryAppChipView.setSplitTranslationX(-secondarySnapshotWidth);
-                    } else {
-                        secondaryAppChipView.setSplitTranslationX(primarySnapshotWidth);
-                    }
-                } else {
-                    primaryAppChipView.setSplitTranslationX(0);
-                    secondaryAppChipView.setSplitTranslationX(0);
-                    int dividerThickness = Math.min(splitConfig.visualDividerBounds.width(),
-                            splitConfig.visualDividerBounds.height());
-                    secondaryAppChipView.setSplitTranslationY(
-                            primarySnapshotHeight + (deviceProfile.isTablet ? 0
-                                    : dividerThickness));
-                }
-            }
-        } else if (deviceProfile.isLeftRightSplit) {
-            // We calculate the "midpoint" of the thumbnail area, and place the icons there.
-            // This is the place where the thumbnail area splits by default, in a near-50/50 split.
-            // It is usually not exactly 50/50, due to insets/screen cutouts.
-            int fullscreenInsetThickness = deviceProfile.isSeascape()
-                    ? deviceProfile.getInsets().right
-                    : deviceProfile.getInsets().left;
-            int fullscreenMidpointFromBottom = ((deviceProfile.widthPx
-                    - fullscreenInsetThickness) / 2);
-            float midpointFromEndPct = (float) fullscreenMidpointFromBottom
-                    / deviceProfile.widthPx;
-            float insetPct = (float) fullscreenInsetThickness / deviceProfile.widthPx;
-            int spaceAboveSnapshots = 0;
-            int overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots;
-            int bottomToMidpointOffset = (int) (overviewThumbnailAreaThickness
-                    * midpointFromEndPct);
-            int insetOffset = (int) (overviewThumbnailAreaThickness * insetPct);
-
-            if (deviceProfile.isSeascape()) {
-                primaryIconParams.gravity = TOP | (isRtl ? END : START);
-                secondaryIconParams.gravity = TOP | (isRtl ? END : START);
-                if (!inSplitSelection) {
-                    if (splitConfig.initiatedFromSeascape) {
-                        // if the split was initiated from seascape,
-                        // the task on the right (secondary) is slightly larger
-                        primaryIconView.setTranslationX(bottomToMidpointOffset - taskIconHeight);
-                        secondaryIconView.setTranslationX(bottomToMidpointOffset);
-                    } else {
-                        // if not,
-                        // the task on the left (primary) is slightly larger
-                        primaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset
-                                - taskIconHeight);
-                        secondaryIconView.setTranslationX(bottomToMidpointOffset + insetOffset);
-                    }
-                }
-            } else {
-                primaryIconParams.gravity = TOP | (isRtl ? START : END);
-                secondaryIconParams.gravity = TOP | (isRtl ? START : END);
-                if (!inSplitSelection) {
-                    if (!splitConfig.initiatedFromSeascape) {
-                        // if the split was initiated from landscape,
-                        // the task on the left (primary) is slightly larger
-                        primaryIconView.setTranslationX(-bottomToMidpointOffset);
-                        secondaryIconView.setTranslationX(-bottomToMidpointOffset + taskIconHeight);
-                    } else {
-                        // if not,
-                        // the task on the right (secondary) is slightly larger
-                        primaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset);
-                        secondaryIconView.setTranslationX(-bottomToMidpointOffset - insetOffset
-                                + taskIconHeight);
-                    }
-                }
-            }
-        } else {
-            primaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
-            secondaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
-            if (!inSplitSelection) {
-                // shifts icon half a width left (height is used here since icons are square)
-                primaryIconView.setTranslationX(-(taskIconHeight / 2f));
-                secondaryIconView.setTranslationX(taskIconHeight / 2f);
-            }
-        }
-        if (!enableOverviewIconMenu() && !inSplitSelection) {
-            primaryIconView.setTranslationY(0);
-            secondaryIconView.setTranslationY(0);
-        }
-
-
-        primaryIconView.setLayoutParams(primaryIconParams);
-        secondaryIconView.setLayoutParams(secondaryIconParams);
-    }
-
-    @Override
-    public int getDefaultSplitPosition(DeviceProfile deviceProfile) {
-        if (!deviceProfile.isTablet) {
-            throw new IllegalStateException("Default position available only for large screens");
-        }
-        if (deviceProfile.isLeftRightSplit) {
-            return STAGE_POSITION_BOTTOM_OR_RIGHT;
-        } else {
-            return STAGE_POSITION_TOP_OR_LEFT;
-        }
-    }
-
-    @Override
-    public Pair<FloatProperty, FloatProperty> getSplitSelectTaskOffset(FloatProperty primary,
-            FloatProperty secondary, DeviceProfile deviceProfile) {
-        if (deviceProfile.isLeftRightSplit) { // or seascape
-            return new Pair<>(primary, secondary);
-        } else {
-            return new Pair<>(secondary, primary);
-        }
-    }
-
-    @Override
-    public float getFloatingTaskOffscreenTranslationTarget(View floatingTask, RectF onScreenRect,
-            @StagePosition int stagePosition, DeviceProfile dp) {
-        if (dp.isLeftRightSplit) {
-            float currentTranslationX = floatingTask.getTranslationX();
-            return stagePosition == STAGE_POSITION_TOP_OR_LEFT
-                    ? currentTranslationX - onScreenRect.width()
-                    : currentTranslationX + onScreenRect.width();
-        } else {
-            float currentTranslationY = floatingTask.getTranslationY();
-            return currentTranslationY - onScreenRect.height();
-        }
-    }
-
-    @Override
-    public void setFloatingTaskPrimaryTranslation(View floatingTask, float translation,
-            DeviceProfile dp) {
-        if (dp.isLeftRightSplit) {
-            floatingTask.setTranslationX(translation);
-        } else {
-            floatingTask.setTranslationY(translation);
-        }
-
-    }
-
-    @Override
-    public float getFloatingTaskPrimaryTranslation(View floatingTask, DeviceProfile dp) {
-        return dp.isLeftRightSplit
-                ? floatingTask.getTranslationX()
-                : floatingTask.getTranslationY();
-    }
-
-    @NonNull
-    @Override
-    public LauncherAtom.TaskSwitcherContainer.OrientationHandler getHandlerTypeForLogging() {
-        return LauncherAtom.TaskSwitcherContainer.OrientationHandler.PORTRAIT;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
new file mode 100644
index 0000000..15eb69e
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
@@ -0,0 +1,915 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.orientation
+
+import android.graphics.Matrix
+import android.graphics.Point
+import android.graphics.PointF
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.drawable.ShapeDrawable
+import android.util.FloatProperty
+import android.util.Pair
+import android.view.Gravity
+import android.view.Surface
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.LinearLayout
+import androidx.core.view.updateLayoutParams
+import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.LauncherAnimUtils
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.logger.LauncherAtom
+import com.android.launcher3.touch.DefaultPagedViewHandler
+import com.android.launcher3.touch.PagedOrientationHandler.Float2DAction
+import com.android.launcher3.touch.PagedOrientationHandler.Int2DAction
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
+import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
+import com.android.quickstep.views.IconAppChipView
+import kotlin.math.max
+import kotlin.math.min
+
+class PortraitPagedViewHandler : DefaultPagedViewHandler(), RecentsPagedOrientationHandler {
+    private val tmpMatrix = Matrix()
+    private val tmpRectF = RectF()
+
+    override fun <T> getPrimaryValue(x: T, y: T): T = x
+
+    override fun <T> getSecondaryValue(x: T, y: T): T = y
+
+    override val isLayoutNaturalToLauncher: Boolean = true
+
+    override fun adjustFloatingIconStartVelocity(velocity: PointF) {
+        // no-op
+    }
+
+    override fun fixBoundsForHomeAnimStartRect(outStartRect: RectF, deviceProfile: DeviceProfile) {
+        if (outStartRect.left > deviceProfile.widthPx) {
+            outStartRect.offsetTo(0f, outStartRect.top)
+        } else if (outStartRect.left < -deviceProfile.widthPx) {
+            outStartRect.offsetTo(0f, outStartRect.top)
+        }
+    }
+
+    override fun <T> setSecondary(target: T, action: Float2DAction<T>, param: Float) =
+        action.call(target, 0f, param)
+
+    override fun <T> set(
+        target: T,
+        action: Int2DAction<T>,
+        primaryParam: Int,
+        secondaryParam: Int,
+    ) = action.call(target, primaryParam, secondaryParam)
+
+    override fun getPrimarySize(view: View): Int = view.width
+
+    override fun getPrimarySize(rect: RectF): Float = rect.width()
+
+    override fun getStart(rect: RectF): Float = rect.left
+
+    override fun getEnd(rect: RectF): Float = rect.right
+
+    override fun rotateInsets(insets: Rect, outInsets: Rect) = outInsets.set(insets)
+
+    override fun getClearAllSidePadding(view: View, isRtl: Boolean): Int =
+        (if (isRtl) view.paddingRight else -view.paddingLeft) / 2
+
+    override fun getSecondaryDimension(view: View): Int = view.height
+
+    override val primaryViewTranslate: FloatProperty<View> = LauncherAnimUtils.VIEW_TRANSLATE_X
+
+    override val secondaryViewTranslate: FloatProperty<View> = LauncherAnimUtils.VIEW_TRANSLATE_Y
+
+    override val degreesRotated: Float = 0f
+
+    override val rotation: Int = Surface.ROTATION_0
+
+    override fun setPrimaryScale(view: View, scale: Float) {
+        view.scaleX = scale
+    }
+
+    override fun setSecondaryScale(view: View, scale: Float) {
+        view.scaleY = scale
+    }
+
+    override val secondaryTranslationDirectionFactor: Int
+        get() = -1
+
+    override fun getSplitTranslationDirectionFactor(
+        stagePosition: Int,
+        deviceProfile: DeviceProfile,
+    ): Int =
+        if (
+            deviceProfile.isLeftRightSplit &&
+                stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+        ) {
+            -1
+        } else {
+            1
+        }
+
+    override fun getTaskMenuX(
+        x: Float,
+        thumbnailView: View,
+        deviceProfile: DeviceProfile,
+        taskInsetMargin: Float,
+        taskViewIcon: View,
+    ): Float =
+        if (deviceProfile.isLandscape) {
+            (x +
+                taskInsetMargin +
+                (thumbnailView.measuredWidth - thumbnailView.measuredHeight) / 2f)
+        } else {
+            x + taskInsetMargin
+        }
+
+    override fun getTaskMenuY(
+        y: Float,
+        thumbnailView: View,
+        stagePosition: Int,
+        taskMenuView: View,
+        taskInsetMargin: Float,
+        taskViewIcon: View,
+    ): Float = y + taskInsetMargin
+
+    override fun getTaskMenuWidth(
+        thumbnailView: View,
+        deviceProfile: DeviceProfile,
+        @StagePosition stagePosition: Int,
+    ): Int =
+        when {
+            enableOverviewIconMenu() -> {
+                thumbnailView.resources.getDimensionPixelSize(
+                    R.dimen.task_thumbnail_icon_menu_expanded_width
+                )
+            }
+
+            (deviceProfile.isLandscape && !deviceProfile.isTablet) -> {
+                val padding =
+                    thumbnailView.resources.getDimensionPixelSize(R.dimen.task_menu_edge_padding)
+                thumbnailView.measuredHeight - (2 * padding)
+            }
+
+            else -> {
+                val padding =
+                    thumbnailView.resources.getDimensionPixelSize(R.dimen.task_menu_edge_padding)
+                thumbnailView.measuredWidth - (2 * padding)
+            }
+        }
+
+    override fun getTaskMenuHeight(
+        taskInsetMargin: Float,
+        deviceProfile: DeviceProfile,
+        taskMenuX: Float,
+        taskMenuY: Float,
+    ): Int =
+        deviceProfile.heightPx -
+            deviceProfile.insets.top -
+            taskMenuY.toInt() -
+            deviceProfile.overviewActionsClaimedSpaceBelow
+
+    override fun setTaskOptionsMenuLayoutOrientation(
+        deviceProfile: DeviceProfile,
+        taskMenuLayout: LinearLayout,
+        dividerSpacing: Int,
+        dividerDrawable: ShapeDrawable,
+    ) {
+        taskMenuLayout.orientation = LinearLayout.VERTICAL
+        dividerDrawable.intrinsicHeight = dividerSpacing
+        taskMenuLayout.dividerDrawable = dividerDrawable
+    }
+
+    override fun setLayoutParamsForTaskMenuOptionItem(
+        lp: LinearLayout.LayoutParams,
+        viewGroup: LinearLayout,
+        deviceProfile: DeviceProfile,
+    ) {
+        viewGroup.orientation = LinearLayout.HORIZONTAL
+        lp.width = LinearLayout.LayoutParams.MATCH_PARENT
+        lp.height = ViewGroup.LayoutParams.WRAP_CONTENT
+    }
+
+    override fun updateDwbBannerLayout(
+        taskViewWidth: Int,
+        taskViewHeight: Int,
+        isGroupedTaskView: Boolean,
+        deviceProfile: DeviceProfile,
+        snapshotViewWidth: Int,
+        snapshotViewHeight: Int,
+        banner: View,
+    ) {
+        banner.pivotX = 0f
+        banner.pivotY = 0f
+        banner.rotation = degreesRotated
+        banner.updateLayoutParams<FrameLayout.LayoutParams> {
+            if (isGroupedTaskView) {
+                gravity =
+                    Gravity.BOTTOM or
+                        (if (deviceProfile.isLeftRightSplit) Gravity.START
+                        else Gravity.CENTER_HORIZONTAL)
+                width = snapshotViewWidth
+            } else {
+                width = ViewGroup.LayoutParams.MATCH_PARENT
+                gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
+            }
+        }
+    }
+
+    override fun getDwbBannerTranslations(
+        taskViewWidth: Int,
+        taskViewHeight: Int,
+        splitBounds: SplitConfigurationOptions.SplitBounds?,
+        deviceProfile: DeviceProfile,
+        thumbnailViews: Array<View>,
+        desiredTaskId: Int,
+        banner: View,
+    ): Pair<Float, Float> {
+        var translationX = 0f
+        var translationY = 0f
+        if (splitBounds != null) {
+            if (deviceProfile.isLeftRightSplit) {
+                if (desiredTaskId == splitBounds.rightBottomTaskId) {
+                    val leftTopTaskPercent = splitBounds.leftTopTaskPercent
+                    val dividerThicknessPercent = splitBounds.dividerPercent
+                    translationX =
+                        ((taskViewWidth * leftTopTaskPercent) +
+                            (taskViewWidth * dividerThicknessPercent))
+                }
+            } else {
+                if (desiredTaskId == splitBounds.leftTopTaskId) {
+                    val snapshotParams = thumbnailViews[0].layoutParams as LinearLayout.LayoutParams
+                    val bottomRightTaskPlusDividerPercent =
+                        (splitBounds.rightBottomTaskPercent + splitBounds.dividerPercent)
+                    translationY =
+                        -((taskViewHeight - snapshotParams.topMargin) *
+                            bottomRightTaskPlusDividerPercent)
+                }
+            }
+        }
+        return Pair(translationX, translationY)
+    }
+
+    /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
+
+    override val upDownSwipeDirection: SingleAxisSwipeDetector.Direction =
+        SingleAxisSwipeDetector.VERTICAL
+
+    // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+    override fun getUpDirection(isRtl: Boolean): Int = SingleAxisSwipeDetector.DIRECTION_POSITIVE
+
+    // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+    override fun getDownDirection(isRtl: Boolean): Int = SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+
+    // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+    override fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean = displacement < 0
+
+    // Ignore rtl since it only affects X value displacement, Y displacement doesn't change
+    override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = 1
+
+    override fun getTaskDismissVerticalDirection(): Int = -1
+
+    override fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+        taskThumbnailBounds.bottom
+
+    override fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+        secondaryDimension - taskThumbnailBounds.bottom
+
+    /* -------------------- */
+
+    override fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int =
+        dp.heightPx - rect.bottom
+
+    override fun getSplitPositionOptions(dp: DeviceProfile): List<SplitPositionOption> =
+        when {
+            dp.isTablet -> {
+                Utilities.getSplitPositionOptions(dp)
+            }
+
+            dp.isSeascape -> {
+                listOf(
+                    SplitPositionOption(
+                        R.drawable.ic_split_horizontal,
+                        R.string.recent_task_option_split_screen,
+                        SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT,
+                        SplitConfigurationOptions.STAGE_TYPE_MAIN,
+                    )
+                )
+            }
+
+            dp.isLeftRightSplit -> {
+                listOf(
+                    SplitPositionOption(
+                        R.drawable.ic_split_horizontal,
+                        R.string.recent_task_option_split_screen,
+                        SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+                        SplitConfigurationOptions.STAGE_TYPE_MAIN,
+                    )
+                )
+            }
+
+            else -> {
+                // Only add top option
+                listOf(
+                    SplitPositionOption(
+                        R.drawable.ic_split_vertical,
+                        R.string.recent_task_option_split_screen,
+                        SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
+                        SplitConfigurationOptions.STAGE_TYPE_MAIN,
+                    )
+                )
+            }
+        }
+
+    override fun getInitialSplitPlaceholderBounds(
+        placeholderHeight: Int,
+        placeholderInset: Int,
+        dp: DeviceProfile,
+        @StagePosition stagePosition: Int,
+        out: Rect,
+    ) {
+        val screenWidth = dp.widthPx
+        val screenHeight = dp.heightPx
+        val pinToRight = stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+        val insetSizeAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight)
+
+        out.set(0, 0, screenWidth, placeholderHeight + insetSizeAdjustment)
+        if (!dp.isLeftRightSplit) {
+            // portrait, phone or tablet - spans width of screen, nothing else to do
+            out.inset(placeholderInset, 0)
+
+            // Adjust the top to account for content off screen. This will help to animate the view
+            // in with rounded corners.
+            val totalHeight =
+                (1.0f * screenHeight / 2 * (screenWidth - 2 * placeholderInset) / screenWidth)
+                    .toInt()
+            out.top -= (totalHeight - placeholderHeight)
+            return
+        }
+
+        // Now we rotate the portrait rect depending on what side we want pinned
+        val postRotateScale = screenHeight.toFloat() / screenWidth
+        tmpMatrix.reset()
+        tmpMatrix.postRotate(if (pinToRight) 90f else 270f)
+        tmpMatrix.postTranslate(
+            (if (pinToRight) screenWidth else 0).toFloat(),
+            (if (pinToRight) 0 else screenWidth).toFloat(),
+        )
+        // The placeholder height stays constant after rotation, so we don't change width scale
+        tmpMatrix.postScale(1f, postRotateScale)
+
+        tmpRectF.set(out)
+        tmpMatrix.mapRect(tmpRectF)
+        tmpRectF.inset(0f, placeholderInset.toFloat())
+        tmpRectF.roundOut(out)
+
+        // Adjust the top to account for content off screen. This will help to animate the view in
+        // with rounded corners.
+        val totalWidth =
+            (1.0f * screenWidth / 2 * (screenHeight - 2 * placeholderInset) / screenHeight).toInt()
+        val width = out.width()
+        if (pinToRight) {
+            out.right += totalWidth - width
+        } else {
+            out.left -= totalWidth - width
+        }
+    }
+
+    override fun updateSplitIconParams(
+        out: View,
+        onScreenRectCenterX: Float,
+        onScreenRectCenterY: Float,
+        fullscreenScaleX: Float,
+        fullscreenScaleY: Float,
+        drawableWidth: Int,
+        drawableHeight: Int,
+        dp: DeviceProfile,
+        @StagePosition stagePosition: Int,
+    ) {
+        val pinToRight = stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+        val insetAdjustment = getPlaceholderSizeAdjustment(dp, pinToRight) / 2f
+        if (!dp.isLeftRightSplit) {
+            out.x = (onScreenRectCenterX / fullscreenScaleX - 1.0f * drawableWidth / 2)
+            out.y =
+                ((onScreenRectCenterY + insetAdjustment) / fullscreenScaleY -
+                    1.0f * drawableHeight / 2)
+        } else {
+            if (pinToRight) {
+                out.x =
+                    ((onScreenRectCenterX - insetAdjustment) / fullscreenScaleX -
+                        1.0f * drawableWidth / 2)
+            } else {
+                out.x =
+                    ((onScreenRectCenterX + insetAdjustment) / fullscreenScaleX -
+                        1.0f * drawableWidth / 2)
+            }
+            out.y = (onScreenRectCenterY / fullscreenScaleY - 1.0f * drawableHeight / 2)
+        }
+    }
+
+    /**
+     * The split placeholder comes with a default inset to buffer the icon from the top of the
+     * screen. But if the device already has a large inset (from cutouts etc), use that instead.
+     */
+    private fun getPlaceholderSizeAdjustment(dp: DeviceProfile, pinToRight: Boolean): Int {
+        val insetThickness =
+            if (!dp.isLandscape) {
+                dp.insets.top
+            } else {
+                if (pinToRight) dp.insets.right else dp.insets.left
+            }
+        return max((insetThickness - dp.splitPlaceholderInset).toDouble(), 0.0).toInt()
+    }
+
+    override fun setSplitInstructionsParams(
+        out: View,
+        dp: DeviceProfile,
+        splitInstructionsHeight: Int,
+        splitInstructionsWidth: Int,
+    ) {
+        out.pivotX = 0f
+        out.pivotY = splitInstructionsHeight.toFloat()
+        out.rotation = degreesRotated
+        val distanceToEdge =
+            if (dp.isPhone) {
+                if (dp.isLandscape) {
+                    out.resources.getDimensionPixelSize(
+                        R.dimen.split_instructions_bottom_margin_phone_landscape
+                    )
+                } else {
+                    out.resources.getDimensionPixelSize(
+                        R.dimen.split_instructions_bottom_margin_phone_portrait
+                    )
+                }
+            } else {
+                dp.overviewActionsClaimedSpaceBelow
+            }
+
+        // Center the view in case of unbalanced insets on left or right of screen
+        val insetCorrectionX = (dp.insets.right - dp.insets.left) / 2
+        // Adjust for any insets on the bottom edge
+        val insetCorrectionY = dp.insets.bottom
+        out.translationX = insetCorrectionX.toFloat()
+        out.translationY = (-distanceToEdge + insetCorrectionY).toFloat()
+        val lp = out.layoutParams as FrameLayout.LayoutParams
+        lp.gravity = Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
+        out.layoutParams = lp
+    }
+
+    override fun getFinalSplitPlaceholderBounds(
+        splitDividerSize: Int,
+        dp: DeviceProfile,
+        @StagePosition stagePosition: Int,
+        out1: Rect,
+        out2: Rect,
+    ) {
+        val screenHeight = dp.heightPx
+        val screenWidth = dp.widthPx
+        out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize)
+        out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight)
+        if (!dp.isLeftRightSplit) {
+            // Portrait - the window bounds are always top and bottom half
+            return
+        }
+
+        // Now we rotate the portrait rect depending on what side we want pinned
+        val pinToRight = stagePosition == SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+        val postRotateScale = screenHeight.toFloat() / screenWidth
+
+        tmpMatrix.reset()
+        tmpMatrix.postRotate(if (pinToRight) 90f else 270f)
+        tmpMatrix.postTranslate(
+            (if (pinToRight) screenHeight else 0).toFloat(),
+            (if (pinToRight) 0 else screenWidth).toFloat(),
+        )
+        tmpMatrix.postScale(1 / postRotateScale, postRotateScale)
+
+        tmpRectF.set(out1)
+        tmpMatrix.mapRect(tmpRectF)
+        tmpRectF.roundOut(out1)
+
+        tmpRectF.set(out2)
+        tmpMatrix.mapRect(tmpRectF)
+        tmpRectF.roundOut(out2)
+    }
+
+    override fun setSplitTaskSwipeRect(
+        dp: DeviceProfile,
+        outRect: Rect,
+        splitInfo: SplitConfigurationOptions.SplitBounds,
+        desiredStagePosition: Int,
+    ) {
+        val topLeftTaskPercent = splitInfo.leftTopTaskPercent
+        val dividerBarPercent = splitInfo.dividerPercent
+
+        val taskbarHeight = if (dp.isTransientTaskbar) 0 else dp.taskbarHeight
+        val scale = outRect.height().toFloat() / (dp.availableHeightPx - taskbarHeight)
+        val topTaskHeight = dp.availableHeightPx * topLeftTaskPercent
+        val scaledTopTaskHeight = topTaskHeight * scale
+        val dividerHeight = dp.availableHeightPx * dividerBarPercent
+        val scaledDividerHeight = dividerHeight * scale
+
+        if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) {
+            if (dp.isLeftRightSplit) {
+                outRect.right = outRect.left + Math.round(outRect.width() * topLeftTaskPercent)
+            } else {
+                outRect.bottom = Math.round(outRect.top + scaledTopTaskHeight)
+            }
+        } else {
+            if (dp.isLeftRightSplit) {
+                outRect.left +=
+                    Math.round(outRect.width() * (topLeftTaskPercent + dividerBarPercent))
+            } else {
+                outRect.top += Math.round(scaledTopTaskHeight + scaledDividerHeight)
+            }
+        }
+    }
+
+    /**
+     * @param inSplitSelection Whether user currently has a task from this task group staged for
+     *   split screen. If true, we have custom translations/scaling in place for the remaining
+     *   snapshot, so we'll skip setting translation/scale here.
+     */
+    override fun measureGroupedTaskViewThumbnailBounds(
+        primarySnapshot: View,
+        secondarySnapshot: View,
+        parentWidth: Int,
+        parentHeight: Int,
+        splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
+        dp: DeviceProfile,
+        isRtl: Boolean,
+        inSplitSelection: Boolean,
+    ) {
+        val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+
+        val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
+        val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
+
+        // Reset margins that aren't used in this method, but are used in other
+        // `RecentsPagedOrientationHandler` variants.
+        secondaryParams.topMargin = 0
+        primaryParams.topMargin = spaceAboveSnapshot
+
+        val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+        val dividerScale = splitBoundsConfig.dividerPercent
+        val taskViewSizes =
+            getGroupedTaskViewSizes(dp, splitBoundsConfig, parentWidth, parentHeight)
+        if (!inSplitSelection) {
+            // Reset translations that aren't used in this method, but are used in other
+            // `RecentsPagedOrientationHandler` variants.
+            primarySnapshot.translationY = 0f
+
+            if (dp.isLeftRightSplit) {
+                val scaledDividerBar = Math.round(parentWidth * dividerScale)
+                if (isRtl) {
+                    val translationX = taskViewSizes.second.x + scaledDividerBar
+                    primarySnapshot.translationX = -translationX.toFloat()
+                    secondarySnapshot.translationX = 0f
+                } else {
+                    val translationX = taskViewSizes.first.x + scaledDividerBar
+                    secondarySnapshot.translationX = translationX.toFloat()
+                    primarySnapshot.translationX = 0f
+                }
+                secondarySnapshot.translationY = spaceAboveSnapshot.toFloat()
+            } else {
+                val finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale).toFloat()
+                val translationY = taskViewSizes.first.y + spaceAboveSnapshot + finalDividerHeight
+                secondarySnapshot.translationY = translationY
+
+                // Reset unused translations.
+                secondarySnapshot.translationX = 0f
+                primarySnapshot.translationX = 0f
+            }
+        }
+
+        primarySnapshot.measure(
+            View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.x, View.MeasureSpec.EXACTLY),
+            View.MeasureSpec.makeMeasureSpec(taskViewSizes.first.y, View.MeasureSpec.EXACTLY),
+        )
+        secondarySnapshot.measure(
+            View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.x, View.MeasureSpec.EXACTLY),
+            View.MeasureSpec.makeMeasureSpec(taskViewSizes.second.y, View.MeasureSpec.EXACTLY),
+        )
+    }
+
+    override fun getGroupedTaskViewSizes(
+        dp: DeviceProfile,
+        splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
+        parentWidth: Int,
+        parentHeight: Int,
+    ): Pair<Point, Point> {
+        val spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx
+        val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
+        val dividerScale = splitBoundsConfig.dividerPercent
+        val taskPercent = splitBoundsConfig.leftTopTaskPercent
+
+        val firstTaskViewSize = Point()
+        val secondTaskViewSize = Point()
+
+        if (dp.isLeftRightSplit) {
+            val scaledDividerBar = Math.round(parentWidth * dividerScale)
+            firstTaskViewSize.x = Math.round(parentWidth * taskPercent)
+            firstTaskViewSize.y = totalThumbnailHeight
+
+            secondTaskViewSize.x = parentWidth - firstTaskViewSize.x - scaledDividerBar
+            secondTaskViewSize.y = totalThumbnailHeight
+        } else {
+            val taskbarHeight = if (dp.isTransientTaskbar) 0 else dp.taskbarHeight
+            val scale = totalThumbnailHeight.toFloat() / (dp.availableHeightPx - taskbarHeight)
+            val topTaskHeight = dp.availableHeightPx * taskPercent
+            val finalDividerHeight = Math.round(totalThumbnailHeight * dividerScale).toFloat()
+            val scaledTopTaskHeight = topTaskHeight * scale
+            firstTaskViewSize.x = parentWidth
+            firstTaskViewSize.y = Math.round(scaledTopTaskHeight)
+
+            secondTaskViewSize.x = parentWidth
+            secondTaskViewSize.y =
+                Math.round((totalThumbnailHeight - firstTaskViewSize.y - finalDividerHeight))
+        }
+
+        return Pair(firstTaskViewSize, secondTaskViewSize)
+    }
+
+    override fun setTaskIconParams(
+        iconParams: FrameLayout.LayoutParams,
+        taskIconMargin: Int,
+        taskIconHeight: Int,
+        thumbnailTopMargin: Int,
+        isRtl: Boolean,
+    ) {
+        iconParams.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+        // Reset margins, since they may have been set on rotation
+        iconParams.rightMargin = 0
+        iconParams.leftMargin = iconParams.rightMargin
+        iconParams.bottomMargin = 0
+        iconParams.topMargin = iconParams.bottomMargin
+    }
+
+    override fun setIconAppChipChildrenParams(
+        iconParams: FrameLayout.LayoutParams,
+        chipChildMarginStart: Int,
+    ) {
+        iconParams.marginStart = chipChildMarginStart
+        iconParams.gravity = Gravity.START or Gravity.CENTER_VERTICAL
+        iconParams.topMargin = 0
+    }
+
+    override fun setIconAppChipMenuParams(
+        iconAppChipView: IconAppChipView,
+        iconMenuParams: FrameLayout.LayoutParams,
+        iconMenuMargin: Int,
+        thumbnailTopMargin: Int,
+    ) {
+        iconMenuParams.gravity = Gravity.TOP or Gravity.START
+        iconMenuParams.marginStart = iconMenuMargin
+        iconMenuParams.topMargin = thumbnailTopMargin
+        iconMenuParams.bottomMargin = 0
+        iconMenuParams.marginEnd = 0
+
+        iconAppChipView.pivotX = 0f
+        iconAppChipView.pivotY = 0f
+        iconAppChipView.setSplitTranslationY(0f)
+        iconAppChipView.rotation = degreesRotated
+    }
+
+    /**
+     * @param inSplitSelection Whether user currently has a task from this task group staged for
+     *   split screen. If true, we have custom translations in place for the remaining icon, so
+     *   we'll skip setting translations here.
+     */
+    override fun setSplitIconParams(
+        primaryIconView: View,
+        secondaryIconView: View,
+        taskIconHeight: Int,
+        primarySnapshotWidth: Int,
+        primarySnapshotHeight: Int,
+        groupedTaskViewHeight: Int,
+        groupedTaskViewWidth: Int,
+        isRtl: Boolean,
+        deviceProfile: DeviceProfile,
+        splitConfig: SplitConfigurationOptions.SplitBounds,
+        inSplitSelection: Boolean,
+        oneIconHiddenDueToSmallWidth: Boolean,
+    ) {
+        val primaryIconParams = primaryIconView.layoutParams as FrameLayout.LayoutParams
+        val secondaryIconParams =
+            if (enableOverviewIconMenu()) secondaryIconView.layoutParams as FrameLayout.LayoutParams
+            else FrameLayout.LayoutParams(primaryIconParams)
+
+        if (enableOverviewIconMenu()) {
+            val primaryAppChipView = primaryIconView as IconAppChipView
+            val secondaryAppChipView = secondaryIconView as IconAppChipView
+            primaryIconParams.gravity = Gravity.TOP or Gravity.START
+            secondaryIconParams.gravity = Gravity.TOP or Gravity.START
+            secondaryIconParams.topMargin = primaryIconParams.topMargin
+            secondaryIconParams.marginStart = primaryIconParams.marginStart
+            if (!inSplitSelection) {
+                if (deviceProfile.isLeftRightSplit) {
+                    if (isRtl) {
+                        val secondarySnapshotWidth = groupedTaskViewWidth - primarySnapshotWidth
+                        primaryAppChipView.setSplitTranslationX(-secondarySnapshotWidth.toFloat())
+                    } else {
+                        val dividerSize =
+                            Math.round(groupedTaskViewWidth * splitConfig.dividerPercent)
+                        secondaryAppChipView.setSplitTranslationX(
+                            primarySnapshotWidth.toFloat() + dividerSize
+                        )
+                    }
+                } else {
+                    primaryAppChipView.setSplitTranslationX(0f)
+                    secondaryAppChipView.setSplitTranslationX(0f)
+                    val dividerThickness =
+                        min(
+                                splitConfig.visualDividerBounds.width().toDouble(),
+                                splitConfig.visualDividerBounds.height().toDouble(),
+                            )
+                            .toInt()
+                    secondaryAppChipView.setSplitTranslationY(
+                        (primarySnapshotHeight +
+                                (if (deviceProfile.isTablet) 0 else dividerThickness))
+                            .toFloat()
+                    )
+                }
+            }
+        } else if (deviceProfile.isLeftRightSplit) {
+            // We calculate the "midpoint" of the thumbnail area, and place the icons there.
+            // This is the place where the thumbnail area splits by default, in a near-50/50 split.
+            // It is usually not exactly 50/50, due to insets/screen cutouts.
+            val fullscreenInsetThickness =
+                if (deviceProfile.isSeascape) deviceProfile.insets.right
+                else deviceProfile.insets.left
+            val fullscreenMidpointFromBottom =
+                ((deviceProfile.widthPx - fullscreenInsetThickness) / 2)
+            val midpointFromEndPct = fullscreenMidpointFromBottom.toFloat() / deviceProfile.widthPx
+            val insetPct = fullscreenInsetThickness.toFloat() / deviceProfile.widthPx
+            val spaceAboveSnapshots = 0
+            val overviewThumbnailAreaThickness = groupedTaskViewWidth - spaceAboveSnapshots
+            val bottomToMidpointOffset =
+                (overviewThumbnailAreaThickness * midpointFromEndPct).toInt()
+            val insetOffset = (overviewThumbnailAreaThickness * insetPct).toInt()
+
+            if (deviceProfile.isSeascape) {
+                primaryIconParams.gravity =
+                    Gravity.TOP or (if (isRtl) Gravity.END else Gravity.START)
+                secondaryIconParams.gravity =
+                    Gravity.TOP or (if (isRtl) Gravity.END else Gravity.START)
+                if (!inSplitSelection) {
+                    if (splitConfig.initiatedFromSeascape) {
+                        if (oneIconHiddenDueToSmallWidth) {
+                            // Center both icons
+                            val centerX = bottomToMidpointOffset - (taskIconHeight / 2f)
+                            primaryIconView.translationX = centerX
+                            secondaryIconView.translationX = centerX
+                        } else {
+                            // the task on the right (secondary) is slightly larger
+                            primaryIconView.translationX =
+                                (bottomToMidpointOffset - taskIconHeight).toFloat()
+                            secondaryIconView.translationX = bottomToMidpointOffset.toFloat()
+                        }
+                    } else {
+                        if (oneIconHiddenDueToSmallWidth) {
+                            // Center both icons
+                            val centerX =
+                                bottomToMidpointOffset + insetOffset - (taskIconHeight / 2f)
+                            primaryIconView.translationX = centerX
+                            secondaryIconView.translationX = centerX
+                        } else {
+                            // the task on the left (primary) is slightly larger
+                            primaryIconView.translationX =
+                                (bottomToMidpointOffset + insetOffset - taskIconHeight).toFloat()
+                            secondaryIconView.translationX =
+                                (bottomToMidpointOffset + insetOffset).toFloat()
+                        }
+                    }
+                }
+            } else {
+                primaryIconParams.gravity =
+                    Gravity.TOP or (if (isRtl) Gravity.START else Gravity.END)
+                secondaryIconParams.gravity =
+                    Gravity.TOP or (if (isRtl) Gravity.START else Gravity.END)
+                if (!inSplitSelection) {
+                    if (!splitConfig.initiatedFromSeascape) {
+                        if (oneIconHiddenDueToSmallWidth) {
+                            // Center both icons
+                            val centerX = -bottomToMidpointOffset + (taskIconHeight / 2f)
+                            primaryIconView.translationX = centerX
+                            secondaryIconView.translationX = centerX
+                        } else {
+                            // the task on the left (primary) is slightly larger
+                            primaryIconView.translationX = -bottomToMidpointOffset.toFloat()
+                            secondaryIconView.translationX =
+                                (-bottomToMidpointOffset + taskIconHeight).toFloat()
+                        }
+                    } else {
+                        if (oneIconHiddenDueToSmallWidth) {
+                            // Center both icons
+                            val centerX =
+                                -bottomToMidpointOffset - insetOffset + (taskIconHeight / 2f)
+                            primaryIconView.translationX = centerX
+                            secondaryIconView.translationX = centerX
+                        } else {
+                            // the task on the right (secondary) is slightly larger
+                            primaryIconView.translationX =
+                                (-bottomToMidpointOffset - insetOffset).toFloat()
+                            secondaryIconView.translationX =
+                                (-bottomToMidpointOffset - insetOffset + taskIconHeight).toFloat()
+                        }
+                    }
+                }
+            }
+        } else {
+            primaryIconParams.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+            secondaryIconParams.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
+            if (!inSplitSelection) {
+                if (oneIconHiddenDueToSmallWidth) {
+                    // Center both icons
+                    primaryIconView.translationX = 0f
+                    secondaryIconView.translationX = 0f
+                } else {
+                    // shifts icon half a width left (height is used here since icons are square)
+                    primaryIconView.translationX = -(taskIconHeight / 2f)
+                    secondaryIconView.translationX = taskIconHeight / 2f
+                }
+            }
+        }
+        if (!enableOverviewIconMenu() && !inSplitSelection) {
+            primaryIconView.translationY = 0f
+            secondaryIconView.translationY = 0f
+        }
+
+        primaryIconView.layoutParams = primaryIconParams
+        secondaryIconView.layoutParams = secondaryIconParams
+    }
+
+    override fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int {
+        check(deviceProfile.isTablet) { "Default position available only for large screens" }
+        return if (deviceProfile.isLeftRightSplit) {
+            SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+        } else {
+            SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
+        }
+    }
+
+    override fun <T> getSplitSelectTaskOffset(
+        primary: FloatProperty<T>,
+        secondary: FloatProperty<T>,
+        deviceProfile: DeviceProfile,
+    ): Pair<FloatProperty<T>, FloatProperty<T>> =
+        if (deviceProfile.isLeftRightSplit) { // or seascape
+            Pair(primary, secondary)
+        } else {
+            Pair(secondary, primary)
+        }
+
+    override fun getFloatingTaskOffscreenTranslationTarget(
+        floatingTask: View,
+        onScreenRect: RectF,
+        @StagePosition stagePosition: Int,
+        dp: DeviceProfile,
+    ): Float {
+        if (dp.isLeftRightSplit) {
+            val currentTranslationX = floatingTask.translationX
+            return if (stagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT)
+                currentTranslationX - onScreenRect.width()
+            else currentTranslationX + onScreenRect.width()
+        } else {
+            val currentTranslationY = floatingTask.translationY
+            return currentTranslationY - onScreenRect.height()
+        }
+    }
+
+    override fun setFloatingTaskPrimaryTranslation(
+        floatingTask: View,
+        translation: Float,
+        dp: DeviceProfile,
+    ) {
+        if (dp.isLeftRightSplit) {
+            floatingTask.translationX = translation
+        } else {
+            floatingTask.translationY = translation
+        }
+    }
+
+    override fun getFloatingTaskPrimaryTranslation(floatingTask: View, dp: DeviceProfile): Float =
+        if (dp.isLeftRightSplit) floatingTask.translationX else floatingTask.translationY
+
+    override fun getHandlerTypeForLogging(): LauncherAtom.TaskSwitcherContainer.OrientationHandler =
+        LauncherAtom.TaskSwitcherContainer.OrientationHandler.PORTRAIT
+}
diff --git a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
index b8d0412..a7bc93b 100644
--- a/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/RecentsPagedOrientationHandler.kt
@@ -82,13 +82,13 @@
 
     fun getSplitTranslationDirectionFactor(
         @StagePosition stagePosition: Int,
-        deviceProfile: DeviceProfile
+        deviceProfile: DeviceProfile,
     ): Int
 
     fun <T> getSplitSelectTaskOffset(
         primary: FloatProperty<T>,
         secondary: FloatProperty<T>,
-        deviceProfile: DeviceProfile
+        deviceProfile: DeviceProfile,
     ): Pair<FloatProperty<T>, FloatProperty<T>>
 
     fun getDistanceToBottomOfRect(dp: DeviceProfile, rect: Rect): Int
@@ -101,7 +101,7 @@
         placeholderInset: Int,
         dp: DeviceProfile,
         @StagePosition stagePosition: Int,
-        out: Rect
+        out: Rect,
     )
 
     /**
@@ -128,7 +128,7 @@
         drawableWidth: Int,
         drawableHeight: Int,
         dp: DeviceProfile,
-        @StagePosition stagePosition: Int
+        @StagePosition stagePosition: Int,
     )
 
     /**
@@ -143,7 +143,7 @@
         out: View,
         dp: DeviceProfile,
         splitInstructionsHeight: Int,
-        splitInstructionsWidth: Int
+        splitInstructionsWidth: Int,
     )
 
     /**
@@ -159,7 +159,7 @@
         dp: DeviceProfile,
         @StagePosition stagePosition: Int,
         out1: Rect,
-        out2: Rect
+        out2: Rect,
     )
 
     fun getDefaultSplitPosition(deviceProfile: DeviceProfile): Int
@@ -174,7 +174,7 @@
         dp: DeviceProfile,
         outRect: Rect,
         splitInfo: SplitConfigurationOptions.SplitBounds,
-        @StagePosition desiredStagePosition: Int
+        @StagePosition desiredStagePosition: Int,
     )
 
     fun measureGroupedTaskViewThumbnailBounds(
@@ -185,7 +185,7 @@
         splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
         dp: DeviceProfile,
         isRtl: Boolean,
-        inSplitSelection: Boolean
+        inSplitSelection: Boolean,
     )
 
     /**
@@ -198,7 +198,7 @@
         dp: DeviceProfile,
         splitBoundsConfig: SplitConfigurationOptions.SplitBounds,
         parentWidth: Int,
-        parentHeight: Int
+        parentHeight: Int,
     ): Pair<Point, Point>
 
     // Overview TaskMenuView methods
@@ -208,7 +208,7 @@
         taskIconMargin: Int,
         taskIconHeight: Int,
         thumbnailTopMargin: Int,
-        isRtl: Boolean
+        isRtl: Boolean,
     )
 
     /**
@@ -216,14 +216,14 @@
      */
     fun setIconAppChipChildrenParams(
         iconParams: FrameLayout.LayoutParams,
-        chipChildMarginStart: Int
+        chipChildMarginStart: Int,
     )
 
     fun setIconAppChipMenuParams(
         iconAppChipView: IconAppChipView,
         iconMenuParams: FrameLayout.LayoutParams,
         iconMenuMargin: Int,
-        thumbnailTopMargin: Int
+        thumbnailTopMargin: Int,
     )
 
     fun setSplitIconParams(
@@ -237,7 +237,8 @@
         isRtl: Boolean,
         deviceProfile: DeviceProfile,
         splitConfig: SplitConfigurationOptions.SplitBounds,
-        inSplitSelection: Boolean
+        inSplitSelection: Boolean,
+        oneIconHiddenDueToSmallWidth: Boolean,
     )
 
     /*
@@ -251,7 +252,7 @@
         thumbnailView: View,
         deviceProfile: DeviceProfile,
         taskInsetMargin: Float,
-        taskViewIcon: View
+        taskViewIcon: View,
     ): Float
 
     fun getTaskMenuY(
@@ -260,20 +261,20 @@
         stagePosition: Int,
         taskMenuView: View,
         taskInsetMargin: Float,
-        taskViewIcon: View
+        taskViewIcon: View,
     ): Float
 
     fun getTaskMenuWidth(
         thumbnailView: View,
         deviceProfile: DeviceProfile,
-        @StagePosition stagePosition: Int
+        @StagePosition stagePosition: Int,
     ): Int
 
     fun getTaskMenuHeight(
         taskInsetMargin: Float,
         deviceProfile: DeviceProfile,
         taskMenuX: Float,
-        taskMenuY: Float
+        taskMenuY: Float,
     ): Int
 
     /**
@@ -284,7 +285,7 @@
         deviceProfile: DeviceProfile,
         taskMenuLayout: LinearLayout,
         dividerSpacing: Int,
-        dividerDrawable: ShapeDrawable
+        dividerDrawable: ShapeDrawable,
     )
 
     /**
@@ -294,7 +295,7 @@
     fun setLayoutParamsForTaskMenuOptionItem(
         lp: LinearLayout.LayoutParams,
         viewGroup: LinearLayout,
-        deviceProfile: DeviceProfile
+        deviceProfile: DeviceProfile,
     )
 
     /** Layout a Digital Wellbeing Banner on its parent. TaskView. */
@@ -305,7 +306,7 @@
         deviceProfile: DeviceProfile,
         snapshotViewWidth: Int,
         snapshotViewHeight: Int,
-        banner: View
+        banner: View,
     )
 
     /**
@@ -321,7 +322,7 @@
         deviceProfile: DeviceProfile,
         thumbnailViews: Array<View>,
         desiredTaskId: Int,
-        banner: View
+        banner: View,
     ): Pair<Float, Float>
 
     // The following are only used by TaskViewTouchHandler.
@@ -332,12 +333,24 @@
     /** @return Given [.getUpDownSwipeDirection], whether POSITIVE or NEGATIVE is up. */
     fun getUpDirection(isRtl: Boolean): Int
 
+    /** @return Given [.getUpDownSwipeDirection], whether POSITIVE or NEGATIVE is down. */
+    fun getDownDirection(isRtl: Boolean): Int
+
     /** @return Whether the displacement is going towards the top of the screen. */
     fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean
 
     /** @return Either 1 or -1, a factor to multiply by so the animation goes the correct way. */
     fun getTaskDragDisplacementFactor(isRtl: Boolean): Int
 
+    /** @return Either 1 or -1, the direction sign towards task dismiss. */
+    fun getTaskDismissVerticalDirection(): Int
+
+    /** @return the length to drag a task off screen for dismiss. */
+    fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int
+
+    /** @return the length to drag a task to full screen for launch. */
+    fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int
+
     /**
      * Maps the velocity from the coordinate plane of the foreground app to that of Launcher's
      * (which now will always be portrait)
@@ -367,7 +380,7 @@
         floatingTask: View,
         onScreenRect: RectF,
         @StagePosition stagePosition: Int,
-        dp: DeviceProfile
+        dp: DeviceProfile,
     ): Float
 
     /**
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
index bc91911..67358bbb 100644
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -26,6 +26,7 @@
 import android.view.View
 import android.view.View.MeasureSpec
 import android.widget.FrameLayout
+import android.widget.LinearLayout
 import androidx.core.util.component1
 import androidx.core.util.component2
 import androidx.core.view.updateLayoutParams
@@ -53,7 +54,7 @@
 
     override fun getSplitTranslationDirectionFactor(
         stagePosition: Int,
-        deviceProfile: DeviceProfile
+        deviceProfile: DeviceProfile,
     ): Int = if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) -1 else 1
 
     override fun getRecentsRtlSetting(resources: Resources): Boolean = Utilities.isRtl(resources)
@@ -70,7 +71,7 @@
         thumbnailView: View,
         deviceProfile: DeviceProfile,
         taskInsetMargin: Float,
-        taskViewIcon: View
+        taskViewIcon: View,
     ): Float = x + taskInsetMargin
 
     override fun getTaskMenuY(
@@ -79,7 +80,7 @@
         stagePosition: Int,
         taskMenuView: View,
         taskInsetMargin: Float,
-        taskViewIcon: View
+        taskViewIcon: View,
     ): Float {
         if (Flags.enableOverviewIconMenu()) {
             return y
@@ -97,24 +98,17 @@
         taskInsetMargin: Float,
         deviceProfile: DeviceProfile,
         taskMenuX: Float,
-        taskMenuY: Float
+        taskMenuY: Float,
     ): Int = (deviceProfile.availableWidthPx - taskInsetMargin - taskMenuX).toInt()
 
     override fun setSplitTaskSwipeRect(
         dp: DeviceProfile,
         outRect: Rect,
         splitInfo: SplitBounds,
-        desiredStagePosition: Int
+        desiredStagePosition: Int,
     ) {
-        val topLeftTaskPercent: Float
-        val dividerBarPercent: Float
-        if (splitInfo.appsStackedVertically) {
-            topLeftTaskPercent = splitInfo.topTaskPercent
-            dividerBarPercent = splitInfo.dividerHeightPercent
-        } else {
-            topLeftTaskPercent = splitInfo.leftTaskPercent
-            dividerBarPercent = splitInfo.dividerWidthPercent
-        }
+        val topLeftTaskPercent = splitInfo.leftTopTaskPercent
+        val dividerBarPercent = splitInfo.dividerPercent
 
         // In seascape, the primary thumbnail is counterintuitively placed at the physical bottom of
         // the screen. This is to preserve consistency when the user rotates: From the user's POV,
@@ -133,7 +127,7 @@
         deviceProfile: DeviceProfile,
         snapshotViewWidth: Int,
         snapshotViewHeight: Int,
-        banner: View
+        banner: View,
     ) {
         banner.pivotX = 0f
         banner.pivotY = 0f
@@ -156,9 +150,9 @@
         deviceProfile: DeviceProfile,
         thumbnailViews: Array<View>,
         desiredTaskId: Int,
-        banner: View
+        banner: View,
     ): Pair<Float, Float> {
-        val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
+        val snapshotParams = thumbnailViews[0].layoutParams as LinearLayout.LayoutParams
         val translationX: Float = (taskViewWidth - banner.height).toFloat()
         val translationY: Float
         if (splitBounds == null) {
@@ -166,11 +160,7 @@
         } else {
             if (desiredTaskId == splitBounds.leftTopTaskId) {
                 val bottomRightTaskPlusDividerPercent =
-                    if (splitBounds.appsStackedVertically) {
-                        1f - splitBounds.topTaskPercent
-                    } else {
-                        1f - splitBounds.leftTaskPercent
-                    }
+                    splitBounds.rightBottomTaskPercent + splitBounds.dividerPercent
                 translationY =
                     banner.height -
                         (taskViewHeight - snapshotParams.topMargin) *
@@ -192,7 +182,7 @@
                 R.drawable.ic_split_horizontal,
                 R.string.recent_task_option_split_screen,
                 STAGE_POSITION_BOTTOM_OR_RIGHT,
-                STAGE_TYPE_MAIN
+                STAGE_TYPE_MAIN,
             )
         )
 
@@ -200,7 +190,7 @@
         out: View,
         dp: DeviceProfile,
         splitInstructionsHeight: Int,
-        splitInstructionsWidth: Int
+        splitInstructionsWidth: Int,
     ) {
         out.pivotX = 0f
         out.pivotY = splitInstructionsHeight.toFloat()
@@ -228,7 +218,7 @@
         taskIconMargin: Int,
         taskIconHeight: Int,
         thumbnailTopMargin: Int,
-        isRtl: Boolean
+        isRtl: Boolean,
     ) {
         iconParams.gravity =
             if (isRtl) {
@@ -241,7 +231,7 @@
 
     override fun setIconAppChipChildrenParams(
         iconParams: FrameLayout.LayoutParams,
-        chipChildMarginStart: Int
+        chipChildMarginStart: Int,
     ) {
         iconParams.setMargins(0, 0, 0, 0)
         iconParams.marginStart = chipChildMarginStart
@@ -252,7 +242,7 @@
         iconAppChipView: IconAppChipView,
         iconMenuParams: FrameLayout.LayoutParams,
         iconMenuMargin: Int,
-        thumbnailTopMargin: Int
+        thumbnailTopMargin: Int,
     ) {
         val isRtl = iconAppChipView.layoutDirection == View.LAYOUT_DIRECTION_RTL
         val iconCenter = iconAppChipView.getHeight() / 2f
@@ -279,7 +269,7 @@
 
     /**
      * @param inSplitSelection Whether user currently has a task from this task group staged for
-     * split screen. Currently this state is not reachable in fake seascape.
+     *   split screen. Currently this state is not reachable in fake seascape.
      */
     override fun measureGroupedTaskViewThumbnailBounds(
         primarySnapshot: View,
@@ -289,7 +279,7 @@
         splitBoundsConfig: SplitBounds,
         dp: DeviceProfile,
         isRtl: Boolean,
-        inSplitSelection: Boolean
+        inSplitSelection: Boolean,
     ) {
         val primaryParams = primarySnapshot.layoutParams as FrameLayout.LayoutParams
         val secondaryParams = secondarySnapshot.layoutParams as FrameLayout.LayoutParams
@@ -311,11 +301,11 @@
             (taskViewSecond.y + spaceAboveSnapshot + dividerBar).toFloat()
         primarySnapshot.measure(
             MeasureSpec.makeMeasureSpec(taskViewFirst.x, MeasureSpec.EXACTLY),
-            MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY)
+            MeasureSpec.makeMeasureSpec(taskViewFirst.y, MeasureSpec.EXACTLY),
         )
         secondarySnapshot.measure(
             MeasureSpec.makeMeasureSpec(taskViewSecond.x, MeasureSpec.EXACTLY),
-            MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY)
+            MeasureSpec.makeMeasureSpec(taskViewSecond.y, MeasureSpec.EXACTLY),
         )
     }
 
@@ -323,7 +313,7 @@
         dp: DeviceProfile,
         splitBoundsConfig: SplitBounds,
         parentWidth: Int,
-        parentHeight: Int
+        parentHeight: Int,
     ): Pair<Point, Point> {
         // Measure and layout the thumbnails bottom up, since the primary is on the visual left
         // (portrait bottom) and secondary is on the right (portrait top)
@@ -331,12 +321,7 @@
         val totalThumbnailHeight = parentHeight - spaceAboveSnapshot
         val dividerBar = getDividerBarSize(totalThumbnailHeight, splitBoundsConfig)
 
-        val taskPercent =
-            if (splitBoundsConfig.appsStackedVertically) {
-                splitBoundsConfig.topTaskPercent
-            } else {
-                splitBoundsConfig.leftTaskPercent
-            }
+        val taskPercent = splitBoundsConfig.leftTopTaskPercent
         val firstTaskViewSize = Point(parentWidth, (totalThumbnailHeight * taskPercent).toInt())
         val secondTaskViewSize =
             Point(parentWidth, totalThumbnailHeight - firstTaskViewSize.y - dividerBar)
@@ -351,11 +336,23 @@
         if (isRtl) SingleAxisSwipeDetector.DIRECTION_POSITIVE
         else SingleAxisSwipeDetector.DIRECTION_NEGATIVE
 
+    override fun getDownDirection(isRtl: Boolean): Int =
+        if (isRtl) SingleAxisSwipeDetector.DIRECTION_NEGATIVE
+        else SingleAxisSwipeDetector.DIRECTION_POSITIVE
+
     override fun isGoingUp(displacement: Float, isRtl: Boolean): Boolean =
         if (isRtl) displacement > 0 else displacement < 0
 
     override fun getTaskDragDisplacementFactor(isRtl: Boolean): Int = if (isRtl) -1 else 1
 
+    override fun getTaskDismissVerticalDirection(): Int = -1
+
+    override fun getTaskDismissLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+        taskThumbnailBounds.right
+
+    override fun getTaskLaunchLength(secondaryDimension: Int, taskThumbnailBounds: Rect): Int =
+        secondaryDimension - taskThumbnailBounds.right
+
     /* -------------------- */
 
     override fun getSplitIconsPosition(
@@ -365,17 +362,18 @@
         isRtl: Boolean,
         overviewTaskMarginPx: Int,
         dividerSize: Int,
+        oneIconHiddenDueToSmallWidth: Boolean,
     ): SplitIconPositions {
         return if (Flags.enableOverviewIconMenu()) {
             if (isRtl) {
                 SplitIconPositions(
                     topLeftY = totalThumbnailHeight - primarySnapshotHeight,
-                    bottomRightY = 0
+                    bottomRightY = 0,
                 )
             } else {
                 SplitIconPositions(
                     topLeftY = 0,
-                    bottomRightY = -(primarySnapshotHeight + dividerSize)
+                    bottomRightY = -(primarySnapshotHeight + dividerSize),
                 )
             }
         } else {
@@ -384,10 +382,16 @@
             // from the bottom to the almost-center of the screen using the bottom margin.
             // The primary snapshot is placed at the bottom, thus we translate the icons using
             // the size of the primary snapshot minus the icon size for the top-left icon.
-            SplitIconPositions(
-                topLeftY = primarySnapshotHeight - taskIconHeight,
-                bottomRightY = primarySnapshotHeight + dividerSize
-            )
+            if (oneIconHiddenDueToSmallWidth) {
+                // Center both icons
+                val centerY = primarySnapshotHeight + ((dividerSize - taskIconHeight) / 2)
+                SplitIconPositions(topLeftY = centerY, bottomRightY = centerY)
+            } else {
+                SplitIconPositions(
+                    topLeftY = primarySnapshotHeight - taskIconHeight,
+                    bottomRightY = primarySnapshotHeight + dividerSize,
+                )
+            }
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/recents/data/HighResLoadingStateNotifier.kt b/quickstep/src/com/android/quickstep/recents/data/HighResLoadingStateNotifier.kt
index df546ca..ad2bd25 100644
--- a/quickstep/src/com/android/quickstep/recents/data/HighResLoadingStateNotifier.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/HighResLoadingStateNotifier.kt
@@ -16,7 +16,7 @@
 
 package com.android.quickstep.recents.data
 
-import com.android.quickstep.TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback
+import com.android.quickstep.HighResLoadingState.HighResLoadingStateChangedCallback
 
 /** Notifies added callbacks that high res state has changed */
 interface HighResLoadingStateNotifier {
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
index 3b59864..53969c5 100644
--- a/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentTasksRepository.kt
@@ -37,6 +37,12 @@
     fun getThumbnailById(taskId: Int): Flow<ThumbnailData?>
 
     /**
+     * Gets the [ThumbnailData] associated with a task that has id [taskId]. Flow will settle on
+     * null if the task was not found or is invisible.
+     */
+    fun getCurrentThumbnailById(taskId: Int): ThumbnailData?
+
+    /**
      * Sets the tasks that are visible, indicating that properties relating to visuals need to be
      * populated e.g. icons/thumbnails etc.
      */
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfile.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfile.kt
index d2cb595..0ee2bd2 100644
--- a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfile.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfile.kt
@@ -20,7 +20,6 @@
  * Container to hold [com.android.launcher3.DeviceProfile] related to Recents.
  *
  * @property isLargeScreen whether the current device posture has a large screen
+ * @property canEnterDesktopMode whether the current device can enter Desktop UI mode
  */
-data class RecentsDeviceProfile(
-    val isLargeScreen: Boolean,
-)
+data class RecentsDeviceProfile(val isLargeScreen: Boolean, val canEnterDesktopMode: Boolean)
diff --git a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImpl.kt b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImpl.kt
index c64453d..8450f09 100644
--- a/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImpl.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImpl.kt
@@ -17,6 +17,7 @@
 package com.android.quickstep.recents.data
 
 import com.android.quickstep.views.RecentsViewContainer
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 
 /**
  * Repository for shrink down version of [com.android.launcher3.DeviceProfile] that only contains
@@ -26,5 +27,10 @@
     RecentsDeviceProfileRepository {
 
     override fun getRecentsDeviceProfile() =
-        with(container.deviceProfile) { RecentsDeviceProfile(isLargeScreen = isTablet) }
+        with(container.deviceProfile) {
+            RecentsDeviceProfile(
+                isLargeScreen = isTablet,
+                canEnterDesktopMode = DesktopModeStatus.canEnterDesktopMode(container.asContext()),
+            )
+        }
 }
diff --git a/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt b/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt
index a45d194..12616a8 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegate.kt
@@ -17,7 +17,8 @@
 package com.android.quickstep.recents.data
 
 import android.os.UserHandle
-import com.android.quickstep.TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback
+import android.util.Log
+import com.android.quickstep.HighResLoadingState.HighResLoadingStateChangedCallback
 import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskIconChangedCallback
 import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskThumbnailChangedCallback
 import com.android.quickstep.util.TaskVisualsChangeListener
@@ -58,7 +59,7 @@
         fun onTaskThumbnailChanged(thumbnailData: ThumbnailData?)
 
         /** Informs the listener that the default resolution for loading thumbnails has changed */
-        fun onHighResLoadingStateChanged()
+        fun onHighResLoadingStateChanged(highResEnabled: Boolean)
     }
 }
 
@@ -91,8 +92,9 @@
     }
 
     override fun onHighResLoadingStateChanged(enabled: Boolean) {
+        Log.d(TAG, "onHighResLoadingStateChanged(enabled = $enabled)")
         taskThumbnailChangedCallbacks.values.forEach { (_, callback) ->
-            callback.onHighResLoadingStateChanged()
+            callback.onHighResLoadingStateChanged(enabled)
         }
     }
 
@@ -142,4 +144,8 @@
             }
         }
     }
+
+    companion object {
+        const val TAG = "TaskVisualsChangedDelegateImpl"
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
index 6c627ef..5274ef3 100644
--- a/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
+++ b/quickstep/src/com/android/quickstep/recents/data/TasksRepository.kt
@@ -25,16 +25,16 @@
 import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
-import kotlin.coroutines.resume
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.async
+import kotlinx.coroutines.awaitAll
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.distinctUntilChangedBy
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withContext
 
 class TasksRepository(
@@ -50,18 +50,26 @@
 
     override fun getAllTaskData(forceRefresh: Boolean): Flow<List<Task>> {
         if (forceRefresh) {
-            recentsModel.getTasks { result ->
-                tasks.value =
-                    MapForStateFlow(
-                        result
-                            .flatMap { groupTask -> groupTask.tasks }
-                            .associateBy { it.key.id }
-                            .also {
-                                // Clean tasks that are not in the latest group tasks list.
-                                val tasksNoLongerVisible = it.keys.subtract(tasks.value.keys)
-                                removeTasks(tasksNoLongerVisible)
-                            }
-                    )
+            recentsModel.getTasks { newTaskList ->
+                val recentTasks =
+                    newTaskList
+                        .flatMap { groupTask -> groupTask.tasks }
+                        .associateBy { it.key.id }
+                        .also { newTaskMap ->
+                            // Clean tasks that are not in the latest group tasks list.
+                            val tasksNoLongerVisible = tasks.value.keys.subtract(newTaskMap.keys)
+                            removeTasks(tasksNoLongerVisible)
+                        }
+                Log.d(
+                    TAG,
+                    "getAllTaskData: oldTasks ${tasks.value.keys}, newTasks: ${recentTasks.keys}",
+                )
+                tasks.value = MapForStateFlow(recentTasks)
+
+                // Request data for tasks to prevent stale data.
+                // This will prevent thumbnail and icon from being replaced and null due to
+                // race condition. The new request will hit the cache and return immediately.
+                taskRequests.keys.forEach(::requestTaskData)
             }
         }
         return tasks.map { it.values.toList() }
@@ -72,6 +80,8 @@
     override fun getThumbnailById(taskId: Int) =
         getTaskDataById(taskId).map { it?.thumbnail }.distinctUntilChangedBy { it?.snapshotId }
 
+    override fun getCurrentThumbnailById(taskId: Int) = tasks.value[taskId]?.thumbnail
+
     override fun setVisibleTasks(visibleTaskIdList: Set<Int>) {
         val tasksNoLongerVisible = taskRequests.keys.subtract(visibleTaskIdList)
         val newlyVisibleTasks = visibleTaskIdList.subtract(taskRequests.keys)
@@ -94,10 +104,11 @@
         taskRequests[taskId] =
             Pair(
                 task.key,
-                recentsCoroutineScope.launch {
+                recentsCoroutineScope.launch(dispatcherProvider.background) {
                     Log.i(TAG, "requestTaskData: $taskId")
-                    fetchIcon(task)
-                    fetchThumbnail(task)
+                    val thumbnailFetchDeferred = async { fetchThumbnail(task) }
+                    val iconFetchDeferred = async { fetchIcon(task) }
+                    awaitAll(thumbnailFetchDeferred, iconFetchDeferred)
                 },
             )
     }
@@ -132,7 +143,7 @@
             task.key,
             object : TaskIconChangedCallback {
                 override fun onTaskIconChanged() {
-                    recentsCoroutineScope.launch {
+                    recentsCoroutineScope.launch(dispatcherProvider.background) {
                         updateIcon(task.key.id, getIconFromDataSource(task))
                     }
                 }
@@ -149,8 +160,18 @@
                     updateThumbnail(task.key.id, thumbnailData)
                 }
 
-                override fun onHighResLoadingStateChanged() {
-                    recentsCoroutineScope.launch {
+                override fun onHighResLoadingStateChanged(highResEnabled: Boolean) {
+                    val isTaskVisible = taskRequests.containsKey(task.key.id)
+                    if (!isTaskVisible) return
+
+                    val isCurrentThumbnailLowRes =
+                        tasks.value[task.key.id]?.thumbnail?.reducedResolution
+                    val isRequestedResHigherThanCurrent =
+                        isCurrentThumbnailLowRes == null ||
+                            (isCurrentThumbnailLowRes && highResEnabled)
+                    if (!isRequestedResHigherThanCurrent) return
+
+                    recentsCoroutineScope.launch(dispatcherProvider.background) {
                         updateThumbnail(task.key.id, getThumbnailFromDataSource(task))
                     }
                 }
@@ -173,30 +194,12 @@
     }
 
     private suspend fun getThumbnailFromDataSource(task: Task) =
-        withContext(dispatcherProvider.main) {
-            suspendCancellableCoroutine { continuation ->
-                val cancellableTask =
-                    taskThumbnailDataSource.getThumbnailInBackground(task) {
-                        continuation.resume(it)
-                    }
-                continuation.invokeOnCancellation { cancellableTask?.cancel() }
-            }
-        }
+        withContext(dispatcherProvider.background) { taskThumbnailDataSource.getThumbnail(task) }
 
     private suspend fun getIconFromDataSource(task: Task) =
-        withContext(dispatcherProvider.main) {
-            suspendCancellableCoroutine { continuation ->
-                val cancellableTask =
-                    taskIconDataSource.getIconInBackground(task) { icon, contentDescription, title
-                        ->
-                        icon.constantState?.let {
-                            continuation.resume(
-                                IconData(it.newDrawable().mutate(), contentDescription, title)
-                            )
-                        }
-                    }
-                continuation.invokeOnCancellation { cancellableTask?.cancel() }
-            }
+        withContext(dispatcherProvider.background) {
+            val iconCacheEntry = taskIconDataSource.getIcon(task)
+            IconData(iconCacheEntry.icon, iconCacheEntry.contentDescription, iconCacheEntry.title)
         }
 
     companion object {
diff --git a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
index b78e214..553a620 100644
--- a/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
+++ b/quickstep/src/com/android/quickstep/recents/di/RecentsDependencies.kt
@@ -18,7 +18,6 @@
 
 import android.content.Context
 import android.util.Log
-import android.view.View
 import com.android.launcher3.util.coroutines.DispatcherProvider
 import com.android.launcher3.util.coroutines.ProductionDispatchers
 import com.android.quickstep.RecentsModel
@@ -26,25 +25,20 @@
 import com.android.quickstep.recents.data.TaskVisualsChangedDelegate
 import com.android.quickstep.recents.data.TaskVisualsChangedDelegateImpl
 import com.android.quickstep.recents.data.TasksRepository
-import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
-import com.android.quickstep.recents.usecase.GetThumbnailUseCase
-import com.android.quickstep.recents.usecase.SysUiStatusNavFlagsUseCase
+import com.android.quickstep.recents.domain.usecase.GetSysUiStatusNavFlagsUseCase
+import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
+import com.android.quickstep.recents.domain.usecase.GetThumbnailPositionUseCase
+import com.android.quickstep.recents.domain.usecase.IsThumbnailValidUseCase
+import com.android.quickstep.recents.domain.usecase.OrganizeDesktopTasksUseCase
 import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
-import com.android.quickstep.task.thumbnail.TaskThumbnailViewData
-import com.android.quickstep.task.viewmodel.TaskContainerData
-import com.android.quickstep.task.viewmodel.TaskOverlayViewModel
-import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
-import com.android.quickstep.task.viewmodel.TaskThumbnailViewModelImpl
-import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
 import kotlinx.coroutines.CoroutineName
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.SupervisorJob
 
 internal typealias RecentsScopeId = String
 
-class RecentsDependencies private constructor(private val appContext: Context) {
+class RecentsDependencies private constructor(appContext: Context) {
     private val scopes = mutableMapOf<RecentsScopeId, RecentsDependenciesScope>()
 
     init {
@@ -52,16 +46,21 @@
     }
 
     /**
-     * This function initialised the default scope with RecentsView dependencies. These dependencies
-     * are used multiple times and should be a singleton to share across Recents classes.
+     * This function initialises the default scope with RecentsView dependencies. Some dependencies
+     * are global while others are per-RecentsView. The scope is used to differentiate between
+     * RecentsViews.
      */
     private fun startDefaultScope(appContext: Context) {
+        Log.d(TAG, "startDefaultScope")
         createScope(DEFAULT_SCOPE_ID).apply {
             set(RecentsViewData::class.java.simpleName, RecentsViewData())
+            val dispatcherProvider: DispatcherProvider = ProductionDispatchers
             val recentsCoroutineScope =
-                CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineName("RecentsView"))
+                CoroutineScope(
+                    SupervisorJob() + dispatcherProvider.unconfined + CoroutineName("RecentsView")
+                )
             set(CoroutineScope::class.java.simpleName, recentsCoroutineScope)
-            set(DispatcherProvider::class.java.simpleName, ProductionDispatchers)
+            set(DispatcherProvider::class.java.simpleName, dispatcherProvider)
             val recentsModel = RecentsModel.INSTANCE.get(appContext)
             val taskVisualsChangedDelegate =
                 TaskVisualsChangedDelegateImpl(
@@ -86,6 +85,32 @@
         }
     }
 
+    /**
+     * This function initialises a scope associated with the dependencies of a single RecentsView.
+     *
+     * @param viewContext the Context associated with a RecentsView.
+     * @return the scope id associated with the new RecentsDependenciesScope.
+     */
+    fun createRecentsViewScope(viewContext: Context): String {
+        val scopeId = viewContext.hashCode().toString()
+        Log.d(TAG, "createRecentsViewScope $scopeId")
+        val scope =
+            createScope(scopeId).apply {
+                set(RecentsViewData::class.java.simpleName, RecentsViewData())
+                val dispatcherProvider: DispatcherProvider =
+                    get<DispatcherProvider>(DEFAULT_SCOPE_ID)
+                val recentsCoroutineScope =
+                    CoroutineScope(
+                        SupervisorJob() +
+                            dispatcherProvider.unconfined +
+                            CoroutineName("RecentsView$scopeId")
+                    )
+                set(CoroutineScope::class.java.simpleName, recentsCoroutineScope)
+            }
+        scope.linkTo(getScope(DEFAULT_SCOPE_ID))
+        return scopeId
+    }
+
     inline fun <reified T> inject(
         scopeId: RecentsScopeId = "",
         extras: RecentsDependenciesExtras = RecentsDependenciesExtras(),
@@ -170,56 +195,17 @@
         log("linked scopes: ${getScope(scopeId).scopeIdsLinked}")
         val instance: Any =
             when (modelClass) {
-                RecentTasksRepository::class.java -> {
-                    with(RecentsModel.INSTANCE.get(appContext)) {
-                        TasksRepository(
-                            this,
-                            thumbnailCache,
-                            iconCache,
-                            get(),
-                            get(),
-                            ProductionDispatchers,
-                        )
-                    }
-                }
-                RecentsViewData::class.java -> RecentsViewData()
-                TaskContainerData::class.java -> TaskContainerData()
-                TaskThumbnailViewData::class.java -> TaskThumbnailViewData()
-                TaskThumbnailViewModel::class.java ->
-                    TaskThumbnailViewModelImpl(
-                        recentsViewData = inject(),
-                        taskContainerData = inject(scopeId),
-                        dispatcherProvider = inject(),
-                        getThumbnailPositionUseCase = inject(),
-                        tasksRepository = inject(),
-                        splashAlphaUseCase = inject(scopeId),
-                    )
-                TaskOverlayViewModel::class.java -> {
-                    val task = extras["Task"] as Task
-                    TaskOverlayViewModel(
-                        task = task,
-                        recentsViewData = inject(),
-                        recentTasksRepository = inject(),
-                        getThumbnailPositionUseCase = inject(),
-                    )
-                }
-                GetThumbnailUseCase::class.java -> GetThumbnailUseCase(taskRepository = inject())
-                SysUiStatusNavFlagsUseCase::class.java ->
-                    SysUiStatusNavFlagsUseCase(taskRepository = inject())
+                IsThumbnailValidUseCase::class.java ->
+                    IsThumbnailValidUseCase(rotationStateRepository = inject(scopeId))
+                GetTaskUseCase::class.java -> GetTaskUseCase(repository = inject(scopeId))
+                GetSysUiStatusNavFlagsUseCase::class.java -> GetSysUiStatusNavFlagsUseCase()
                 GetThumbnailPositionUseCase::class.java ->
                     GetThumbnailPositionUseCase(
-                        deviceProfileRepository = inject(),
-                        rotationStateRepository = inject(),
-                        tasksRepository = inject(),
+                        deviceProfileRepository = inject(scopeId),
+                        rotationStateRepository = inject(scopeId),
+                        previewPositionHelper = PreviewPositionHelper(),
                     )
-                SplashAlphaUseCase::class.java ->
-                    SplashAlphaUseCase(
-                        recentsViewData = inject(),
-                        taskContainerData = inject(scopeId),
-                        taskThumbnailViewData = inject(scopeId),
-                        tasksRepository = inject(),
-                        rotationStateRepository = inject(),
-                    )
+                OrganizeDesktopTasksUseCase::class.java -> OrganizeDesktopTasksUseCase()
                 else -> {
                     log("Factory for ${modelClass.simpleName} not defined!", Log.ERROR)
                     error("Factory for ${modelClass.simpleName} not defined!")
@@ -250,32 +236,59 @@
     }
 
     companion object {
-        private const val DEFAULT_SCOPE_ID = "RecentsDependencies::GlobalScope"
+        const val DEFAULT_SCOPE_ID = "RecentsDependencies::GlobalScope"
         private const val TAG = "RecentsDependencies"
         private const val DEBUG = false
 
-        @Volatile private lateinit var instance: RecentsDependencies
+        @Volatile private var instance: RecentsDependencies? = null
 
-        fun initialize(view: View): RecentsDependencies = initialize(view.context)
+        private fun initialize(context: Context): RecentsDependencies {
+            Log.d(TAG, "initializing")
+            synchronized(this) {
+                val newInstance = RecentsDependencies(context.applicationContext)
+                instance = newInstance
+                return newInstance
+            }
+        }
 
-        fun initialize(context: Context): RecentsDependencies {
-            synchronized(this) { instance = RecentsDependencies(context.applicationContext) }
-            return instance
+        fun maybeInitialize(context: Context): RecentsDependencies {
+            return instance ?: initialize(context)
         }
 
         fun getInstance(): RecentsDependencies {
-            if (!Companion::instance.isInitialized) {
-                throw UninitializedPropertyAccessException(
-                    "Recents dependencies are not initialized. " +
-                        "Call `RecentsDependencies.initialize` before using this container."
-                )
-            }
             return instance
+                ?: throw UninitializedPropertyAccessException(
+                    "Recents dependencies are not initialized. " +
+                        "Call `RecentsDependencies.maybeInitialize` before using this container."
+                )
         }
 
-        fun destroy() {
-            instance.scopes.clear()
-            instance.startDefaultScope(instance.appContext)
+        @JvmStatic
+        fun destroy(viewContext: Context) {
+            synchronized(this) {
+                val localInstance = instance ?: return
+                val scopeId = viewContext.hashCode().toString()
+                val scope = localInstance.scopes[scopeId]
+                if (scope == null) {
+                    Log.e(
+                        TAG,
+                        "Trying to destroy an unknown scope. Scopes: ${localInstance.scopes.size}",
+                    )
+                    return
+                }
+                scope.close()
+                localInstance.scopes.remove(scopeId)
+                if (DEBUG) {
+                    Log.d(TAG, "destroyed $scopeId", Exception("Printing stack trace"))
+                } else {
+                    Log.d(TAG, "destroyed $scopeId")
+                }
+                if (localInstance.scopes.size == 1) {
+                    // Only the default scope left - destroy this too.
+                    instance = null
+                    Log.d(TAG, "also destroyed default scope")
+                }
+            }
         }
     }
 }
diff --git a/src/com/android/launcher3/shapes/AppShape.kt b/quickstep/src/com/android/quickstep/recents/domain/model/DesktopTaskBoundsData.kt
similarity index 72%
copy from src/com/android/launcher3/shapes/AppShape.kt
copy to quickstep/src/com/android/quickstep/recents/domain/model/DesktopTaskBoundsData.kt
index 68200a0..a7f102c 100644
--- a/src/com/android/launcher3/shapes/AppShape.kt
+++ b/quickstep/src/com/android/quickstep/recents/domain/model/DesktopTaskBoundsData.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
-package com.android.launcher3.shapes
+package com.android.quickstep.recents.domain.model
 
-class AppShape(val key: String, val title: String, val path: String)
+import android.graphics.Rect
+
+data class DesktopTaskBoundsData(val taskId: Int, val bounds: Rect)
diff --git a/quickstep/src/com/android/quickstep/recents/domain/model/TaskModel.kt b/quickstep/src/com/android/quickstep/recents/domain/model/TaskModel.kt
new file mode 100644
index 0000000..bf29b1d
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/domain/model/TaskModel.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.domain.model
+
+import android.graphics.drawable.Drawable
+import com.android.systemui.shared.recents.model.ThumbnailData
+
+/**
+ * Data class representing a task in the application.
+ *
+ * This class holds the essential information about a task, including its unique identifier, display
+ * title, associated icon, optional thumbnail data, and background color.
+ *
+ * @property id The unique identifier for this task. Must be an integer.
+ * @property title The display title of the task.
+ * @property titleDescription A content description of the task.
+ * @property icon An optional drawable resource representing an icon for the task. Can be null if no
+ *   icon is required.
+ * @property thumbnail An optional [ThumbnailData] object containing thumbnail information. Can be
+ *   null if no thumbnail is needed.
+ * @property backgroundColor The background color of the task, represented as an integer color
+ *   value.
+ * @property isLocked Indicates whether the [Task] is locked.
+ */
+data class TaskModel(
+    val id: TaskId,
+    val title: String?,
+    val titleDescription: String?,
+    val icon: Drawable?,
+    val thumbnail: ThumbnailData?,
+    val backgroundColor: Int,
+    val isLocked: Boolean,
+)
+
+typealias TaskId = Int
diff --git a/quickstep/src/com/android/quickstep/recents/domain/usecase/GetSysUiStatusNavFlagsUseCase.kt b/quickstep/src/com/android/quickstep/recents/domain/usecase/GetSysUiStatusNavFlagsUseCase.kt
new file mode 100644
index 0000000..da0e2d1
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/domain/usecase/GetSysUiStatusNavFlagsUseCase.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.domain.usecase
+
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+import com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV
+import com.android.launcher3.util.SystemUiController.FLAG_DARK_STATUS
+import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV
+import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS
+import com.android.systemui.shared.recents.model.ThumbnailData
+
+/** UseCase to calculate flags for status bar and navigation bar */
+class GetSysUiStatusNavFlagsUseCase {
+    operator fun invoke(thumbnailData: ThumbnailData?): Int {
+        if (thumbnailData == null) return 0
+        val thumbnailAppearance = thumbnailData.appearance
+        var flags = 0
+        flags =
+            flags or
+                if (thumbnailAppearance and APPEARANCE_LIGHT_STATUS_BARS != 0) FLAG_LIGHT_STATUS
+                else FLAG_DARK_STATUS
+        flags =
+            flags or
+                if (thumbnailAppearance and APPEARANCE_LIGHT_NAVIGATION_BARS != 0) FLAG_LIGHT_NAV
+                else FLAG_DARK_NAV
+        return flags
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCase.kt b/quickstep/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCase.kt
new file mode 100644
index 0000000..a60144b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCase.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.domain.usecase
+
+import com.android.quickstep.recents.data.RecentTasksRepository
+import com.android.quickstep.recents.domain.model.TaskModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+class GetTaskUseCase(private val repository: RecentTasksRepository) {
+    operator fun invoke(taskId: Int): Flow<TaskModel?> =
+        repository.getTaskDataById(taskId).map { task ->
+            if (task != null) {
+                TaskModel(
+                    id = task.key.id,
+                    title = task.title,
+                    titleDescription = task.titleDescription,
+                    icon = task.icon,
+                    thumbnail = task.thumbnail,
+                    backgroundColor = task.colorBackground,
+                    isLocked = task.isLocked,
+                )
+            } else {
+                null
+            }
+        }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt b/quickstep/src/com/android/quickstep/recents/domain/usecase/GetThumbnailPositionUseCase.kt
similarity index 60%
rename from quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt
rename to quickstep/src/com/android/quickstep/recents/domain/usecase/GetThumbnailPositionUseCase.kt
index f060d7d..8501382 100644
--- a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCase.kt
+++ b/quickstep/src/com/android/quickstep/recents/domain/usecase/GetThumbnailPositionUseCase.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,29 +14,30 @@
  * limitations under the License.
  */
 
-package com.android.quickstep.recents.usecase
+package com.android.quickstep.recents.domain.usecase
 
 import android.graphics.Matrix
 import android.graphics.Rect
-import com.android.quickstep.recents.data.RecentTasksRepository
 import com.android.quickstep.recents.data.RecentsDeviceProfileRepository
 import com.android.quickstep.recents.data.RecentsRotationStateRepository
-import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
-import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
+import com.android.systemui.shared.recents.model.ThumbnailData
 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
-import kotlinx.coroutines.flow.firstOrNull
 
 /** Use case for retrieving [Matrix] for positioning Thumbnail in a View */
 class GetThumbnailPositionUseCase(
     private val deviceProfileRepository: RecentsDeviceProfileRepository,
     private val rotationStateRepository: RecentsRotationStateRepository,
-    private val tasksRepository: RecentTasksRepository,
-    private val previewPositionHelper: PreviewPositionHelper = PreviewPositionHelper()
+    private val previewPositionHelper: PreviewPositionHelper,
 ) {
-    suspend fun run(taskId: Int, width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
-        val thumbnailData =
-            tasksRepository.getThumbnailById(taskId).firstOrNull() ?: return MissingThumbnail
-        val thumbnail = thumbnailData.thumbnail ?: return MissingThumbnail
+    operator fun invoke(
+        thumbnailData: ThumbnailData?,
+        width: Int,
+        height: Int,
+        isRtl: Boolean,
+    ): ThumbnailPosition {
+        val thumbnail =
+            thumbnailData?.thumbnail ?: return ThumbnailPosition(Matrix.IDENTITY_MATRIX, false)
+
         previewPositionHelper.updateThumbnailMatrix(
             Rect(0, 0, thumbnail.width, thumbnail.height),
             thumbnailData,
@@ -44,11 +45,13 @@
             height,
             deviceProfileRepository.getRecentsDeviceProfile().isLargeScreen,
             rotationStateRepository.getRecentsRotationState().activityRotation,
-            isRtl
+            isRtl,
         )
-        return MatrixScaling(
-            previewPositionHelper.matrix,
-            previewPositionHelper.isOrientationChanged
+        return ThumbnailPosition(
+            matrix = previewPositionHelper.matrix,
+            isRotated = previewPositionHelper.isOrientationChanged,
         )
     }
 }
+
+data class ThumbnailPosition(val matrix: Matrix, val isRotated: Boolean)
diff --git a/quickstep/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCase.kt b/quickstep/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCase.kt
new file mode 100644
index 0000000..02f8329
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCase.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.domain.usecase
+
+import android.graphics.Bitmap
+import android.view.Surface
+import com.android.quickstep.recents.data.RecentsRotationStateRepository
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
+import com.android.systemui.shared.recents.utilities.Utilities
+
+/**
+ * Use case responsible for validating the aspect ratio and rotation of a thumbnail against the
+ * expected values based on the view's dimensions and the current rotation state.
+ *
+ * This class checks if the thumbnail's aspect ratio significantly differs from the aspect ratio of
+ * the view it is intended to be displayed in, and if the thumbnail's rotation is consistent with
+ * the device's current rotation state.
+ *
+ * @property rotationStateRepository Repository providing the current rotation state of the device.
+ */
+class IsThumbnailValidUseCase(private val rotationStateRepository: RecentsRotationStateRepository) {
+    operator fun invoke(thumbnailData: ThumbnailData?, viewWidth: Int, viewHeight: Int): Boolean {
+        val thumbnail = thumbnailData?.thumbnail ?: return false
+        return !isInaccurateThumbnail(thumbnail, viewWidth, viewHeight, thumbnailData.rotation)
+    }
+
+    private fun isInaccurateThumbnail(
+        thumbnail: Bitmap,
+        viewWidth: Int,
+        viewHeight: Int,
+        rotation: Int,
+    ): Boolean =
+        isAspectRatioDifferentFromViewAspectRatio(
+            thumbnail = thumbnail,
+            width = viewWidth.toFloat(),
+            height = viewHeight.toFloat(),
+        ) || isRotationDifferentFromTask(rotation)
+
+    private fun isAspectRatioDifferentFromViewAspectRatio(
+        thumbnail: Bitmap,
+        width: Float,
+        height: Float,
+    ): Boolean {
+        return Utilities.isRelativePercentDifferenceGreaterThan(
+            /* first = */ width / height,
+            /* second = */ thumbnail.width / thumbnail.height.toFloat(),
+            /* bound = */ PreviewPositionHelper.MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT,
+        )
+    }
+
+    private fun isRotationDifferentFromTask(thumbnailRotation: Int): Boolean {
+        val rotationState = rotationStateRepository.getRecentsRotationState()
+        return if (rotationState.orientationHandlerRotation == Surface.ROTATION_0) {
+            (rotationState.activityRotation - thumbnailRotation) % 2 != 0
+        } else {
+            rotationState.orientationHandlerRotation != thumbnailRotation
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/domain/usecase/OrganizeDesktopTasksUseCase.kt b/quickstep/src/com/android/quickstep/recents/domain/usecase/OrganizeDesktopTasksUseCase.kt
new file mode 100644
index 0000000..4ea39d8
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/domain/usecase/OrganizeDesktopTasksUseCase.kt
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.domain.usecase
+
+import android.graphics.Rect
+import android.graphics.RectF
+import androidx.core.graphics.toRect
+import com.android.quickstep.recents.domain.model.DesktopTaskBoundsData
+
+/** This usecase is responsible for organizing desktop windows in a non-overlapping way. */
+class OrganizeDesktopTasksUseCase {
+    /**
+     * Run to layout [taskBounds] within the screen [desktopBounds]. Layout is done in 2 stages:
+     * 1. Optimal height is determined. In this stage height is bisected to find maximum height
+     *    which still allows all the windows to fit.
+     * 2. Row widths are balanced. In this stage the available width is reduced until some windows
+     *    are no longer fitting or until the difference between the narrowest and the widest rows
+     *    starts growing. Overall this achieves the goals of maximum size for previews (or maximum
+     *    row height which is equivalent assuming fixed height), balanced rows and minimal wasted
+     *    space.
+     */
+    fun run(
+        desktopBounds: Rect,
+        taskBounds: List<DesktopTaskBoundsData>,
+    ): List<DesktopTaskBoundsData> {
+        if (desktopBounds.isEmpty || taskBounds.isEmpty()) {
+            return emptyList()
+        }
+
+        // Filter out [taskBounds] with empty rects before calculating layout.
+        val validTaskBounds = taskBounds.filterNot { it.bounds.isEmpty }
+
+        if (validTaskBounds.isEmpty()) {
+            return emptyList()
+        }
+
+        val availableLayoutBounds = desktopBounds.getLayoutEffectiveBounds()
+        val resultRects = findOptimalHeightAndBalancedWidth(availableLayoutBounds, validTaskBounds)
+
+        centerTaskWindows(
+            availableLayoutBounds,
+            resultRects.maxOf { it.bottom }.toInt(),
+            resultRects,
+        )
+
+        val result = mutableListOf<DesktopTaskBoundsData>()
+        for (i in validTaskBounds.indices) {
+            result.add(DesktopTaskBoundsData(validTaskBounds[i].taskId, resultRects[i].toRect()))
+        }
+        return result
+    }
+
+    /** Calculates the effective bounds for layout by applying insets to the raw desktop bounds. */
+    private fun Rect.getLayoutEffectiveBounds() =
+        Rect(this).apply { inset(OVERVIEW_INSET_TOP_BOTTOM, OVERVIEW_INSET_LEFT_RIGHT) }
+
+    /**
+     * Determines the optimal height for task windows and balances the row widths to minimize wasted
+     * space. Returns the bounds for each task window after layout.
+     */
+    private fun findOptimalHeightAndBalancedWidth(
+        availableLayoutBounds: Rect,
+        validTaskBounds: List<DesktopTaskBoundsData>,
+    ): List<RectF> {
+        // Right bound of the narrowest row.
+        var minRight: Int
+        // Right bound of the widest row.
+        var maxRight: Int
+
+        // Keep track of the difference between the narrowest and the widest row.
+        // Initially this is set to the worst it can ever be assuming the windows fit.
+        var widthDiff = availableLayoutBounds.width()
+
+        // Initially allow the windows to occupy all available width. Shrink this available space
+        // horizontally to find the breakdown into rows that achieves the minimal [widthDiff].
+        var rightBound = availableLayoutBounds.right
+
+        // Determine the optimal height bisecting between [lowHeight] and [highHeight]. Once this
+        // optimal height is known, [heightFixed] is set to `true` and the rows are balanced by
+        // repeatedly squeezing the widest row to cause windows to overflow to the subsequent rows.
+        var lowHeight = VERTICAL_SPACE_BETWEEN_TASKS
+        var highHeight = maxOf(lowHeight, availableLayoutBounds.height() + 1)
+        var optimalHeight = 0.5f * (lowHeight + highHeight)
+        var heightFixed = false
+
+        // Repeatedly try to fit the windows [resultRects] within [rightBound]. If a maximum
+        // [optimalHeight] is found such that all window [resultRects] fit, this fitting continues
+        // while shrinking the [rightBound] in order to balance the rows. If the windows fit the
+        // [rightBound] would have been decremented at least once so it needs to be incremented once
+        // before getting out of this loop and one additional pass made to actually fit the
+        // [resultRects]. If the [resultRects] cannot fit (e.g. there are too many windows) the
+        // bisection will still finish and we might increment the [rightBound] one pixel extra
+        // which is acceptable since there is an unused margin on the right.
+        var makeLastAdjustment = false
+        var resultRects: List<RectF>
+
+        while (true) {
+            val fitWindowResult =
+                fitWindowRectsInBounds(
+                    Rect(availableLayoutBounds).apply { right = rightBound },
+                    validTaskBounds,
+                    minOf(MAXIMUM_TASK_HEIGHT, optimalHeight.toInt()),
+                )
+            val allWindowsFit = fitWindowResult.allWindowsFit
+            resultRects = fitWindowResult.calculatedBounds
+            minRight = fitWindowResult.minRight
+            maxRight = fitWindowResult.maxRight
+
+            if (heightFixed) {
+                if (!allWindowsFit) {
+                    // Revert the previous change to [rightBound] and do one last pass.
+                    rightBound++
+                    makeLastAdjustment = true
+                    break
+                }
+                // Break if all the windows are zero-width at the current scale.
+                if (maxRight <= availableLayoutBounds.left) {
+                    break
+                }
+            } else {
+                // Find the optimal row height bisecting between [lowHeight] and [highHeight].
+                if (allWindowsFit) {
+                    lowHeight = optimalHeight.toInt()
+                } else {
+                    highHeight = optimalHeight.toInt()
+                }
+                optimalHeight = 0.5f * (lowHeight + highHeight)
+                // When height can no longer be improved, start balancing the rows.
+                if (optimalHeight.toInt() == lowHeight) {
+                    heightFixed = true
+                }
+            }
+
+            if (allWindowsFit && heightFixed) {
+                if (maxRight - minRight <= widthDiff) {
+                    // Row alignment is getting better. Try to shrink the [rightBound] in order to
+                    // squeeze the widest row.
+                    rightBound = maxRight - 1
+                    widthDiff = maxRight - minRight
+                } else {
+                    // Row alignment is getting worse.
+                    // Revert the previous change to [rightBound] and do one last pass.
+                    rightBound++
+                    makeLastAdjustment = true
+                    break
+                }
+            }
+        }
+
+        // Once the windows no longer fit, the change to [rightBound] was reverted. Perform one last
+        // pass to position the [resultRects].
+        if (makeLastAdjustment) {
+            val fitWindowResult =
+                fitWindowRectsInBounds(
+                    Rect(availableLayoutBounds).apply { right = rightBound },
+                    validTaskBounds,
+                    minOf(MAXIMUM_TASK_HEIGHT, optimalHeight.toInt()),
+                )
+            resultRects = fitWindowResult.calculatedBounds
+        }
+
+        return resultRects
+    }
+
+    /**
+     * Data structure to hold the returned result of [fitWindowRectsInBounds] function.
+     * [allWindowsFit] specifies whether all windows can be fit into the provided layout bounds.
+     * [calculatedBounds] specifies the output bounds for all provided task windows. [minRight]
+     * specifies the right bound of the narrowest row. [maxRight] specifies the right bound of the
+     * widest rows.
+     */
+    data class FitWindowResult(
+        val allWindowsFit: Boolean,
+        val calculatedBounds: List<RectF>,
+        val minRight: Int,
+        val maxRight: Int,
+    )
+
+    /**
+     * Attempts to fit all [taskBounds] inside [layoutBounds]. The method ensures that the returned
+     * output bounds list has appropriate size and populates it with the values placing task windows
+     * next to each other left-to-right in rows of equal [optimalWindowHeight].
+     */
+    private fun fitWindowRectsInBounds(
+        layoutBounds: Rect,
+        taskBounds: List<DesktopTaskBoundsData>,
+        optimalWindowHeight: Int,
+    ): FitWindowResult {
+        val numTasks = taskBounds.size
+        val outRects = mutableListOf<RectF>()
+
+        // Start in the top-left corner of [layoutBounds].
+        var left = layoutBounds.left
+        var top = layoutBounds.top
+
+        // Right bound of the narrowest row.
+        var minRight = layoutBounds.right
+        // Right bound of the widest row.
+        var maxRight = layoutBounds.left
+
+        var allWindowsFit = true
+        for (i in 0 until numTasks) {
+            val taskBounds = taskBounds[i].bounds
+
+            // Use the height to calculate the width
+            val scale = optimalWindowHeight / taskBounds.height().toFloat()
+            val width = (taskBounds.width() * scale).toInt()
+            val optimalRowHeight = optimalWindowHeight + VERTICAL_SPACE_BETWEEN_TASKS
+
+            if ((left + width + HORIZONTAL_SPACE_BETWEEN_TASKS) > layoutBounds.right) {
+                // Move to the next row if possible.
+                minRight = minOf(minRight, left)
+                maxRight = maxOf(maxRight, left)
+                top += optimalRowHeight
+
+                // Check if the new row reaches the bottom or if the first item in the new
+                // row does not fit within the available width.
+                if (
+                    (top + optimalRowHeight) > layoutBounds.bottom ||
+                        layoutBounds.left + width + HORIZONTAL_SPACE_BETWEEN_TASKS >
+                            layoutBounds.right
+                ) {
+                    allWindowsFit = false
+                    break
+                }
+                left = layoutBounds.left
+            }
+
+            // Position the current rect.
+            outRects.add(
+                RectF(
+                    left.toFloat(),
+                    top.toFloat(),
+                    (left + width).toFloat(),
+                    (top + optimalWindowHeight).toFloat(),
+                )
+            )
+
+            // Increment horizontal position.
+            left += (width + HORIZONTAL_SPACE_BETWEEN_TASKS)
+        }
+
+        // Update the narrowest and widest row width for the last row.
+        minRight = minOf(minRight, left)
+        maxRight = maxOf(maxRight, left)
+
+        return FitWindowResult(allWindowsFit, outRects, minRight, maxRight)
+    }
+
+    /** Centers task windows in the center of Overview. */
+    private fun centerTaskWindows(layoutBounds: Rect, maxBottom: Int, outWindowRects: List<RectF>) {
+        if (outWindowRects.isEmpty()) {
+            return
+        }
+
+        val currentRowUnionRange = RectF(outWindowRects[0])
+        var currentRowY = outWindowRects[0].top
+        var currentRowFirstItemIndex = 0
+        val offsetY = (layoutBounds.bottom - maxBottom) / 2f
+
+        // Batch process to center overview desktop task windows within the same row.
+        fun batchCenterDesktopTaskWindows(endIndex: Int) {
+            // Calculate the shift amount required to center the desktop task items.
+            val rangeCenterX = (currentRowUnionRange.left + currentRowUnionRange.right) / 2f
+            val currentDiffX = (layoutBounds.centerX() - rangeCenterX).coerceAtLeast(0f)
+            for (j in currentRowFirstItemIndex until endIndex) {
+                outWindowRects[j].offset(currentDiffX, offsetY)
+            }
+        }
+
+        outWindowRects.forEachIndexed { index, rect ->
+            if (rect.top != currentRowY) {
+                // As a new row begins processing, batch-shift the previous row's rects
+                // and reset its parameters.
+                batchCenterDesktopTaskWindows(index)
+                currentRowUnionRange.set(rect)
+                currentRowY = rect.top
+                currentRowFirstItemIndex = index
+            }
+
+            // Extend the range by adding the [rect]'s width and extra in-between items
+            // spacing.
+            currentRowUnionRange.right = rect.right
+        }
+
+        // Post-processing rects in the last row.
+        batchCenterDesktopTaskWindows(outWindowRects.size)
+    }
+
+    private companion object {
+        const val VERTICAL_SPACE_BETWEEN_TASKS = 24
+        const val HORIZONTAL_SPACE_BETWEEN_TASKS = 24
+        const val OVERVIEW_INSET_TOP_BOTTOM = 16
+        const val OVERVIEW_INSET_LEFT_RIGHT = 16
+        const val MAXIMUM_TASK_HEIGHT = 800
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
new file mode 100644
index 0000000..aa1c236
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.ui.mapper
+
+import android.view.View.OnClickListener
+import com.android.quickstep.recents.ui.viewmodel.TaskData
+import com.android.quickstep.task.thumbnail.TaskHeaderUiState
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
+
+object TaskUiStateMapper {
+
+    /**
+     * Converts a [TaskData] object into a [TaskHeaderUiState] for display in the UI.
+     *
+     * This function handles different types of [TaskData] and determines the appropriate UI state
+     * based on the data and provided flags.
+     *
+     * @param taskData The [TaskData] to convert. Can be null or a specific subclass.
+     * @param hasHeader A flag indicating whether the UI should display a header.
+     * @param clickCloseListener A callback when the close button in the UI is clicked.
+     * @return A [TaskHeaderUiState] representing the UI state for the given task data.
+     */
+    fun toTaskHeaderState(
+        taskData: TaskData?,
+        hasHeader: Boolean,
+        clickCloseListener: OnClickListener?,
+    ): TaskHeaderUiState =
+        when {
+            taskData !is TaskData.Data -> TaskHeaderUiState.HideHeader
+            canHeaderBeCreated(taskData, hasHeader, clickCloseListener) -> {
+                TaskHeaderUiState.ShowHeader(
+                    TaskHeaderUiState.ThumbnailHeader(
+                        // TODO(http://b/353965691): figure out what to do when `icon` or
+                        // `titleDescription` is null.
+                        taskData.icon!!,
+                        taskData.titleDescription!!,
+                        clickCloseListener!!,
+                    )
+                )
+            }
+            else -> TaskHeaderUiState.HideHeader
+        }
+
+    /**
+     * Converts a [TaskData] object into a [TaskThumbnailUiState] for display in the UI.
+     *
+     * This function handles different types of [TaskData] and determines the appropriate UI state
+     * based on the data and provided flags.
+     *
+     * @param taskData The [TaskData] to convert. Can be null or a specific subclass.
+     * @param isLiveTile A flag indicating whether the task data represents live tile.
+     * @return A [TaskThumbnailUiState] representing the UI state for the given task data.
+     */
+    fun toTaskThumbnailUiState(taskData: TaskData?, isLiveTile: Boolean): TaskThumbnailUiState =
+        when {
+            taskData !is TaskData.Data -> Uninitialized
+            isLiveTile -> LiveTile
+            isBackgroundOnly(taskData) -> BackgroundOnly(taskData.backgroundColor)
+            isSnapshotSplash(taskData) ->
+                SnapshotSplash(
+                    Snapshot(
+                        taskData.thumbnailData?.thumbnail!!,
+                        taskData.thumbnailData.rotation,
+                        taskData.backgroundColor,
+                    ),
+                    taskData.icon,
+                )
+
+            else -> Uninitialized
+        }
+
+    private fun isBackgroundOnly(taskData: TaskData.Data) =
+        taskData.isLocked || taskData.thumbnailData == null
+
+    private fun isSnapshotSplash(taskData: TaskData.Data) =
+        taskData.thumbnailData?.thumbnail != null && !taskData.isLocked
+
+    private fun canHeaderBeCreated(
+        taskData: TaskData.Data,
+        hasHeader: Boolean,
+        clickCloseListener: OnClickListener?,
+    ) =
+        hasHeader &&
+            taskData.icon != null &&
+            taskData.titleDescription != null &&
+            clickCloseListener != null
+}
diff --git a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/DesktopTaskViewModel.kt b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/DesktopTaskViewModel.kt
new file mode 100644
index 0000000..4de0b90
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/DesktopTaskViewModel.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.ui.viewmodel
+
+import android.graphics.Rect
+import android.util.Size
+import com.android.quickstep.recents.domain.model.DesktopTaskBoundsData
+import com.android.quickstep.recents.domain.usecase.OrganizeDesktopTasksUseCase
+
+/** ViewModel used for [com.android.quickstep.views.DesktopTaskView]. */
+class DesktopTaskViewModel(private val organizeDesktopTasksUseCase: OrganizeDesktopTasksUseCase) {
+    /** Positions for desktop tasks as calculated by [organizeDesktopTasksUseCase] */
+    var organizedDesktopTaskPositions = emptyList<DesktopTaskBoundsData>()
+        private set
+
+    /**
+     * Computes new task positions using [organizeDesktopTasksUseCase]. The result is stored in
+     * [organizedDesktopTaskPositions]. This is used for the exploded desktop view where the usecase
+     * will scale and translate tasks so that they don't overlap.
+     *
+     * @param desktopSize the size available for organizing the tasks.
+     * @param defaultPositions the tasks and their bounds as they appear on a desktop.
+     */
+    fun organizeDesktopTasks(desktopSize: Size, defaultPositions: List<DesktopTaskBoundsData>) {
+        organizedDesktopTaskPositions =
+            organizeDesktopTasksUseCase.run(
+                desktopBounds = Rect(0, 0, desktopSize.width, desktopSize.height),
+                taskBounds = defaultPositions,
+            )
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt
new file mode 100644
index 0000000..8a6a805
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskTileUiState.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.ui.viewmodel
+
+import android.graphics.drawable.Drawable
+import com.android.systemui.shared.recents.model.ThumbnailData
+
+/**
+ * This class represents the UI state to be consumed by TaskView, GroupTaskView and DesktopTaskView.
+ * Data class representing the state of a list of tasks.
+ *
+ * This class encapsulates a list of [TaskTileUiState] objects, along with a flag indicating whether
+ * the data is being used for a live tile display.
+ *
+ * @property tasks The list of [TaskTileUiState] objects representing the individual tasks.
+ * @property isLiveTile Indicates whether this data is intended for a live tile. If `true`, the
+ *   running app will be displayed instead of the thumbnail.
+ * @property sysUiStatusNavFlags Flags for status bar and navigation bar
+ */
+data class TaskTileUiState(
+    val tasks: List<TaskData>,
+    val isLiveTile: Boolean,
+    val hasHeader: Boolean,
+    val sysUiStatusNavFlags: Int,
+    val taskOverlayEnabled: Boolean,
+)
+
+sealed class TaskData {
+    abstract val taskId: Int
+
+    /** When no data was found for the TaskId provided */
+    data class NoData(override val taskId: Int) : TaskData()
+
+    /**
+     * This class provides UI information related to a Task (App) to be displayed within a TaskView.
+     *
+     * @property taskId Identifier of the task
+     * @property title App title
+     * @property titleDescription App content description
+     * @property icon App icon
+     * @property thumbnailData Information related to the last snapshot retrieved from the app
+     * @property backgroundColor The background color of the task.
+     * @property isLocked Indicates whether the task is locked or not.
+     */
+    data class Data(
+        override val taskId: Int,
+        val title: String?,
+        val titleDescription: String?,
+        val icon: Drawable?,
+        val thumbnailData: ThumbnailData?,
+        val backgroundColor: Int,
+        val isLocked: Boolean,
+    ) : TaskData()
+}
diff --git a/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
new file mode 100644
index 0000000..09e2071
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModel.kt
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.ui.viewmodel
+
+import android.annotation.ColorInt
+import android.util.Log
+import androidx.core.graphics.ColorUtils
+import com.android.launcher3.util.coroutines.DispatcherProvider
+import com.android.quickstep.recents.domain.model.TaskId
+import com.android.quickstep.recents.domain.model.TaskModel
+import com.android.quickstep.recents.domain.usecase.GetSysUiStatusNavFlagsUseCase
+import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
+import com.android.quickstep.recents.domain.usecase.GetThumbnailPositionUseCase
+import com.android.quickstep.recents.domain.usecase.IsThumbnailValidUseCase
+import com.android.quickstep.recents.domain.usecase.ThumbnailPosition
+import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.views.TaskViewType
+import com.android.quickstep.views.TaskViewType.SINGLE
+import com.android.systemui.shared.recents.model.ThumbnailData
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+
+/**
+ * ViewModel used for [com.android.quickstep.views.TaskView],
+ * [com.android.quickstep.views.DesktopTaskView] and [com.android.quickstep.views.GroupedTaskView].
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class TaskViewModel(
+    private val taskViewType: TaskViewType,
+    recentsViewData: RecentsViewData,
+    private val getTaskUseCase: GetTaskUseCase,
+    private val getSysUiStatusNavFlagsUseCase: GetSysUiStatusNavFlagsUseCase,
+    private val isThumbnailValidUseCase: IsThumbnailValidUseCase,
+    private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
+    dispatcherProvider: DispatcherProvider,
+) {
+    private var taskIds = MutableStateFlow(emptySet<Int>())
+
+    private val isLiveTile =
+        combine(
+                taskIds,
+                recentsViewData.runningTaskIds,
+                recentsViewData.runningTaskShowScreenshot,
+            ) { taskIds, runningTaskIds, runningTaskShowScreenshot ->
+                runningTaskIds == taskIds && !runningTaskShowScreenshot
+            }
+            .distinctUntilChanged()
+
+    private val taskData =
+        taskIds.flatMapLatest { ids ->
+            // Combine Tasks requests
+            combine(
+                ids.map { id -> getTaskUseCase(id).map { taskModel -> id to taskModel } },
+                ::mapToTaskData,
+            )
+        }
+
+    private val overlayEnabled =
+        combine(recentsViewData.overlayEnabled, recentsViewData.settledFullyVisibleTaskIds) {
+                isOverlayEnabled,
+                settledFullyVisibleTaskIds ->
+                taskViewType == SINGLE &&
+                    isOverlayEnabled &&
+                    settledFullyVisibleTaskIds.any { it in taskIds.value }
+            }
+            .distinctUntilChanged()
+
+    val state: Flow<TaskTileUiState> =
+        combine(taskData, isLiveTile, overlayEnabled, ::mapToTaskTile)
+            .distinctUntilChanged()
+            .flowOn(dispatcherProvider.background)
+
+    fun bind(vararg taskId: TaskId) {
+        taskIds.value = taskId.toSet().also { Log.d(TAG, "bind: $it") }
+    }
+
+    fun isThumbnailValid(thumbnail: ThumbnailData?, width: Int, height: Int): Boolean =
+        isThumbnailValidUseCase(thumbnail, width, height)
+
+    fun getThumbnailPosition(
+        thumbnail: ThumbnailData?,
+        width: Int,
+        height: Int,
+        isRtl: Boolean,
+    ): ThumbnailPosition =
+        getThumbnailPositionUseCase(
+            thumbnailData = thumbnail,
+            width = width,
+            height = height,
+            isRtl = isRtl,
+        )
+
+    private fun mapToTaskTile(
+        tasks: List<TaskData>,
+        isLiveTile: Boolean,
+        overlayEnabled: Boolean,
+    ): TaskTileUiState {
+        val firstThumbnailData = (tasks.firstOrNull() as? TaskData.Data)?.thumbnailData
+        return TaskTileUiState(
+            tasks = tasks,
+            isLiveTile = isLiveTile,
+            hasHeader = taskViewType == TaskViewType.DESKTOP,
+            sysUiStatusNavFlags = getSysUiStatusNavFlagsUseCase(firstThumbnailData),
+            taskOverlayEnabled = overlayEnabled,
+        )
+    }
+
+    private fun mapToTaskData(result: Array<Pair<TaskId, TaskModel?>>): List<TaskData> =
+        result.map { mapToTaskData(it.first, it.second) }
+
+    private fun mapToTaskData(taskId: TaskId, result: TaskModel?): TaskData =
+        result?.let {
+            TaskData.Data(
+                taskId = taskId,
+                title = result.title,
+                titleDescription = result.titleDescription,
+                icon = result.icon,
+                thumbnailData = result.thumbnail,
+                backgroundColor = result.backgroundColor.removeAlpha(),
+                isLocked = result.isLocked,
+            )
+        } ?: TaskData.NoData(taskId)
+
+    @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff)
+
+    private companion object {
+        const val TAG = "TaskViewModel"
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
deleted file mode 100644
index 3aa808e..0000000
--- a/quickstep/src/com/android/quickstep/recents/usecase/GetThumbnailUseCase.kt
+++ /dev/null
@@ -1,30 +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.quickstep.recents.usecase
-
-import android.graphics.Bitmap
-import com.android.quickstep.recents.data.RecentTasksRepository
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.runBlocking
-
-/** Use case for retrieving thumbnail. */
-class GetThumbnailUseCase(private val taskRepository: RecentTasksRepository) {
-    /** Returns the latest thumbnail associated with [taskId] if loaded, or null otherwise */
-    fun run(taskId: Int): Bitmap? = runBlocking {
-        taskRepository.getThumbnailById(taskId).firstOrNull()?.thumbnail
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt b/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt
deleted file mode 100644
index 1d19c7d..0000000
--- a/quickstep/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCase.kt
+++ /dev/null
@@ -1,53 +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.quickstep.recents.usecase
-
-import android.view.WindowInsetsController
-import com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV
-import com.android.launcher3.util.SystemUiController.FLAG_DARK_STATUS
-import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV
-import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS
-import com.android.quickstep.recents.data.RecentTasksRepository
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.runBlocking
-
-/** UseCase to calculate flags for status bar and navigation bar */
-class SysUiStatusNavFlagsUseCase(private val taskRepository: RecentTasksRepository) {
-    fun getSysUiStatusNavFlags(taskId: Int): Int {
-        val thumbnailData =
-            runBlocking { taskRepository.getThumbnailById(taskId).firstOrNull() } ?: return 0
-
-        val thumbnailAppearance = thumbnailData.appearance
-        var flags = 0
-        flags =
-            flags or
-                if (
-                    thumbnailAppearance and WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS != 0
-                )
-                    FLAG_LIGHT_STATUS
-                else FLAG_DARK_STATUS
-        flags =
-            flags or
-                if (
-                    thumbnailAppearance and
-                        WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS != 0
-                )
-                    FLAG_LIGHT_NAV
-                else FLAG_DARK_NAV
-        return flags
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/recents/usecase/ThumbnailPositionState.kt b/quickstep/src/com/android/quickstep/recents/usecase/ThumbnailPositionState.kt
deleted file mode 100644
index 1a1bef7..0000000
--- a/quickstep/src/com/android/quickstep/recents/usecase/ThumbnailPositionState.kt
+++ /dev/null
@@ -1,26 +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.quickstep.recents.usecase
-
-import android.graphics.Matrix
-
-/** State on how a task Thumbnail can fit on given canvas */
-sealed class ThumbnailPositionState {
-    data object MissingThumbnail : ThumbnailPositionState()
-
-    data class MatrixScaling(val matrix: Matrix, val isRotated: Boolean) : ThumbnailPositionState()
-}
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
index 6ccf372..2465a46 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewData.kt
@@ -20,8 +20,6 @@
 
 // This is far from complete but serves the purpose of enabling refactoring in other areas
 class RecentsViewData {
-    val fullscreenProgress = MutableStateFlow(1f)
-
     // Whether the current RecentsView state supports task overlays.
     // TODO(b/331753115): Derive from RecentsView state flow once migrated to MVVM.
     val overlayEnabled = MutableStateFlow(false)
@@ -29,11 +27,6 @@
     // The settled set of visible taskIds that is updated after RecentsView scroll settles.
     val settledFullyVisibleTaskIds = MutableStateFlow(emptySet<Int>())
 
-    // Color tint on foreground scrim
-    val tintAmount = MutableStateFlow(0f)
-
-    val thumbnailSplashProgress = MutableStateFlow(0f)
-
     // A list of taskIds that are associated with a RecentsAnimationController. */
     val runningTaskIds = MutableStateFlow(emptySet<Int>())
 
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
index cfebb81..5ff8aaa 100644
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
+++ b/quickstep/src/com/android/quickstep/recents/viewmodel/RecentsViewModel.kt
@@ -34,10 +34,6 @@
         recentsTasksRepository.setVisibleTasks(visibleTaskIdList.toSet())
     }
 
-    fun updateFullscreenProgress(fullscreenProgress: Float) {
-        recentsViewData.fullscreenProgress.value = fullscreenProgress
-    }
-
     fun updateTasksFullyVisible(taskIds: Set<Int>) {
         recentsViewData.settledFullyVisibleTaskIds.value = taskIds
     }
@@ -46,14 +42,6 @@
         recentsViewData.overlayEnabled.value = isOverlayEnabled
     }
 
-    fun setTintAmount(tintAmount: Float) {
-        recentsViewData.tintAmount.value = tintAmount
-    }
-
-    fun updateThumbnailSplashProgress(taskThumbnailSplashAlpha: Float) {
-        recentsViewData.thumbnailSplashProgress.value = taskThumbnailSplashAlpha
-    }
-
     suspend fun waitForThumbnailsToUpdate(updatedThumbnails: Map<Int, ThumbnailData>?) {
         if (updatedThumbnails.isNullOrEmpty()) return
         combine(
diff --git a/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt b/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt
deleted file mode 100644
index 168c1e0..0000000
--- a/quickstep/src/com/android/quickstep/recents/viewmodel/TaskContainerViewModel.kt
+++ /dev/null
@@ -1,38 +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.quickstep.recents.viewmodel
-
-import android.graphics.Bitmap
-import com.android.quickstep.recents.usecase.GetThumbnailUseCase
-import com.android.quickstep.recents.usecase.SysUiStatusNavFlagsUseCase
-import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
-import kotlinx.coroutines.flow.firstOrNull
-import kotlinx.coroutines.runBlocking
-
-class TaskContainerViewModel(
-    private val sysUiStatusNavFlagsUseCase: SysUiStatusNavFlagsUseCase,
-    private val getThumbnailUseCase: GetThumbnailUseCase,
-    private val splashAlphaUseCase: SplashAlphaUseCase,
-) {
-    fun getThumbnail(taskId: Int): Bitmap? = getThumbnailUseCase.run(taskId)
-
-    fun getSysUiStatusNavFlags(taskId: Int) =
-        sysUiStatusNavFlagsUseCase.getSysUiStatusNavFlags(taskId)
-
-    fun shouldShowThumbnailSplash(taskId: Int): Boolean =
-        (runBlocking { splashAlphaUseCase.execute(taskId).firstOrNull() } ?: 0f) > 0f
-}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCase.kt b/quickstep/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCase.kt
deleted file mode 100644
index 7673c71..0000000
--- a/quickstep/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCase.kt
+++ /dev/null
@@ -1,90 +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.quickstep.task.thumbnail
-
-import android.graphics.Bitmap
-import android.view.Surface
-import com.android.quickstep.recents.data.RecentTasksRepository
-import com.android.quickstep.recents.data.RecentsRotationStateRepository
-import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.viewmodel.TaskContainerData
-import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
-import com.android.systemui.shared.recents.utilities.Utilities
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-
-class SplashAlphaUseCase(
-    private val recentsViewData: RecentsViewData,
-    private val taskContainerData: TaskContainerData,
-    private val taskThumbnailViewData: TaskThumbnailViewData,
-    private val tasksRepository: RecentTasksRepository,
-    private val rotationStateRepository: RecentsRotationStateRepository,
-) {
-    fun execute(taskId: Int): Flow<Float> =
-        combine(
-                taskThumbnailViewData.width,
-                taskThumbnailViewData.height,
-                tasksRepository.getThumbnailById(taskId),
-                taskContainerData.thumbnailSplashProgress,
-                recentsViewData.thumbnailSplashProgress
-            ) { width, height, thumbnailData, taskSplashProgress, globalSplashProgress ->
-                val thumbnail = thumbnailData?.thumbnail
-                when {
-                    thumbnail == null -> 0f
-                    taskSplashProgress > 0f -> taskSplashProgress
-                    globalSplashProgress > 0f &&
-                        isInaccurateThumbnail(thumbnail, thumbnailData.rotation, width, height) ->
-                        globalSplashProgress
-                    else -> 0f
-                }
-            }
-            .distinctUntilChanged()
-
-    private fun isInaccurateThumbnail(
-        thumbnail: Bitmap,
-        thumbnailRotation: Int,
-        width: Int,
-        height: Int
-    ): Boolean {
-        return isThumbnailAspectRatioDifferentFromThumbnailData(thumbnail, width, height) ||
-            isThumbnailRotationDifferentFromTask(thumbnailRotation)
-    }
-
-    private fun isThumbnailAspectRatioDifferentFromThumbnailData(
-        thumbnail: Bitmap,
-        viewWidth: Int,
-        viewHeight: Int
-    ): Boolean {
-        val viewAspect: Float = viewWidth / viewHeight.toFloat()
-        val thumbnailAspect: Float = thumbnail.width / thumbnail.height.toFloat()
-        return Utilities.isRelativePercentDifferenceGreaterThan(
-            viewAspect,
-            thumbnailAspect,
-            PreviewPositionHelper.MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT
-        )
-    }
-
-    private fun isThumbnailRotationDifferentFromTask(thumbnailRotation: Int): Boolean {
-        val rotationState = rotationStateRepository.getRecentsRotationState()
-        return if (rotationState.orientationHandlerRotation == Surface.ROTATION_0) {
-            (rotationState.activityRotation - thumbnailRotation) % 2 != 0
-        } else {
-            rotationState.orientationHandlerRotation != thumbnailRotation
-        }
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskContentView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskContentView.kt
new file mode 100644
index 0000000..2dbd811
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskContentView.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.thumbnail
+
+import android.content.Context
+import android.graphics.Outline
+import android.graphics.Path
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewOutlineProvider
+import android.widget.LinearLayout
+import androidx.core.view.isInvisible
+import com.android.launcher3.Flags.enableDesktopExplodedView
+import com.android.launcher3.Flags.enableRefactorTaskThumbnail
+import com.android.launcher3.R
+import com.android.launcher3.util.ViewPool
+import com.android.quickstep.views.TaskHeaderView
+import com.android.quickstep.views.TaskThumbnailViewDeprecated
+
+/**
+ * TaskContentView is a wrapper around the TaskHeaderView and TaskThumbnailView. It is a sibling to
+ * DWB, AiAi (TaskOverlay).
+ */
+class TaskContentView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+    LinearLayout(context, attrs), ViewPool.Reusable {
+
+    private var taskHeaderView: TaskHeaderView? = null
+    private var taskThumbnailView: TaskThumbnailView? = null
+    private var taskThumbnailViewDeprecated: TaskThumbnailViewDeprecated? = null
+    private var onSizeChanged: ((width: Int, height: Int) -> Unit)? = null
+    private val outlinePath = Path()
+
+    /**
+     * Sets the outline bounds of the view. Default to use view's bound as outline when set to null.
+     */
+    var outlineBounds: Rect? = null
+        set(value) {
+            field = value
+            invalidateOutline()
+        }
+
+    private val bounds = Rect()
+
+    var cornerRadius: Float = 0f
+        set(value) {
+            field = value
+            invalidateOutline()
+        }
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        maybeCreateHeader()
+        createTaskThumbnailView()
+    }
+
+    override fun setScaleX(scaleX: Float) {
+        super.setScaleX(scaleX)
+        taskThumbnailView?.parentScaleXUpdated(scaleX)
+    }
+
+    override fun setScaleY(scaleY: Float) {
+        super.setScaleY(scaleY)
+        taskThumbnailView?.parentScaleYUpdated(scaleY)
+    }
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        clipToOutline = true
+        outlineProvider =
+            object : ViewOutlineProvider() {
+                override fun getOutline(view: View, outline: Outline) {
+                    val outlineRect = outlineBounds ?: bounds
+                    outlinePath.apply {
+                        rewind()
+                        addRoundRect(
+                            outlineRect.left.toFloat(),
+                            outlineRect.top.toFloat(),
+                            outlineRect.right.toFloat(),
+                            outlineRect.bottom.toFloat(),
+                            cornerRadius / scaleX,
+                            cornerRadius / scaleY,
+                            Path.Direction.CW,
+                        )
+                    }
+                    outline.setPath(outlinePath)
+                }
+            }
+    }
+
+    override fun onRecycle() {
+        taskHeaderView?.isInvisible = true
+        onSizeChanged = null
+        outlineBounds = null
+        taskThumbnailView?.onRecycle()
+        taskThumbnailViewDeprecated?.onRecycle()
+    }
+
+    fun doOnSizeChange(action: (width: Int, height: Int) -> Unit) {
+        onSizeChanged = action
+    }
+
+    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+        super.onSizeChanged(w, h, oldw, oldh)
+        onSizeChanged?.invoke(width, height)
+        bounds.set(0, 0, w, h)
+        invalidateOutline()
+    }
+
+    private fun maybeCreateHeader() {
+        if (enableDesktopExplodedView() && taskHeaderView == null) {
+            taskHeaderView =
+                LayoutInflater.from(context).inflate(R.layout.task_header_view, this, false)
+                    as TaskHeaderView
+            addView(taskHeaderView)
+        }
+    }
+
+    private fun createTaskThumbnailView() {
+        if (taskThumbnailView == null) {
+            if (enableRefactorTaskThumbnail()) {
+                taskThumbnailView =
+                    LayoutInflater.from(context).inflate(R.layout.task_thumbnail, this, false)
+                        as TaskThumbnailView
+                addView(taskThumbnailView)
+            } else {
+                taskThumbnailViewDeprecated =
+                    LayoutInflater.from(context)
+                        .inflate(R.layout.task_thumbnail_deprecated, this, false)
+                        as TaskThumbnailViewDeprecated
+                addView(taskThumbnailViewDeprecated)
+            }
+        }
+    }
+
+    fun setState(
+        taskHeaderState: TaskHeaderUiState,
+        taskThumbnailUiState: TaskThumbnailUiState,
+        taskId: Int?,
+    ) {
+        taskHeaderView?.setState(taskHeaderState)
+        taskThumbnailView?.setState(taskThumbnailUiState, taskId)
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskHeaderUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskHeaderUiState.kt
new file mode 100644
index 0000000..09fb540
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskHeaderUiState.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.thumbnail
+
+import android.graphics.drawable.Drawable
+import android.view.View
+
+sealed class TaskHeaderUiState {
+    data class ShowHeader(val header: ThumbnailHeader) : TaskHeaderUiState()
+
+    data object HideHeader : TaskHeaderUiState()
+
+    data class ThumbnailHeader(
+        val icon: Drawable,
+        val title: String,
+        val clickCloseListener: View.OnClickListener,
+    )
+}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt
deleted file mode 100644
index 5fb5b90..0000000
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskOverlayUiState.kt
+++ /dev/null
@@ -1,26 +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.quickstep.task.thumbnail
-
-import android.graphics.Bitmap
-
-/** Ui state for [com.android.quickstep.TaskOverlayFactory.TaskOverlay] */
-sealed class TaskOverlayUiState {
-    data object Disabled : TaskOverlayUiState()
-
-    data class Enabled(val isRealSnapshot: Boolean, val thumbnail: Bitmap?) : TaskOverlayUiState()
-}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
index 36a86f2..a5c9ac0 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
@@ -24,18 +24,16 @@
 sealed class TaskThumbnailUiState {
     data object Uninitialized : TaskThumbnailUiState()
 
-    data object LiveTile : TaskThumbnailUiState()
-
     data class BackgroundOnly(@ColorInt val backgroundColor: Int) : TaskThumbnailUiState()
 
-    data class SnapshotSplash(
-        val snapshot: Snapshot,
-        val splash: Drawable?,
-    ) : TaskThumbnailUiState()
+    data object LiveTile : TaskThumbnailUiState()
+
+    data class SnapshotSplash(val snapshot: Snapshot, val splash: Drawable?) :
+        TaskThumbnailUiState()
 
     data class Snapshot(
         val bitmap: Bitmap,
         @Surface.Rotation val thumbnailRotation: Int,
-        @ColorInt val backgroundColor: Int
+        @ColorInt val backgroundColor: Int,
     )
 }
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 0c783d3..78a16f1 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -18,56 +18,36 @@
 
 import android.content.Context
 import android.graphics.Color
-import android.graphics.Outline
-import android.graphics.Rect
+import android.graphics.Matrix
+import android.graphics.drawable.ShapeDrawable
 import android.util.AttributeSet
 import android.util.Log
 import android.view.View
-import android.view.ViewOutlineProvider
+import android.widget.FrameLayout
 import androidx.annotation.ColorInt
-import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.view.isInvisible
+import com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA
 import com.android.launcher3.R
-import com.android.launcher3.util.ViewPool
-import com.android.quickstep.recents.di.RecentsDependencies
-import com.android.quickstep.recents.di.get
+import com.android.launcher3.util.MultiPropertyFactory
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
-import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
 import com.android.quickstep.views.FixedSizeImageView
-import kotlinx.coroutines.CoroutineName
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
 
-class TaskThumbnailView : ConstraintLayout, ViewPool.Reusable {
-    private lateinit var viewData: TaskThumbnailViewData
-    private lateinit var viewModel: TaskThumbnailViewModel
-
-    private lateinit var viewAttachedScope: CoroutineScope
-
+class TaskThumbnailView : FrameLayout {
     private val scrimView: View by lazy { findViewById(R.id.task_thumbnail_scrim) }
     private val liveTileView: LiveTileView by lazy { findViewById(R.id.task_thumbnail_live_tile) }
     private val thumbnailView: FixedSizeImageView by lazy { findViewById(R.id.task_thumbnail) }
     private val splashBackground: View by lazy { findViewById(R.id.splash_background) }
     private val splashIcon: FixedSizeImageView by lazy { findViewById(R.id.splash_icon) }
+    private val dimAlpha: MultiPropertyFactory<View> by lazy {
+        MultiPropertyFactory(scrimView, VIEW_ALPHA, ScrimViewAlpha.entries.size, ::maxOf)
+    }
 
     private var uiState: TaskThumbnailUiState = Uninitialized
 
-    private val bounds = Rect()
-
-    var cornerRadius: Float = 0f
-        set(value) {
-            field = value
-            invalidateOutline()
-        }
-
     constructor(context: Context) : super(context)
 
     constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
@@ -78,83 +58,49 @@
         defStyleAttr: Int,
     ) : super(context, attrs, defStyleAttr)
 
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        viewAttachedScope =
-            CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineName("TaskThumbnailView"))
-        viewData = RecentsDependencies.get(this)
-        updateViewDataValues()
-        viewModel = RecentsDependencies.get(this)
-        viewModel.uiState
-            .onEach { viewModelUiState ->
-                Log.d(TAG, "viewModelUiState changed from: $uiState to: $viewModelUiState")
-                uiState = viewModelUiState
-                resetViews()
-                when (viewModelUiState) {
-                    is Uninitialized -> {}
-                    is LiveTile -> drawLiveWindow()
-                    is SnapshotSplash -> drawSnapshotSplash(viewModelUiState)
-                    is BackgroundOnly -> drawBackground(viewModelUiState.backgroundColor)
-                }
-            }
-            .launchIn(viewAttachedScope)
-        viewModel.dimProgress
-            .onEach { dimProgress -> scrimView.alpha = dimProgress }
-            .launchIn(viewAttachedScope)
-        viewModel.splashAlpha
-            .onEach { splashAlpha ->
-                splashBackground.alpha = splashAlpha
-                splashIcon.alpha = splashAlpha
-            }
-            .launchIn(viewAttachedScope)
-
-        clipToOutline = true
-        outlineProvider =
-            object : ViewOutlineProvider() {
-                override fun getOutline(view: View, outline: Outline) {
-                    outline.setRoundRect(bounds, cornerRadius)
-                }
-            }
-    }
-
-    override fun onDetachedFromWindow() {
-        super.onDetachedFromWindow()
-        viewAttachedScope.cancel("TaskThumbnailView detaching from window")
-    }
-
-    override fun onRecycle() {
+    fun onRecycle() {
         uiState = Uninitialized
+        resetViews()
     }
 
-    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
-        super.onLayout(changed, left, top, right, bottom)
-        if (changed) {
-            updateViewDataValues()
+    fun setState(state: TaskThumbnailUiState, taskId: Int? = null) {
+        if (uiState == state) return
+        logDebug("taskId: $taskId - uiState changed from: $uiState to: $state")
+        uiState = state
+        resetViews()
+        when (state) {
+            is Uninitialized -> {}
+            is LiveTile -> drawLiveWindow()
+            is SnapshotSplash -> drawSnapshotSplash(state)
+            is BackgroundOnly -> drawBackground(state.backgroundColor)
         }
     }
 
-    private fun updateViewDataValues() {
-        viewData.width.value = width
-        viewData.height.value = height
+    /**
+     * Updates the alpha of the dim layer on top of this view. If dimAlpha is 0, no dimming is
+     * applied; if dimAlpha is 1, the thumbnail will be the extracted background color.
+     *
+     * @param tintAmount The amount of alpha that will be applied to the dim layer.
+     */
+    fun updateTintAmount(tintAmount: Float) {
+        dimAlpha[ScrimViewAlpha.TintAmount.ordinal].value = tintAmount
     }
 
-    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
-        super.onSizeChanged(w, h, oldw, oldh)
-        if (uiState is SnapshotSplash) {
-            setImageMatrix()
-        }
-        bounds.set(0, 0, w, h)
-        invalidateOutline()
+    fun updateMenuOpenProgress(progress: Float) {
+        dimAlpha[ScrimViewAlpha.MenuProgress.ordinal].value = progress * MAX_SCRIM_ALPHA
     }
 
-    override fun setScaleX(scaleX: Float) {
-        super.setScaleX(scaleX)
+    fun updateSplashAlpha(value: Float) {
+        splashBackground.alpha = value
+        splashIcon.alpha = value
+    }
+
+    fun parentScaleXUpdated(scaleX: Float) {
         // Splash icon should ignore scale on TTV
         splashIcon.scaleX = 1 / scaleX
     }
 
-    override fun setScaleY(scaleY: Float) {
-        super.setScaleY(scaleY)
+    fun parentScaleYUpdated(scaleY: Float) {
         // Splash icon should ignore scale on TTV
         splashIcon.scaleY = 1 / scaleY
     }
@@ -162,8 +108,10 @@
     private fun resetViews() {
         liveTileView.isInvisible = true
         thumbnailView.isInvisible = true
+        thumbnailView.setImageBitmap(null)
         splashBackground.alpha = 0f
         splashIcon.alpha = 0f
+        splashIcon.setImageDrawable(null)
         scrimView.alpha = 0f
         setBackgroundColor(Color.BLACK)
     }
@@ -180,21 +128,33 @@
         drawSnapshot(snapshotSplash.snapshot)
 
         splashBackground.setBackgroundColor(snapshotSplash.snapshot.backgroundColor)
-        splashIcon.setImageDrawable(snapshotSplash.splash)
+        val icon = snapshotSplash.splash?.constantState?.newDrawable()?.mutate() ?: ShapeDrawable()
+        splashIcon.setImageDrawable(icon)
     }
 
     private fun drawSnapshot(snapshot: Snapshot) {
         drawBackground(snapshot.backgroundColor)
         thumbnailView.setImageBitmap(snapshot.bitmap)
         thumbnailView.isInvisible = false
-        setImageMatrix()
     }
 
-    private fun setImageMatrix() {
-        thumbnailView.imageMatrix = viewModel.getThumbnailPositionState(width, height, isLayoutRtl)
+    fun setImageMatrix(matrix: Matrix) {
+        if (uiState is SnapshotSplash) {
+            thumbnailView.imageMatrix = matrix
+        }
+    }
+
+    private fun logDebug(message: String) {
+        Log.d(TAG, "[TaskThumbnailView@${Integer.toHexString(hashCode())}] $message")
     }
 
     private companion object {
         const val TAG = "TaskThumbnailView"
+        private const val MAX_SCRIM_ALPHA = 0.4f
+
+        enum class ScrimViewAlpha {
+            MenuProgress,
+            TintAmount,
+        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskIconDataSource.kt b/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskIconDataSource.kt
index ab699c6..c45458c 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskIconDataSource.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskIconDataSource.kt
@@ -16,10 +16,9 @@
 
 package com.android.quickstep.task.thumbnail.data
 
-import com.android.launcher3.util.CancellableTask
-import com.android.quickstep.TaskIconCache.GetTaskIconCallback
+import com.android.quickstep.TaskIconCache
 import com.android.systemui.shared.recents.model.Task
 
 interface TaskIconDataSource {
-    fun getIconInBackground(task: Task, callback: GetTaskIconCallback): CancellableTask<*>?
+    suspend fun getIcon(task: Task): TaskIconCache.TaskCacheEntry
 }
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskThumbnailDataSource.kt b/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskThumbnailDataSource.kt
index 986acbe..6e63ea9 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskThumbnailDataSource.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/data/TaskThumbnailDataSource.kt
@@ -16,14 +16,9 @@
 
 package com.android.quickstep.task.thumbnail.data
 
-import com.android.launcher3.util.CancellableTask
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
-import java.util.function.Consumer
 
 interface TaskThumbnailDataSource {
-    fun getThumbnailInBackground(
-        task: Task,
-        callback: Consumer<ThumbnailData>
-    ): CancellableTask<ThumbnailData>?
+    suspend fun getThumbnail(task: Task): ThumbnailData?
 }
diff --git a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt b/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
deleted file mode 100644
index 203177a..0000000
--- a/quickstep/src/com/android/quickstep/task/util/TaskOverlayHelper.kt
+++ /dev/null
@@ -1,114 +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.quickstep.task.util
-
-import android.util.Log
-import android.view.View.OnLayoutChangeListener
-import com.android.quickstep.TaskOverlayFactory
-import com.android.quickstep.recents.di.RecentsDependencies
-import com.android.quickstep.recents.di.get
-import com.android.quickstep.task.thumbnail.TaskOverlayUiState
-import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled
-import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Enabled
-import com.android.quickstep.task.viewmodel.TaskOverlayViewModel
-import com.android.systemui.shared.recents.model.Task
-import kotlinx.coroutines.CoroutineName
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.flow.onEach
-
-/**
- * Helper for [TaskOverlayFactory.TaskOverlay] to interact with [TaskOverlayViewModel], this helper
- * should merge with [TaskOverlayFactory.TaskOverlay] when it's migrated to MVVM.
- */
-class TaskOverlayHelper(val task: Task, val overlay: TaskOverlayFactory.TaskOverlay<*>) {
-    private lateinit var overlayInitializedScope: CoroutineScope
-    private var uiState: TaskOverlayUiState = Disabled
-
-    private lateinit var viewModel: TaskOverlayViewModel
-
-    // TODO(b/331753115): TaskOverlay should listen for state changes and react.
-    val enabledState: Enabled
-        get() = uiState as Enabled
-
-    private val snapshotLayoutChangeListener = OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
-        (uiState as? Enabled)?.let { initOverlay(it) }
-    }
-
-    fun getThumbnailMatrix() = getThumbnailPositionState().matrix
-
-    private fun getThumbnailPositionState() =
-        viewModel.getThumbnailPositionState(
-            overlay.snapshotView.width,
-            overlay.snapshotView.height,
-            overlay.snapshotView.isLayoutRtl,
-        )
-
-    fun init() {
-        overlayInitializedScope =
-            CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineName("TaskOverlayHelper"))
-        viewModel =
-            TaskOverlayViewModel(
-                task = task,
-                recentsViewData = RecentsDependencies.get(),
-                getThumbnailPositionUseCase = RecentsDependencies.get(),
-                recentTasksRepository = RecentsDependencies.get(),
-            )
-        viewModel.overlayState
-            .onEach {
-                uiState = it
-                if (it is Enabled) {
-                    initOverlay(it)
-                } else {
-                    reset()
-                }
-            }
-            .launchIn(overlayInitializedScope)
-        overlay.snapshotView.addOnLayoutChangeListener(snapshotLayoutChangeListener)
-    }
-
-    private fun initOverlay(enabledState: Enabled) {
-        if (DEBUG) {
-            Log.d(TAG, "initOverlay - taskId: ${task.key.id}, thumbnail: ${enabledState.thumbnail}")
-        }
-        with(getThumbnailPositionState()) {
-            overlay.initOverlay(task, enabledState.thumbnail, matrix, isRotated)
-        }
-    }
-
-    private fun reset() {
-        if (DEBUG) {
-            Log.d(TAG, "reset - taskId: ${task.key.id}")
-        }
-        overlay.reset()
-    }
-
-    fun destroy() {
-        overlayInitializedScope.cancel()
-        uiState = Disabled
-        overlay.snapshotView.removeOnLayoutChangeListener(snapshotLayoutChangeListener)
-        reset()
-    }
-
-    companion object {
-        private const val TAG = "TaskOverlayHelper"
-        private const val DEBUG = false
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
deleted file mode 100644
index 5f2de94..0000000
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskContainerData.kt
+++ /dev/null
@@ -1,25 +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.quickstep.task.viewmodel
-
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class TaskContainerData {
-    val taskMenuOpenProgress = MutableStateFlow(0f)
-
-    val thumbnailSplashProgress = MutableStateFlow(0f)
-}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
deleted file mode 100644
index 4e13d1c..0000000
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModel.kt
+++ /dev/null
@@ -1,79 +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.quickstep.task.viewmodel
-
-import android.graphics.Matrix
-import com.android.quickstep.recents.data.RecentTasksRepository
-import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
-import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
-import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
-import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled
-import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Enabled
-import com.android.systemui.shared.recents.model.Task
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.runBlocking
-
-/** View model for TaskOverlay */
-class TaskOverlayViewModel(
-    private val task: Task,
-    recentsViewData: RecentsViewData,
-    private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
-    recentTasksRepository: RecentTasksRepository,
-) {
-    val overlayState =
-        combine(
-                recentsViewData.overlayEnabled,
-                recentsViewData.settledFullyVisibleTaskIds.map { it.contains(task.key.id) },
-                recentTasksRepository.getThumbnailById(task.key.id)
-            ) { isOverlayEnabled, isFullyVisible, thumbnailData ->
-                if (isOverlayEnabled && isFullyVisible) {
-                    Enabled(
-                        isRealSnapshot = (thumbnailData?.isRealSnapshot ?: false) && !task.isLocked,
-                        thumbnailData?.thumbnail,
-                    )
-                } else {
-                    Disabled
-                }
-            }
-            .distinctUntilChanged()
-
-    fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): ThumbnailPositionState {
-        return runBlocking {
-            val matrix: Matrix
-            val isRotated: Boolean
-            when (
-                val thumbnailPositionState =
-                    getThumbnailPositionUseCase.run(task.key.id, width, height, isRtl)
-            ) {
-                is MatrixScaling -> {
-                    matrix = thumbnailPositionState.matrix
-                    isRotated = thumbnailPositionState.isRotated
-                }
-                is MissingThumbnail -> {
-                    matrix = Matrix.IDENTITY_MATRIX
-                    isRotated = false
-                }
-            }
-            ThumbnailPositionState(matrix, isRotated)
-        }
-    }
-
-    data class ThumbnailPositionState(val matrix: Matrix, val isRotated: Boolean)
-}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
deleted file mode 100644
index a048a1d..0000000
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModel.kt
+++ /dev/null
@@ -1,39 +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.quickstep.task.viewmodel
-
-import android.graphics.Matrix
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
-import kotlinx.coroutines.flow.Flow
-
-/** ViewModel for representing TaskThumbnails */
-interface TaskThumbnailViewModel {
-    /** Provides the level of dimming that the View should have */
-    val dimProgress: Flow<Float>
-
-    /** Provides the alpha of the splash icon */
-    val splashAlpha: Flow<Float>
-
-    /** Provides the UiState by which the task thumbnail can be represented */
-    val uiState: Flow<TaskThumbnailUiState>
-
-    /** Attaches this ViewModel to a specific task id for it to provide data from. */
-    fun bind(taskId: Int)
-
-    /** Returns a Matrix which can be applied to the snapshot */
-    fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix
-}
diff --git a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt b/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
deleted file mode 100644
index e5bad67..0000000
--- a/quickstep/src/com/android/quickstep/task/viewmodel/TaskThumbnailViewModelImpl.kt
+++ /dev/null
@@ -1,145 +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 goveryning permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.task.viewmodel
-
-import android.annotation.ColorInt
-import android.app.ActivityTaskManager.INVALID_TASK_ID
-import android.graphics.Matrix
-import android.util.Log
-import androidx.core.graphics.ColorUtils
-import com.android.launcher3.util.coroutines.DispatcherProvider
-import com.android.quickstep.recents.data.RecentTasksRepository
-import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
-import com.android.quickstep.recents.usecase.ThumbnailPositionState
-import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.thumbnail.SplashAlphaUseCase
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
-import com.android.systemui.shared.recents.model.Task
-import kotlin.math.max
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.runBlocking
-
-@OptIn(ExperimentalCoroutinesApi::class)
-class TaskThumbnailViewModelImpl(
-    recentsViewData: RecentsViewData,
-    taskContainerData: TaskContainerData,
-    dispatcherProvider: DispatcherProvider,
-    private val tasksRepository: RecentTasksRepository,
-    private val getThumbnailPositionUseCase: GetThumbnailPositionUseCase,
-    private val splashAlphaUseCase: SplashAlphaUseCase,
-) : TaskThumbnailViewModel {
-    private val task = MutableStateFlow<Flow<Task?>>(flowOf(null))
-    private val splashProgress = MutableStateFlow(flowOf(0f))
-    private var taskId: Int = INVALID_TASK_ID
-
-    override val dimProgress: Flow<Float> =
-        combine(taskContainerData.taskMenuOpenProgress, recentsViewData.tintAmount) {
-                taskMenuOpenProgress,
-                tintAmount ->
-                max(taskMenuOpenProgress * MAX_SCRIM_ALPHA, tintAmount)
-            }
-            .flowOn(dispatcherProvider.background)
-
-    override val splashAlpha =
-        splashProgress.flatMapLatest { it }.flowOn(dispatcherProvider.background)
-
-    private val isLiveTile =
-        combine(
-                task.flatMapLatest { it }.map { it?.key?.id }.distinctUntilChanged(),
-                recentsViewData.runningTaskIds,
-                recentsViewData.runningTaskShowScreenshot,
-            ) { taskId, runningTaskIds, runningTaskShowScreenshot ->
-                runningTaskIds.contains(taskId) && !runningTaskShowScreenshot
-            }
-            .distinctUntilChanged()
-
-    override val uiState: Flow<TaskThumbnailUiState> =
-        combine(task.flatMapLatest { it }, isLiveTile) { taskVal, isRunning ->
-                // TODO(b/369339561) This log is firing a lot. Reduce emissions from TasksRepository
-                //  then re-enable this log.
-                //                Log.d(
-                //                    TAG,
-                //                    "Received task and / or live tile update. taskVal: $taskVal"
-                //                    + " isRunning: $isRunning.",
-                //                )
-                when {
-                    taskVal == null -> Uninitialized
-                    isRunning -> LiveTile
-                    isBackgroundOnly(taskVal) ->
-                        BackgroundOnly(taskVal.colorBackground.removeAlpha())
-                    isSnapshotSplashState(taskVal) ->
-                        SnapshotSplash(createSnapshotState(taskVal), taskVal.icon)
-                    else -> Uninitialized
-                }
-            }
-            .distinctUntilChanged()
-            .flowOn(dispatcherProvider.background)
-
-    override fun bind(taskId: Int) {
-        Log.d(TAG, "bind taskId: $taskId")
-        this.taskId = taskId
-        task.value = tasksRepository.getTaskDataById(taskId)
-        splashProgress.value = splashAlphaUseCase.execute(taskId)
-    }
-
-    override fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean): Matrix {
-        return runBlocking {
-            when (
-                val thumbnailPositionState =
-                    getThumbnailPositionUseCase.run(taskId, width, height, isRtl)
-            ) {
-                is ThumbnailPositionState.MatrixScaling -> thumbnailPositionState.matrix
-                is ThumbnailPositionState.MissingThumbnail -> Matrix.IDENTITY_MATRIX
-            }
-        }
-    }
-
-    private fun isBackgroundOnly(task: Task): Boolean = task.isLocked || task.thumbnail == null
-
-    private fun isSnapshotSplashState(task: Task): Boolean {
-        val thumbnailPresent = task.thumbnail?.thumbnail != null
-        val taskLocked = task.isLocked
-
-        return thumbnailPresent && !taskLocked
-    }
-
-    private fun createSnapshotState(task: Task): Snapshot {
-        val thumbnailData = task.thumbnail
-        val bitmap = thumbnailData?.thumbnail!!
-        return Snapshot(bitmap, thumbnailData.rotation, task.colorBackground.removeAlpha())
-    }
-
-    @ColorInt private fun Int.removeAlpha(): Int = ColorUtils.setAlphaComponent(this, 0xff)
-
-    private companion object {
-        const val MAX_SCRIM_ALPHA = 0.4f
-        const val TAG = "TaskThumbnailViewModel"
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt b/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt
new file mode 100644
index 0000000..a1ff0ce
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ActiveTrackpadList.kt
@@ -0,0 +1,71 @@
+/*
+ * 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 android.content.Context
+import android.hardware.input.InputManager
+import android.view.InputDevice
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.IntSet
+
+/** Utility class to maintain a list of actively connected trackpad devices */
+class ActiveTrackpadList(ctx: Context, private val updateCallback: Runnable) :
+    IntSet(), InputManager.InputDeviceListener {
+
+    private val inputManager = ctx.getSystemService(InputManager::class.java)!!
+
+    init {
+        inputManager.registerInputDeviceListener(this, Executors.UI_HELPER_EXECUTOR.handler)
+        inputManager.inputDeviceIds.filter(this::isTrackpadDevice).forEach(this::add)
+    }
+
+    override fun onInputDeviceAdded(deviceId: Int) {
+        if (isTrackpadDevice(deviceId)) {
+            // This updates internal TIS state so it needs to also run on the main
+            // thread.
+            Executors.MAIN_EXECUTOR.execute {
+                val wasEmpty = isEmpty
+                add(deviceId)
+                if (wasEmpty) update()
+            }
+        }
+    }
+
+    override fun onInputDeviceChanged(deviceId: Int) {}
+
+    override fun onInputDeviceRemoved(deviceId: Int) {
+        // This updates internal TIS state so it needs to also run on the main thread.
+        Executors.MAIN_EXECUTOR.execute {
+            remove(deviceId)
+            if (isEmpty) update()
+        }
+    }
+
+    private fun update() {
+        updateCallback.run()
+    }
+
+    fun destroy() {
+        inputManager.unregisterInputDeviceListener(this)
+        clear()
+    }
+
+    /** This is a blocking binder call that should run on a bg thread. */
+    private fun isTrackpadDevice(deviceId: Int) =
+        inputManager.getInputDevice(deviceId)?.sources ==
+            (InputDevice.SOURCE_MOUSE or InputDevice.SOURCE_TOUCHPAD)
+}
diff --git a/quickstep/src/com/android/quickstep/util/ActivityPreloadUtil.kt b/quickstep/src/com/android/quickstep/util/ActivityPreloadUtil.kt
new file mode 100644
index 0000000..df26b6b
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ActivityPreloadUtil.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.content.Context
+import android.content.Intent
+import android.os.Trace
+import android.view.Display.DEFAULT_DISPLAY
+import com.android.launcher3.provider.RestoreDbTask
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.LockedUserState
+import com.android.quickstep.OverviewComponentObserver
+import com.android.quickstep.RecentsAnimationDeviceState
+import com.android.systemui.shared.system.ActivityManagerWrapper
+
+/** Utility class for preloading overview */
+object ActivityPreloadUtil {
+
+    @JvmStatic
+    fun preloadOverviewForSUWAllSet(ctx: Context) {
+        preloadOverview(ctx, fromInit = false, forSUWAllSet = true)
+    }
+
+    @JvmStatic
+    fun preloadOverviewForTIS(ctx: Context, fromInit: Boolean) {
+        preloadOverview(ctx, fromInit = fromInit, forSUWAllSet = false)
+    }
+
+    private fun preloadOverview(ctx: Context, fromInit: Boolean, forSUWAllSet: Boolean) {
+        Trace.beginSection("preloadOverview(fromInit=$fromInit, forSUWAllSet=$forSUWAllSet)")
+
+        try {
+            if (!LockedUserState.get(ctx).isUserUnlocked) return
+
+            val deviceState = RecentsAnimationDeviceState.INSTANCE[ctx]
+            val overviewCompObserver = OverviewComponentObserver.INSTANCE[ctx]
+
+            // Prevent the overview from being started before the real home on first boot
+            if (deviceState.isButtonNavMode && !overviewCompObserver.isHomeAndOverviewSame) return
+
+            // Preloading while a restore is pending may cause launcher to start the restore too
+            // early
+            if ((RestoreDbTask.isPending(ctx) && !forSUWAllSet) || !deviceState.isUserSetupComplete)
+                return
+
+            // The activity has been created before the initialization of overview service. It is
+            // usually happens when booting or launcher is the top activity, so we should already
+            // have the latest state.
+            if (
+                fromInit &&
+                    overviewCompObserver.getContainerInterface(DEFAULT_DISPLAY).createdContainer !=
+                        null
+            )
+                return
+
+            ActiveGestureProtoLogProxy.logPreloadRecentsAnimation()
+            val overviewIntent = Intent(overviewCompObserver.overviewIntentIgnoreSysUiState)
+            Executors.UI_HELPER_EXECUTOR.execute {
+                ActivityManagerWrapper.getInstance().preloadRecentsActivity(overviewIntent)
+            }
+        } finally {
+            Trace.endSection()
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/AnimUtils.java b/quickstep/src/com/android/quickstep/util/AnimUtils.java
index 31aca03..3492788 100644
--- a/quickstep/src/com/android/quickstep/util/AnimUtils.java
+++ b/quickstep/src/com/android/quickstep/util/AnimUtils.java
@@ -21,16 +21,22 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.animation.AnimatorSet;
-import android.annotation.NonNull;
+import android.os.BinderUtils;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.view.animation.Interpolator;
 
+import androidx.annotation.NonNull;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.views.ActivityContext;
 import com.android.quickstep.views.RecentsViewContainer;
 
 /**
@@ -78,8 +84,7 @@
             @NonNull SplitAnimationController animationController) {
         StateAnimationConfig config = new StateAnimationConfig();
         BaseState startState = stateManager.getState();
-        long duration = startState.getTransitionDuration(container.asContext(),
-                false /*isToState*/);
+        long duration = startState.getTransitionDuration(container, false /*isToState*/);
         if (duration == 0) {
             // Case where we're in contextual on workspace (NORMAL), which by default has 0
             // transition duration
@@ -96,14 +101,30 @@
     }
 
     /**
-     * Returns a IRemoteCallback which completes the provided list as a result
+     * Returns a IRemoteCallback which completes the provided list as a result or when the owner
+     * is destroyed
      */
-    public static IRemoteCallback completeRunnableListCallback(RunnableList list) {
+    public static IRemoteCallback completeRunnableListCallback(
+            RunnableList list, ActivityContext owner) {
+        DefaultLifecycleObserver destroyObserver = new DefaultLifecycleObserver() {
+            @Override
+            public void onDestroy(@NonNull LifecycleOwner owner) {
+                list.executeAllAndClear();
+            }
+        };
+        MAIN_EXECUTOR.execute(() -> owner.getLifecycle().addObserver(destroyObserver));
+        list.add(() -> owner.getLifecycle().removeObserver(destroyObserver));
+
         return new IRemoteCallback.Stub() {
             @Override
             public void sendResult(Bundle bundle) {
                 MAIN_EXECUTOR.execute(list::executeAllAndDestroy);
             }
+
+            @Override
+            public IBinder asBinder() {
+                return BinderUtils.wrapLifecycle(this, owner.getOwnerCleanupSet());
+            }
         };
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
index b583a4b..37d7030 100644
--- a/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
+++ b/quickstep/src/com/android/quickstep/util/AnimatorControllerWithResistance.java
@@ -17,7 +17,6 @@
 
 import static com.android.app.animation.Interpolators.DECELERATE;
 import static com.android.app.animation.Interpolators.LINEAR;
-import static com.android.launcher3.Flags.enableGridOnlyOverview;
 import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
 import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
 
@@ -50,7 +49,6 @@
     private enum RecentsResistanceParams {
         FROM_APP(0.75f, 0.5f, 1f, false),
         FROM_APP_TABLET(1f, 0.7f, 1f, true),
-        FROM_APP_TABLET_GRID_ONLY(1f, 1f, 1f, true),
         FROM_OVERVIEW(1f, 0.75f, 0.5f, false);
 
         RecentsResistanceParams(float scaleStartResist, float scaleMaxResist,
@@ -259,9 +257,7 @@
             this.translationTarget = translationTarget;
             this.translationProperty = translationProperty;
             if (dp.isTablet) {
-                resistanceParams = enableGridOnlyOverview()
-                        ? RecentsResistanceParams.FROM_APP_TABLET_GRID_ONLY
-                        : RecentsResistanceParams.FROM_APP_TABLET;
+                resistanceParams = RecentsResistanceParams.FROM_APP_TABLET;
             } else {
                 resistanceParams = RecentsResistanceParams.FROM_APP;
             }
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 1312aa4..8385485 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -26,10 +26,10 @@
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
+import static com.android.systemui.shared.recents.utilities.Utilities.isFreeformTask;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.getIndex;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.isPersistentSnapPosition;
 
 import android.content.Context;
@@ -47,7 +47,6 @@
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.apppairs.AppPairIcon;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.logging.InstanceId;
 import com.android.launcher3.logging.StatsLogManager;
@@ -55,6 +54,7 @@
 import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.model.data.TaskViewItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
@@ -70,10 +70,12 @@
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 import java.util.function.Consumer;
 
 /**
@@ -126,29 +128,27 @@
                 .anyMatch(att -> att != null && att.getItemInfo() != null
                         && ((att.getItemInfo().runtimeStatusFlags
                             & ItemInfoWithIcon.FLAG_NOT_PINNABLE) != 0));
-        if (!FeatureFlags.enableAppPairs()
-                || !taskView.containsMultipleTasks()
+        if (!taskView.containsMultipleTasks()
                 || hasUnpinnableApp
-                || !(taskView instanceof GroupedTaskView)) {
+                || !(taskView instanceof GroupedTaskView groupedTaskView)) {
             return false;
         }
 
-        GroupedTaskView gtv = (GroupedTaskView) taskView;
-        List<TaskContainer> containers = gtv.getTaskContainers();
-        ComponentKey taskKey1 = TaskUtils.getLaunchComponentKeyForTask(
-                containers.get(0).getTask().key);
-        ComponentKey taskKey2 = TaskUtils.getLaunchComponentKeyForTask(
-                containers.get(1).getTask().key);
-        AppInfo app1 = resolveAppInfoByComponent(taskKey1);
-        AppInfo app2 = resolveAppInfoByComponent(taskKey2);
+        ComponentKey leftTopComponentKey = TaskUtils.getLaunchComponentKeyForTask(
+                groupedTaskView.getLeftTopTaskContainer().getTask().key);
+        ComponentKey rightBottomComponentKey = TaskUtils.getLaunchComponentKeyForTask(
+                groupedTaskView.getRightBottomTaskContainer().getTask().key);
+        AppInfo leftTopAppInfo = resolveAppInfoByComponent(leftTopComponentKey);
+        AppInfo rightBottomAppInfo = resolveAppInfoByComponent(rightBottomComponentKey);
 
-        if (app1 == null || app2 == null) {
+        if (leftTopAppInfo == null || rightBottomAppInfo == null) {
             // Disallow saving app pairs for apps that don't have a front-door in Launcher
             return false;
         }
 
-        if (PackageManagerHelper.isSameAppForMultiInstance(app1, app2)) {
-            if (!app1.supportsMultiInstance() || !app2.supportsMultiInstance()) {
+        if (PackageManagerHelper.isSameAppForMultiInstance(leftTopAppInfo, rightBottomAppInfo)) {
+            if (!leftTopAppInfo.supportsMultiInstance()
+                    || !rightBottomAppInfo.supportsMultiInstance()) {
                 return false;
             }
         }
@@ -171,20 +171,6 @@
      */
     public void saveAppPair(GroupedTaskView gtv) {
         InteractionJankMonitorWrapper.begin(gtv, Cuj.CUJ_LAUNCHER_SAVE_APP_PAIR);
-        List<TaskContainer> containers = gtv.getTaskContainers();
-        WorkspaceItemInfo recentsInfo1 = containers.get(0).getItemInfo();
-        WorkspaceItemInfo recentsInfo2 = containers.get(1).getItemInfo();
-        WorkspaceItemInfo app1 = resolveAppPairWorkspaceInfo(recentsInfo1);
-        WorkspaceItemInfo app2 = resolveAppPairWorkspaceInfo(recentsInfo2);
-
-        if (app1 == null || app2 == null) {
-            // This shouldn't happen if canSaveAppPair() is called above, but log an error and do
-            // not create the app pair if the workspace items can't be resolved
-            Log.w(TAG, "Failed to save app pair due to invalid apps ("
-                    + "app1=" + recentsInfo1.getComponentKey().componentName
-                    + " app2=" + recentsInfo2.getComponentKey().componentName + ")");
-            return;
-        }
 
         @PersistentSnapPosition int snapPosition = gtv.getSnapPosition();
         if (snapPosition == SNAP_TO_NONE) {
@@ -198,16 +184,36 @@
             return;
         }
 
-        app1.rank = encodeRank(SPLIT_POSITION_TOP_OR_LEFT, snapPosition);
-        app2.rank = encodeRank(SPLIT_POSITION_BOTTOM_OR_RIGHT, snapPosition);
-        AppPairInfo newAppPair = new AppPairInfo(app1, app2);
+        List<TaskViewItemInfo> recentsInfos =
+                gtv.getTaskContainers().stream().map(TaskContainer::getItemInfo).toList();
+        List<WorkspaceItemInfo> apps =
+                recentsInfos.stream().map(this::resolveAppPairWorkspaceInfo).toList();
+
+        if (apps.stream().anyMatch(Objects::isNull)) {
+            // This shouldn't happen if canSaveAppPair() is called above, but log an error and do
+            // not create the app pair if the workspace items can't be resolved
+            StringBuilder error =
+                    new StringBuilder("Failed to save app pair due to invalid apps (");
+            for (int i = 0; i < recentsInfos.size(); i++) {
+                error.append("app").append(i).append("=")
+                        .append(recentsInfos.get(i).getComponentKey().componentName).append(" ");
+            }
+            error.append(")");
+            Log.w(TAG, error.toString());
+            return;
+        }
+
+        for (int i = 0; i < apps.size(); i++) {
+            apps.get(i).rank = encodeRank(getIndex(i), snapPosition);
+        }
+        AppPairInfo newAppPair = new AppPairInfo(apps);
 
         IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
         MODEL_EXECUTOR.execute(() -> {
             newAppPair.getAppContents().forEach(member -> {
                 member.title = "";
                 member.bitmap = iconCache.getDefaultIcon(newAppPair.user);
-                iconCache.getTitleAndIcon(member, member.usingLowResIcon());
+                iconCache.getTitleAndIcon(member, member.getMatchingLookupFlag());
             });
             MAIN_EXECUTOR.execute(() -> {
                 LauncherAccessibilityDelegate delegate =
@@ -333,10 +339,12 @@
      *   c) App B is on-screen, but App A isn't.
      *   d) Neither is on-screen.
      *
-     * If the user tapped an app pair while inside a single app, there are 3 cases:
-     *   a) The on-screen app is App A of the app pair.
-     *   b) The on-screen app is App B of the app pair.
-     *   c) It is neither.
+     * If the user tapped an app pair while a fullscreen or freeform app is visible on screen,
+     * there are 4 cases:
+     *   a) At least one of the apps in the app pair is in freeform windowing mode.
+     *   b) The on-screen app is App A of the app pair.
+     *   c) The on-screen app is App B of the app pair.
+     *   d) It is neither.
      *
      * For each case, we call the appropriate animation and split launch type.
      */
@@ -418,6 +426,14 @@
                     foundTasks -> {
                         Task foundTask1 = foundTasks[0];
                         Task foundTask2 = foundTasks[1];
+
+                        if (DesktopModeStatus.canEnterDesktopMode(context) && (isFreeformTask(
+                                foundTask1) || isFreeformTask(foundTask2))) {
+                            launchAppPair(launchingIconView,
+                                    CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_TASKBAR);
+                            return;
+                        }
+
                         boolean task1IsOnScreen;
                         boolean task2IsOnScreen;
                         if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
diff --git a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
index 54f6443..207e482 100644
--- a/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
+++ b/quickstep/src/com/android/quickstep/util/AsyncClockEventDelegate.java
@@ -59,8 +59,7 @@
 
     private final Context mContext;
     private final SettingsCache mSettingsCache;
-    private final SimpleBroadcastReceiver mReceiver =
-            new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, this::onClockEventReceived);
+    private final SimpleBroadcastReceiver mReceiver;
 
     private final ArrayMap<BroadcastReceiver, Handler> mTimeEventReceivers = new ArrayMap<>();
     private final List<ContentObserver> mFormatObservers = new ArrayList<>();
@@ -76,7 +75,9 @@
         super(context);
         mContext = context;
         mSettingsCache = settingsCache;
-        mReceiver.register(mContext, ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED);
+        mReceiver = new SimpleBroadcastReceiver(
+                context, UI_HELPER_EXECUTOR, this::onClockEventReceived);
+        mReceiver.register(ACTION_TIME_CHANGED, ACTION_TIMEZONE_CHANGED);
         tracker.addCloseable(this);
     }
 
@@ -138,6 +139,6 @@
     public void close() {
         mDestroyed = true;
         mSettingsCache.unregister(mFormatUri, this);
-        mReceiver.unregisterReceiverSafely(mContext);
+        mReceiver.unregisterReceiverSafely();
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/BackAnimState.kt b/quickstep/src/com/android/quickstep/util/BackAnimState.kt
index 9009eaa..4c1e1ff 100644
--- a/quickstep/src/com/android/quickstep/util/BackAnimState.kt
+++ b/quickstep/src/com/android/quickstep/util/BackAnimState.kt
@@ -18,6 +18,7 @@
 
 import android.animation.AnimatorSet
 import android.content.Context
+import com.android.launcher3.Flags
 import com.android.launcher3.LauncherAnimationRunner.AnimationResult
 import com.android.launcher3.anim.AnimatorListeners.forEndCallback
 import com.android.launcher3.util.RunnableList
@@ -36,14 +37,20 @@
     BackAnimState {
 
     override fun addOnAnimCompleteCallback(r: Runnable) {
-        val springAnimWait = RunnableList()
-        springAnim?.addAnimatorListener(forEndCallback(springAnimWait::executeAllAndDestroy))
-            ?: springAnimWait.executeAllAndDestroy()
-
         val animWait = RunnableList()
-        anim?.addListener(
-            forEndCallback(Runnable { springAnimWait.add(animWait::executeAllAndDestroy) })
-        ) ?: springAnimWait.add(animWait::executeAllAndDestroy)
+        if (Flags.predictiveBackToHomePolish()) {
+            springAnim?.addAnimatorListener(forEndCallback(animWait::executeAllAndDestroy))
+                ?: anim?.addListener(forEndCallback(animWait::executeAllAndDestroy))
+                ?: animWait.executeAllAndDestroy()
+        } else {
+            val springAnimWait = RunnableList()
+            springAnim?.addAnimatorListener(forEndCallback(springAnimWait::executeAllAndDestroy))
+                ?: springAnimWait.executeAllAndDestroy()
+
+            anim?.addListener(
+                forEndCallback(Runnable { springAnimWait.add(animWait::executeAllAndDestroy) })
+            ) ?: springAnimWait.add(animWait::executeAllAndDestroy)
+        }
         animWait.add(r)
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt b/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt
index 286b77a..7ec605d 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchHapticManager.kt
@@ -21,18 +21,25 @@
 import android.os.VibrationEffect.Composition
 import android.os.Vibrator
 import com.android.launcher3.dagger.ApplicationContext
-import com.android.launcher3.util.MainThreadInitializedObject
-import com.android.launcher3.util.SafeCloseable
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.DaggerSingletonObject
 import com.android.launcher3.util.VibratorWrapper
 import com.android.quickstep.DeviceConfigWrapper.Companion.get
+import com.android.quickstep.dagger.QuickstepBaseAppComponent
+import javax.inject.Inject
 import kotlin.math.pow
 
 /** Manages haptics relating to Contextual Search invocations. */
+@LauncherAppSingleton
 class ContextualSearchHapticManager
-internal constructor(@ApplicationContext private val context: Context) : SafeCloseable {
+@Inject
+internal constructor(
+    @ApplicationContext private val context: Context,
+    private val contextualSearchStateManager: ContextualSearchStateManager,
+    private val vibratorWrapper: VibratorWrapper,
+) {
 
     private var searchEffect = createSearchEffect()
-    private var contextualSearchStateManager = ContextualSearchStateManager.INSTANCE[context]
 
     private fun createSearchEffect() =
         if (
@@ -50,7 +57,7 @@
 
     /** Indicates that search has been invoked. */
     fun vibrateForSearch() {
-        searchEffect.let { VibratorWrapper.INSTANCE[context].vibrate(it) }
+        searchEffect.let { vibratorWrapper.vibrate(it) }
     }
 
     /** Indicates that search will be invoked if the current gesture is maintained. */
@@ -93,13 +100,13 @@
                     composition.addPrimitive(Composition.PRIMITIVE_LOW_TICK, scale)
                 }
             }
-            VibratorWrapper.INSTANCE[context].vibrate(composition.compose())
+            vibratorWrapper.vibrate(composition.compose())
         }
     }
 
-    override fun close() {}
-
     companion object {
-        @JvmField val INSTANCE = MainThreadInitializedObject { ContextualSearchHapticManager(it) }
+        @JvmField
+        val INSTANCE =
+            DaggerSingletonObject(QuickstepBaseAppComponent::getContextualSearchHapticManager)
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
index d00a39c..3bc9adc 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
@@ -21,6 +21,7 @@
 import android.app.contextualsearch.ContextualSearchManager.FEATURE_CONTEXTUAL_SEARCH
 import android.content.Context
 import android.util.Log
+import android.view.Display.DEFAULT_DISPLAY
 import androidx.annotation.VisibleForTesting
 import com.android.internal.app.AssistUtils
 import com.android.launcher3.logging.StatsLogManager
@@ -222,7 +223,8 @@
 
     @VisibleForTesting
     fun getRecentsContainerInterface(): BaseContainerInterface<*, *>? {
-        return OverviewComponentObserver.INSTANCE.get(context).containerInterface
+        return OverviewComponentObserver.INSTANCE.get(context)
+            .getContainerInterface(DEFAULT_DISPLAY)
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
index f75d3b3..a8d3c6d 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
@@ -21,7 +21,6 @@
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_SEARCH_SCREEN;
 
 import android.app.PendingIntent;
@@ -44,11 +43,13 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.R;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.EventLogArray;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.quickstep.DeviceConfigWrapper;
@@ -58,12 +59,14 @@
 import java.io.PrintWriter;
 import java.util.Optional;
 
-/** Long-lived class to manage Contextual Search states like the user setting and availability. */
-public class ContextualSearchStateManager implements ResourceBasedOverride, SafeCloseable {
+import javax.inject.Inject;
 
-    public static final MainThreadInitializedObject<ContextualSearchStateManager> INSTANCE =
-            forOverride(ContextualSearchStateManager.class,
-                    R.string.contextual_search_state_manager_class);
+/** Long-lived class to manage Contextual Search states like the user setting and availability. */
+@LauncherAppSingleton
+public class ContextualSearchStateManager  {
+
+    public static final DaggerSingletonObject<ContextualSearchStateManager> INSTANCE =
+            new DaggerSingletonObject<>(LauncherAppComponent::getContextualSearchStateManager);
 
     private static final String TAG = "ContextualSearchStMgr";
     private static final int MAX_DEBUG_EVENT_SIZE = 20;
@@ -71,25 +74,33 @@
             Settings.Secure.getUriFor(Settings.Secure.SEARCH_ALL_ENTRYPOINTS_ENABLED);
 
     private final Runnable mSysUiStateChangeListener = this::updateOverridesToSysUi;
-    private final SimpleBroadcastReceiver mContextualSearchPackageReceiver =
-            new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, (unused) -> requestUpdateProperties());
-    private final SettingsCache.OnChangeListener mContextualSearchSettingChangedListener =
-            this::onContextualSearchSettingChanged;
+    private final SimpleBroadcastReceiver mContextualSearchPackageReceiver;
     protected final EventLogArray mEventLogArray = new EventLogArray(TAG, MAX_DEBUG_EVENT_SIZE);
 
     // Cached value whether the ContextualSearch intent filter matched any enabled components.
     private boolean mIsContextualSearchIntentAvailable;
     private boolean mIsContextualSearchSettingEnabled;
 
-    protected Context mContext;
-    protected String mContextualSearchPackage;
+    protected final Context mContext;
+    protected final String mContextualSearchPackage;
+    protected final SystemUiProxy mSystemUiProxy;
+    protected final TopTaskTracker mTopTaskTracker;
 
-    public ContextualSearchStateManager() {}
-
-    public ContextualSearchStateManager(Context context) {
+    @Inject
+    public ContextualSearchStateManager(
+            @ApplicationContext Context context,
+            SettingsCache settingsCache,
+            SystemUiProxy systemUiProxy,
+            TopTaskTracker topTaskTracker,
+            DaggerSingletonTracker lifeCycle) {
         mContext = context;
+        mContextualSearchPackageReceiver =
+                new SimpleBroadcastReceiver(context, UI_HELPER_EXECUTOR,
+                        (unused) -> requestUpdateProperties());
         mContextualSearchPackage = mContext.getResources().getString(
                 com.android.internal.R.string.config_defaultContextualSearchPackageName);
+        mSystemUiProxy = systemUiProxy;
+        mTopTaskTracker = topTaskTracker;
 
         if (areAllContextualSearchFlagsDisabled()
                 || !context.getPackageManager().hasSystemFeature(FEATURE_CONTEXTUAL_SEARCH)) {
@@ -103,14 +114,23 @@
         requestUpdateProperties();
         registerSearchScreenSystemAction();
         mContextualSearchPackageReceiver.registerPkgActions(
-                context, mContextualSearchPackage, Intent.ACTION_PACKAGE_ADDED,
+                mContextualSearchPackage, Intent.ACTION_PACKAGE_ADDED,
                 Intent.ACTION_PACKAGE_CHANGED, Intent.ACTION_PACKAGE_REMOVED);
 
-        SettingsCache.INSTANCE.get(context).register(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI,
-                mContextualSearchSettingChangedListener);
-        onContextualSearchSettingChanged(
-                SettingsCache.INSTANCE.get(context).getValue(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI));
-        SystemUiProxy.INSTANCE.get(mContext).addOnStateChangeListener(mSysUiStateChangeListener);
+        SettingsCache.OnChangeListener settingChangedListener =
+                isEnabled -> mIsContextualSearchSettingEnabled = isEnabled;
+        settingsCache.register(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI, settingChangedListener);
+        mIsContextualSearchSettingEnabled =
+                settingsCache.getValue(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI);
+
+        systemUiProxy.addOnStateChangeListener(mSysUiStateChangeListener);
+
+        lifeCycle.addCloseable(() -> {
+            mContextualSearchPackageReceiver.unregisterReceiverSafely();
+            unregisterSearchScreenSystemAction();
+            settingsCache.unregister(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI, settingChangedListener);
+            systemUiProxy.removeOnStateChangeListener(mSysUiStateChangeListener);
+        });
     }
 
     /** Return {@code true} if the Settings toggle is enabled. */
@@ -118,10 +138,6 @@
         return mIsContextualSearchSettingEnabled;
     }
 
-    private void onContextualSearchSettingChanged(boolean isEnabled) {
-        mIsContextualSearchSettingEnabled = isEnabled;
-    }
-
     /** Whether search supports showing on the lockscreen. */
     protected boolean supportsShowWhenLocked() {
         return false;
@@ -208,7 +224,7 @@
 
     protected final void updateOverridesToSysUi() {
         // LPH commit haptic is always enabled
-        SystemUiProxy.INSTANCE.get(mContext).setOverrideHomeButtonLongPress(
+        mSystemUiProxy.setOverrideHomeButtonLongPress(
                 getLPHDurationMillis().orElse(0L), getLPHCustomSlopMultiplier().orElse(0f), true);
         Log.i(TAG, "Sent LPH override to sysui: " + getLPHDurationMillis().orElse(0L) + ";"
                 + getLPHCustomSlopMultiplier().orElse(0f));
@@ -227,10 +243,8 @@
                                     new ContextualSearchInvoker(mContext).show(
                                             ENTRYPOINT_SYSTEM_ACTION);
                             if (contextualSearchInvoked) {
-                                String runningPackage =
-                                        TopTaskTracker.INSTANCE.get(mContext).getCachedTopTask(
-                                                /* filterOnlyVisibleRecents */
-                                                true).getPackageName();
+                                String runningPackage = mTopTaskTracker.getCachedTopTask(
+                                        /* filterOnlyVisibleRecents */ true).getPackageName();
                                 StatsLogManager.newInstance(mContext).logger()
                                         .withPackageName(runningPackage)
                                         .log(LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION);
@@ -259,15 +273,6 @@
         }
     }
 
-    @Override
-    public void close() {
-        mContextualSearchPackageReceiver.unregisterReceiverSafely(mContext);
-        unregisterSearchScreenSystemAction();
-        SettingsCache.INSTANCE.get(mContext).unregister(SEARCH_ALL_ENTRYPOINTS_ENABLED_URI,
-                mContextualSearchSettingChangedListener);
-        SystemUiProxy.INSTANCE.get(mContext).removeOnStateChangeListener(mSysUiStateChangeListener);
-    }
-
     protected final void addEventLog(String event) {
         synchronized (mEventLogArray) {
             mEventLogArray.addLog(event);
diff --git a/quickstep/src/com/android/quickstep/util/DesksUtils.kt b/quickstep/src/com/android/quickstep/util/DesksUtils.kt
new file mode 100644
index 0000000..ccfdbb9
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/DesksUtils.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.content.Context
+import android.window.DesktopExperienceFlags
+import com.android.systemui.shared.recents.model.Task
+
+class DesksUtils {
+    companion object {
+        @JvmStatic
+        fun areMultiDesksFlagsEnabled() =
+            DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue() &&
+                DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_FRONTEND.isTrue()
+
+        /** Returns true if this [task] contains the [DesktopWallpaperActivity]. */
+        @JvmStatic
+        fun isDesktopWallpaperTask(context: Context, task: Task): Boolean {
+            val sysUiPackage =
+                context.getResources().getString(com.android.internal.R.string.config_systemUi)
+            val component = task.key.component
+            if (component != null) {
+                return component.className.contains("DesktopWallpaperActivity") &&
+                    component.packageName.contains(sysUiPackage)
+            }
+            return false
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.java b/quickstep/src/com/android/quickstep/util/DesktopTask.java
deleted file mode 100644
index fc4fc4d..0000000
--- a/quickstep/src/com/android/quickstep/util/DesktopTask.java
+++ /dev/null
@@ -1,89 +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.quickstep.util;
-
-import androidx.annotation.NonNull;
-
-import com.android.quickstep.views.TaskViewType;
-import com.android.systemui.shared.recents.model.Task;
-
-import java.util.List;
-import java.util.Objects;
-
-/**
- * A {@link Task} container that can contain N number of tasks that are part of the desktop in
- * recent tasks list.
- */
-public class DesktopTask extends GroupTask {
-
-    @NonNull
-    public final List<Task> tasks;
-
-    public DesktopTask(@NonNull List<Task> tasks) {
-        super(tasks.get(0), null, null, TaskViewType.DESKTOP);
-        this.tasks = tasks;
-    }
-
-    @Override
-    public boolean containsTask(int taskId) {
-        for (Task task : tasks) {
-            if (task.key.id == taskId) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public boolean hasMultipleTasks() {
-        return tasks.size() > 1;
-    }
-
-    @Override
-    public boolean supportsMultipleTasks() {
-        return true;
-    }
-
-    @Override
-    @NonNull
-    public List<Task> getTasks() {
-        return tasks;
-    }
-
-    @Override
-    public DesktopTask copy() {
-        return new DesktopTask(tasks);
-    }
-
-    @Override
-    public String toString() {
-        return "type=" + taskViewType + " tasks=" + tasks;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (!(o instanceof DesktopTask that)) return false;
-        if (!super.equals(o)) return false;
-        return Objects.equals(tasks, that.tasks);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(super.hashCode(), tasks);
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/util/DesktopTask.kt b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
new file mode 100644
index 0000000..fbe3bc6
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/DesktopTask.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util
+
+import com.android.quickstep.views.TaskViewType
+import com.android.systemui.shared.recents.model.Task
+
+/**
+ * A [Task] container that can contain N number of tasks that are part of the desktop in recent
+ * tasks list. Note that desktops can be empty with no tasks in them. The [deskId] makes sense only
+ * when the multiple desks feature is enabled.
+ */
+class DesktopTask(val deskId: Int, tasks: List<Task>) : GroupTask(tasks, TaskViewType.DESKTOP) {
+
+    override fun copy() = DesktopTask(deskId, tasks)
+
+    override fun toString() = "type=$taskViewType deskId=$deskId tasks=$tasks"
+
+    override fun equals(o: Any?): Boolean {
+        if (this === o) return true
+        if (o !is DesktopTask) return false
+        if (deskId != o.deskId) return false
+        return super.equals(o)
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt b/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
new file mode 100644
index 0000000..455b312
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.Display.INVALID_DISPLAY
+import com.android.systemui.shared.recents.model.Task
+
+/** Whether this displayId belongs to an external display */
+val Int.isExternalDisplay
+    get() = this != DEFAULT_DISPLAY
+
+/** Returns displayId of this [Task], default to [DEFAULT_DISPLAY] */
+val Task?.displayId
+    get() =
+        this?.key?.displayId.let { displayId ->
+            when (displayId) {
+                null -> DEFAULT_DISPLAY
+                INVALID_DISPLAY -> DEFAULT_DISPLAY
+                else -> displayId
+            }
+        }
+
+/** Returns if this task belongs tto [DEFAULT_DISPLAY] */
+val Task?.isExternalDisplay
+    get() = displayId.isExternalDisplay
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.java b/quickstep/src/com/android/quickstep/util/GroupTask.java
deleted file mode 100644
index 7aeeb2f..0000000
--- a/quickstep/src/com/android/quickstep/util/GroupTask.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2021 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 androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
-import com.android.quickstep.views.TaskViewType;
-import com.android.systemui.shared.recents.model.Task;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * A {@link Task} container that can contain one or two tasks, depending on if the two tasks
- * are represented as an app-pair in the recents task list.
- */
-public class GroupTask {
-    @NonNull
-    public final Task task1;
-    @Nullable
-    public final Task task2;
-    @Nullable
-    public final SplitBounds mSplitBounds;
-    public final TaskViewType taskViewType;
-
-    public GroupTask(@NonNull Task task) {
-        this(task, null, null);
-    }
-
-    public GroupTask(@NonNull Task t1, @Nullable Task t2, @Nullable SplitBounds splitBounds) {
-        this(t1, t2, splitBounds, t2 != null ? TaskViewType.GROUPED : TaskViewType.SINGLE);
-    }
-
-    protected GroupTask(@NonNull Task t1, @Nullable Task t2, @Nullable SplitBounds splitBounds,
-            TaskViewType taskViewType) {
-        task1 = t1;
-        task2 = t2;
-        mSplitBounds = splitBounds;
-        this.taskViewType = taskViewType;
-    }
-
-    public boolean containsTask(int taskId) {
-        return task1.key.id == taskId || (task2 != null && task2.key.id == taskId);
-    }
-
-    public boolean hasMultipleTasks() {
-        return task2 != null;
-    }
-
-    /**
-     * Returns whether this task supports multiple tasks or not.
-     */
-    public boolean supportsMultipleTasks() {
-        return taskViewType == TaskViewType.GROUPED;
-    }
-
-    /**
-     * Returns a List of all the Tasks in this GroupTask
-     */
-    public List<Task> getTasks() {
-        if (task2 == null) {
-            return Collections.singletonList(task1);
-        } else {
-            return Arrays.asList(task1, task2);
-        }
-    }
-
-    /**
-     * Create a copy of this instance
-     */
-    public GroupTask copy() {
-        return new GroupTask(
-                new Task(task1),
-                task2 != null ? new Task(task2) : null,
-                mSplitBounds);
-    }
-
-    @Override
-    public String toString() {
-        return "type=" + taskViewType + " task1=" + task1 + " task2=" + task2;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) return true;
-        if (!(o instanceof GroupTask that)) return false;
-        return taskViewType == that.taskViewType && Objects.equals(task1,
-                that.task1) && Objects.equals(task2, that.task2)
-                && Objects.equals(mSplitBounds, that.mSplitBounds);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(task1, task2, mSplitBounds, taskViewType);
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.kt b/quickstep/src/com/android/quickstep/util/GroupTask.kt
new file mode 100644
index 0000000..add8821
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2021 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.launcher3.LauncherSettings.Favorites.ITEM_TYPE_TASK
+import com.android.launcher3.model.data.TaskItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.quickstep.views.TaskViewType
+import com.android.systemui.shared.recents.model.Task
+import java.util.Objects
+
+/**
+ * An abstract class for creating [Task] containers that can be [SingleTask]s, [SplitTask]s, or
+ * [DesktopTask]s in the recent tasks list.
+ */
+abstract class GroupTask(val tasks: List<Task>, @JvmField val taskViewType: TaskViewType) {
+    fun containsTask(taskId: Int) = tasks.any { it.key.id == taskId }
+
+    /**
+     * Returns true if a task in this group has a package name that matches the given `packageName`.
+     */
+    fun containsPackage(packageName: String?) = tasks.any { it.key.packageName == packageName }
+
+    /**
+     * Returns true if a task in this group has a package name that matches the given `packageName`,
+     * and its user ID matches the given `userId`.
+     */
+    fun containsPackage(packageName: String?, userId: Int) =
+        tasks.any { it.key.packageName == packageName && it.key.userId == userId }
+
+    fun isEmpty() = tasks.isEmpty()
+
+    /** Creates a copy of this instance */
+    abstract fun copy(): GroupTask
+
+    override fun equals(o: Any?): Boolean {
+        if (this === o) return true
+        if (o !is GroupTask) return false
+        return taskViewType == o.taskViewType && tasks == o.tasks
+    }
+
+    override fun hashCode() = Objects.hash(tasks, taskViewType)
+}
+
+/** A [Task] container that must contain exactly one task in the recent tasks list. */
+class SingleTask(task: Task) : GroupTask(listOf(task), TaskViewType.SINGLE) {
+
+    val task: Task
+        get() = tasks[0]
+
+    override fun copy() = SingleTask(task)
+
+    override fun toString() = "type=$taskViewType task=$task"
+
+    override fun equals(o: Any?): Boolean {
+        if (this === o) return true
+        if (o !is SingleTask) return false
+        return super.equals(o)
+    }
+
+    companion object {
+        /** Creates a [TaskItemInfo] using the information of the SingleTask */
+        fun createTaskItemInfo(task: SingleTask): TaskItemInfo {
+            // TODO: b/344657629 - Support GroupTask in addition to SingleTask.
+            val wii =
+                WorkspaceItemInfo().apply {
+                    title = task.task.title
+                    intent = task.task.key.baseIntent
+                    itemType = ITEM_TYPE_TASK
+                    contentDescription = task.task.titleDescription
+                }
+            return TaskItemInfo(task.task.key.id, wii)
+        }
+    }
+}
+
+/**
+ * A [Task] container that must contain exactly two tasks and split bounds to represent an app-pair
+ * in the recent tasks list.
+ */
+class SplitTask(task1: Task, task2: Task, val splitBounds: SplitConfigurationOptions.SplitBounds) :
+    GroupTask(listOf(task1, task2), TaskViewType.GROUPED) {
+
+    val topLeftTask: Task
+        get() = if (splitBounds.leftTopTaskId == tasks[0].key.id) tasks[0] else tasks[1]
+
+    val bottomRightTask: Task
+        get() = if (topLeftTask == tasks[0]) tasks[1] else tasks[0]
+
+    override fun copy() = SplitTask(tasks[0], tasks[1], splitBounds)
+
+    override fun toString() =
+        "type=$taskViewType topLeftTask=$topLeftTask bottomRightTask=$bottomRightTask"
+
+    override fun equals(o: Any?): Boolean {
+        if (this === o) return true
+        if (o !is SplitTask) return false
+        if (splitBounds != o.splitBounds) return false
+        return super.equals(o)
+    }
+
+    override fun hashCode() = Objects.hash(super.hashCode(), splitBounds)
+}
diff --git a/quickstep/src/com/android/quickstep/util/IconLabelUtil.kt b/quickstep/src/com/android/quickstep/util/IconLabelUtil.kt
new file mode 100644
index 0000000..a876bca
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/IconLabelUtil.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.app.ActivityManager
+import android.content.Context
+import android.content.pm.ActivityInfo
+import android.os.UserHandle
+import com.android.launcher3.Utilities
+
+object IconLabelUtil {
+    @JvmStatic
+    @JvmOverloads
+    fun getBadgedContentDescription(
+        context: Context,
+        info: ActivityInfo,
+        userId: Int,
+        taskDescription: ActivityManager.TaskDescription? = null,
+    ): String {
+        val packageManager = context.packageManager
+        var taskLabel = taskDescription?.let { Utilities.trim(it.label) }
+        if (taskLabel.isNullOrEmpty()) {
+            taskLabel = Utilities.trim(info.loadLabel(packageManager))
+        }
+
+        val applicationLabel = Utilities.trim(info.applicationInfo.loadLabel(packageManager))
+        val badgedApplicationLabel =
+            if (userId != UserHandle.myUserId())
+                packageManager
+                    .getUserBadgedLabel(applicationLabel, UserHandle.of(userId))
+                    .toString()
+            else applicationLabel
+        return if (applicationLabel == taskLabel) badgedApplicationLabel
+        else "$badgedApplicationLabel $taskLabel"
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index a5be89a..9cdde01 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -22,7 +22,9 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
+import static com.android.launcher3.Flags.enableOverviewOnConnectedDisplays;
 import static com.android.launcher3.LauncherPrefs.ALLOW_ROTATION;
+import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SettingsCache.ROTATION_SETTING_URI;
 import static com.android.quickstep.BaseActivityInterface.getTaskDimension;
@@ -43,6 +45,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherPrefChangeListener;
 import com.android.launcher3.LauncherPrefs;
@@ -53,6 +56,7 @@
 import com.android.quickstep.BaseContainerInterface;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskAnimationManager;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
 
 import java.lang.annotation.Retention;
@@ -105,9 +109,12 @@
     // Ignore shared prefs for home rotation rotation, allowing it in if the activity supports it
     private static final int FLAG_IGNORE_ALLOW_HOME_ROTATION_PREF = 1 << 9;
 
+    // Shared prefs for fixed 90 degree rotation, activities should rotate if they support it
+    private static final int FLAG_HOME_FIXED_LANDSCAPE_PREFS = 1 << 10;
+
     private static final int MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE =
             FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_ACTIVITY
-            | FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
+                    | FLAG_MULTIPLE_ORIENTATION_SUPPORTED_BY_DENSITY;
 
     // State for which rotation watcher will be enabled. We skip it when home rotation or
     // multi-window is enabled as in that case, activity itself rotates.
@@ -225,7 +232,7 @@
 
     private boolean updateHandler() {
         mRecentsActivityRotation = inferRecentsActivityRotation(mDisplayRotation);
-        if (mRecentsActivityRotation == mTouchRotation || isRecentsActivityRotationAllowed()) {
+        if (mRecentsActivityRotation == mTouchRotation || shouldUseRealOrientation()) {
             mOrientationHandler = RecentsPagedOrientationHandler.PORTRAIT;
         } else if (mTouchRotation == ROTATION_90) {
             mOrientationHandler = RecentsPagedOrientationHandler.LANDSCAPE;
@@ -247,9 +254,13 @@
         return mStateId != oldStateId;
     }
 
+    private boolean shouldUseRealOrientation() {
+        return isRecentsActivityRotationAllowed() || isLauncherFixedLandscape();
+    }
+
     @SurfaceRotation
     private int inferRecentsActivityRotation(@SurfaceRotation int displayRotation) {
-        if (isRecentsActivityRotationAllowed()) {
+        if (shouldUseRealOrientation()) {
             return mRecentsRotation < 0 ? displayRotation : mRecentsRotation;
         } else {
             return ROTATION_0;
@@ -286,6 +297,9 @@
         if (LauncherPrefs.ALLOW_ROTATION.getSharedPrefKey().equals(s)) {
             updateHomeRotationSetting();
         }
+        if (LauncherPrefs.FIXED_LANDSCAPE_MODE.getSharedPrefKey().equals(s)) {
+            updateFixedLandscapeSetting();
+        }
     }
 
     private void updateAutoRotateSetting() {
@@ -293,6 +307,15 @@
                 mSettingsCache.getValue(ROTATION_SETTING_URI, 1));
     }
 
+    private void updateFixedLandscapeSetting() {
+        if (Flags.oneGridSpecs()) {
+            setFlag(
+                    FLAG_HOME_FIXED_LANDSCAPE_PREFS,
+                    LauncherPrefs.get(mContext).get(FIXED_LANDSCAPE_MODE)
+            );
+        }
+    }
+
     private void updateHomeRotationSetting() {
         boolean homeRotationEnabled = LauncherPrefs.get(mContext).get(ALLOW_ROTATION);
         setFlag(FLAG_HOME_ROTATION_ALLOWED_IN_PREFS, homeRotationEnabled);
@@ -305,6 +328,7 @@
         // initialize external flags
         updateAutoRotateSetting();
         updateHomeRotationSetting();
+        updateFixedLandscapeSetting();
     }
 
     private void initMultipleOrientationListeners() {
@@ -381,15 +405,19 @@
         setFlag(FLAG_IGNORE_ALLOW_HOME_ROTATION_PREF, true);
     }
 
+    public boolean isLauncherFixedLandscape() {
+        return (mFlags & FLAG_HOME_FIXED_LANDSCAPE_PREFS) == FLAG_HOME_FIXED_LANDSCAPE_PREFS;
+    }
+
     public boolean isRecentsActivityRotationAllowed() {
         // Activity rotation is allowed if the multi-simulated-rotation is not supported
         // (fallback recents or tablets) or activity rotation is enabled by various settings.
         return ((mFlags & MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
                 != MASK_MULTIPLE_ORIENTATION_SUPPORTED_BY_DEVICE)
                 || (mFlags & (FLAG_IGNORE_ALLOW_HOME_ROTATION_PREF
-                        | FLAG_HOME_ROTATION_ALLOWED_IN_PREFS
-                        | FLAG_MULTIWINDOW_ROTATION_ALLOWED
-                        | FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING)) != 0;
+                | FLAG_HOME_ROTATION_ALLOWED_IN_PREFS
+                | FLAG_MULTIWINDOW_ROTATION_ALLOWED
+                | FLAG_HOME_ROTATION_FORCE_ENABLED_FOR_TESTING)) != 0;
     }
 
     /**
@@ -571,19 +599,25 @@
     /**
      * Returns the device profile based on expected launcher rotation
      */
-    public DeviceProfile getLauncherDeviceProfile() {
-        InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
-        Point currentSize = DisplayController.INSTANCE.get(mContext).getInfo().currentSize;
-
-        int width, height;
-        if ((mRecentsActivityRotation == ROTATION_90 || mRecentsActivityRotation == ROTATION_270)) {
-            width = Math.max(currentSize.x, currentSize.y);
-            height = Math.min(currentSize.x, currentSize.y);
+    public DeviceProfile getLauncherDeviceProfile(int displayId) {
+        if (enableOverviewOnConnectedDisplays()) {
+            return RecentsDisplayModel.getINSTANCE().get(mContext).getRecentsWindowManager(
+                    displayId).getDeviceProfile();
         } else {
-            width = Math.min(currentSize.x, currentSize.y);
-            height = Math.max(currentSize.x, currentSize.y);
+            InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
+            Point currentSize = DisplayController.INSTANCE.get(mContext).getInfo().currentSize;
+
+            int width, height;
+            if ((mRecentsActivityRotation == ROTATION_90
+                    || mRecentsActivityRotation == ROTATION_270)) {
+                width = Math.max(currentSize.x, currentSize.y);
+                height = Math.min(currentSize.x, currentSize.y);
+            } else {
+                width = Math.min(currentSize.x, currentSize.y);
+                height = Math.max(currentSize.x, currentSize.y);
+            }
+            return idp.getBestMatch(width, height, mRecentsActivityRotation);
         }
-        return idp.getBestMatch(width, height, mRecentsActivityRotation);
     }
 
     private static String nameAndAddress(Object obj) {
diff --git a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
deleted file mode 100644
index 908e9f9..0000000
--- a/quickstep/src/com/android/quickstep/util/RecentsViewUtils.kt
+++ /dev/null
@@ -1,174 +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.quickstep.util
-
-import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
-import com.android.quickstep.RecentsAnimationController
-import com.android.quickstep.views.DesktopTaskView
-import com.android.quickstep.views.TaskView
-import com.android.quickstep.views.TaskViewType
-import com.android.systemui.shared.recents.model.ThumbnailData
-
-/**
- * Helper class for [com.android.quickstep.views.RecentsView]. This util class contains refactored
- * and extracted functions from RecentsView to facilitate the implementation of unit tests.
- */
-class RecentsViewUtils {
-    /** Takes a screenshot of all [taskView] and return map of taskId to the screenshot */
-    fun screenshotTasks(
-        taskView: TaskView,
-        recentsAnimationController: RecentsAnimationController,
-    ): Map<Int, ThumbnailData> =
-        taskView.taskContainers.associate {
-            it.task.key.id to recentsAnimationController.screenshotTask(it.task.key.id)
-        }
-
-    /**
-     * Sorts task groups to move desktop tasks to the end of the list.
-     *
-     * @param tasks List of group tasks to be sorted.
-     * @return Sorted list of GroupTasks to be used in the RecentsView.
-     */
-    fun sortDesktopTasksToFront(tasks: List<GroupTask>): List<GroupTask> {
-        val (desktopTasks, otherTasks) = tasks.partition { it.taskViewType == TaskViewType.DESKTOP }
-        return otherTasks + desktopTasks
-    }
-
-    /** Counts [TaskView]s that are [DesktopTaskView] instances. */
-    fun getDesktopTaskViewCount(taskViews: Iterable<TaskView>): Int =
-        taskViews.count { it is DesktopTaskView }
-
-    /** Returns a list of all large TaskView Ids from [TaskView]s */
-    fun getLargeTaskViewIds(taskViews: Iterable<TaskView>): List<Int> =
-        taskViews.filter { it.isLargeTile }.map { it.taskViewId }
-
-    /** Counts [TaskView]s that are large tiles. */
-    fun getLargeTileCount(taskViews: Iterable<TaskView>): Int = taskViews.count { it.isLargeTile }
-
-    /**
-     * Returns the first TaskView that should be displayed as a large tile.
-     *
-     * @param taskViews List of [TaskView]s
-     * @param splitSelectActive current split state
-     */
-    fun getFirstLargeTaskView(
-        taskViews: MutableIterable<TaskView>,
-        splitSelectActive: Boolean,
-    ): TaskView? =
-        taskViews.firstOrNull { it.isLargeTile && !(splitSelectActive && it is DesktopTaskView) }
-
-    /** Returns the expected focus task. */
-    fun getExpectedFocusedTask(taskViews: Iterable<TaskView>): TaskView? =
-        if (enableLargeDesktopWindowingTile()) taskViews.firstOrNull { it !is DesktopTaskView }
-        else taskViews.firstOrNull()
-
-    /**
-     * Returns the [TaskView] that should be the current page during task binding, in the following
-     * priorities:
-     * 1. Running task
-     * 2. Focused task
-     * 3. First non-desktop task
-     * 4. Last desktop task
-     * 5. null otherwise
-     */
-    fun getExpectedCurrentTask(
-        runningTaskView: TaskView?,
-        focusedTaskView: TaskView?,
-        taskViews: Iterable<TaskView>,
-    ): TaskView? =
-        runningTaskView
-            ?: focusedTaskView
-            ?: taskViews.firstOrNull { it !is DesktopTaskView }
-            ?: taskViews.lastOrNull()
-
-    /**
-     * Returns the first TaskView that is not large
-     *
-     * @param taskViews List of [TaskView]s
-     */
-    fun getFirstSmallTaskView(taskViews: MutableIterable<TaskView>): TaskView? =
-        taskViews.firstOrNull { !it.isLargeTile }
-
-    /** Returns the last TaskView that should be displayed as a large tile. */
-    fun getLastLargeTaskView(taskViews: Iterable<TaskView>): TaskView? =
-        taskViews.lastOrNull { it.isLargeTile }
-
-    /** Returns the first [TaskView], with some tasks possibly hidden in the carousel. */
-    fun getFirstTaskViewInCarousel(
-        nonRunningTaskCarouselHidden: Boolean,
-        taskViews: Iterable<TaskView>,
-        runningTaskView: TaskView?,
-    ): TaskView? =
-        taskViews.firstOrNull {
-            it.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden)
-        }
-
-    /** Returns the last [TaskView], with some tasks possibly hidden in the carousel. */
-    fun getLastTaskViewInCarousel(
-        nonRunningTaskCarouselHidden: Boolean,
-        taskViews: Iterable<TaskView>,
-        runningTaskView: TaskView?,
-    ): TaskView? =
-        taskViews.lastOrNull {
-            it.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden)
-        }
-
-    /** Returns if any small tasks are fully visible */
-    fun isAnySmallTaskFullyVisible(
-        taskViews: Iterable<TaskView>,
-        isTaskViewFullyVisible: (TaskView) -> Boolean,
-    ): Boolean = taskViews.any { !it.isLargeTile && isTaskViewFullyVisible(it) }
-
-    /** Returns the current list of [TaskView] children. */
-    fun getTaskViews(taskViewCount: Int, requireTaskViewAt: (Int) -> TaskView): Iterable<TaskView> =
-        (0 until taskViewCount).map(requireTaskViewAt)
-
-    /** Apply attachAlpha to all [TaskView] accordingly to different conditions. */
-    fun applyAttachAlpha(
-        taskViews: Iterable<TaskView>,
-        runningTaskView: TaskView?,
-        runningTaskAttachAlpha: Float,
-        nonRunningTaskCarouselHidden: Boolean,
-    ) {
-        taskViews.forEach { taskView ->
-            taskView.attachAlpha =
-                if (taskView == runningTaskView) {
-                    runningTaskAttachAlpha
-                } else {
-                    if (taskView.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden))
-                        1f
-                    else 0f
-                }
-        }
-    }
-
-    fun TaskView.isVisibleInCarousel(
-        runningTaskView: TaskView?,
-        nonRunningTaskCarouselHidden: Boolean,
-    ): Boolean =
-        if (!nonRunningTaskCarouselHidden) true
-        else getCarouselType() == runningTaskView.getCarouselType()
-
-    /** Returns the carousel type of the TaskView, and default to fullscreen if it's null. */
-    private fun TaskView?.getCarouselType(): TaskViewCarousel =
-        if (this is DesktopTaskView) TaskViewCarousel.DESKTOP else TaskViewCarousel.FULL_SCREEN
-
-    private enum class TaskViewCarousel {
-        FULL_SCREEN,
-        DESKTOP,
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
index f719bed..63eae92 100644
--- a/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
+++ b/quickstep/src/com/android/quickstep/util/ScalingWorkspaceRevealAnim.kt
@@ -54,14 +54,15 @@
     private val launcher: QuickstepLauncher,
     siblingAnimation: RectFSpringAnim?,
     windowTargetRect: RectF?,
+    playAlphaReveal: Boolean = true,
 ) {
     companion object {
         private const val FADE_DURATION_MS = 200L
         private const val SCALE_DURATION_MS = 1000L
         private const val MAX_ALPHA = 1f
         private const val MIN_ALPHA = 0f
-        private const val MAX_SIZE = 1f
-        private const val MIN_SIZE = 0.85f
+        internal const val MAX_SIZE = 1f
+        internal const val MIN_SIZE = 0.85f
 
         /**
          * Custom interpolator for both the home and wallpaper scaling. Necessary because EMPHASIZED
@@ -132,21 +133,23 @@
             SCALE_INTERPOLATOR,
         )
 
-        // Fade in quickly at the beginning of the animation, so the content doesn't look like it's
-        // popping into existence out of nowhere.
-        val fadeClamp = FADE_DURATION_MS.toFloat() / SCALE_DURATION_MS
-        workspace.alpha = MIN_ALPHA
-        animation.setViewAlpha(
-            workspace,
-            MAX_ALPHA,
-            Interpolators.clampToProgress(LINEAR, 0f, fadeClamp),
-        )
-        hotseat.alpha = MIN_ALPHA
-        animation.setViewAlpha(
-            hotseat,
-            MAX_ALPHA,
-            Interpolators.clampToProgress(LINEAR, 0f, fadeClamp),
-        )
+        if (playAlphaReveal) {
+            // Fade in quickly at the beginning of the animation, so the content doesn't look like
+            // it's popping into existence out of nowhere.
+            val fadeClamp = FADE_DURATION_MS.toFloat() / SCALE_DURATION_MS
+            workspace.alpha = MIN_ALPHA
+            animation.setViewAlpha(
+                workspace,
+                MAX_ALPHA,
+                Interpolators.clampToProgress(LINEAR, 0f, fadeClamp),
+            )
+            hotseat.alpha = MIN_ALPHA
+            animation.setViewAlpha(
+                hotseat,
+                MAX_ALPHA,
+                Interpolators.clampToProgress(LINEAR, 0f, fadeClamp),
+            )
+        }
 
         val transitionConfig = StateAnimationConfig()
 
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index f708f4b..d6e553d 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -52,9 +52,9 @@
 import com.android.launcher3.QuickstepTransitionManager
 import com.android.launcher3.R
 import com.android.launcher3.Utilities
+import com.android.launcher3.anim.AnimatedFloat
 import com.android.launcher3.anim.PendingAnimation
 import com.android.launcher3.apppairs.AppPairIcon
-import com.android.launcher3.config.FeatureFlags
 import com.android.launcher3.logging.StatsLogManager.EventEnum
 import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.statehandlers.DepthController
@@ -65,6 +65,7 @@
 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource
 import com.android.launcher3.views.BaseDragLayer
 import com.android.quickstep.TaskViewUtils
+import com.android.quickstep.util.SplitScreenUtils.Companion.extractTopParentAndChildren
 import com.android.quickstep.views.FloatingAppPairView
 import com.android.quickstep.views.FloatingTaskView
 import com.android.quickstep.views.GroupedTaskView
@@ -94,7 +95,7 @@
             val fadeWithThumbnail: Boolean,
             val isStagedTask: Boolean,
             val iconView: View?,
-            val contentDescription: CharSequence?
+            val contentDescription: CharSequence?,
         )
     }
 
@@ -104,7 +105,7 @@
      */
     fun getFirstAnimInitViews(
         taskViewSupplier: Supplier<TaskView>,
-        splitSelectSourceSupplier: Supplier<SplitSelectSource?>
+        splitSelectSourceSupplier: Supplier<SplitSelectSource?>,
     ): SplitAnimInitProps {
         val splitSelectSource = splitSelectSourceSupplier.get()
         if (!splitSelectStateController.isAnimateCurrentTaskDismissal) {
@@ -116,7 +117,7 @@
                 fadeWithThumbnail = false,
                 isStagedTask = true,
                 iconView = null,
-                splitSelectSource.itemInfo.contentDescription
+                splitSelectSource.itemInfo.contentDescription,
             )
         } else if (splitSelectStateController.isDismissingFromSplitPair) {
             // Initiating split from overview, but on a split pair
@@ -126,12 +127,12 @@
                     val drawable = getDrawable(container.iconView, splitSelectSource)
                     return SplitAnimInitProps(
                         container.snapshotView,
-                        container.splitAnimationThumbnail,
+                        container.thumbnail,
                         drawable,
                         fadeWithThumbnail = true,
                         isStagedTask = true,
                         iconView = container.iconView.asView(),
-                        container.task.titleDescription
+                        container.task.titleDescription,
                     )
                 }
             }
@@ -142,16 +143,16 @@
         } else {
             // Initiating split from overview on fullscreen task TaskView
             val taskView = taskViewSupplier.get()
-            taskView.taskContainers.first().let {
+            taskView.firstTaskContainer!!.let {
                 val drawable = getDrawable(it.iconView, splitSelectSource)
                 return SplitAnimInitProps(
                     it.snapshotView,
-                    it.splitAnimationThumbnail,
+                    it.thumbnail,
                     drawable,
                     fadeWithThumbnail = true,
                     isStagedTask = true,
                     iconView = it.iconView.asView(),
-                    it.task.titleDescription
+                    it.task.titleDescription,
                 )
             }
         }
@@ -189,42 +190,38 @@
         deviceProfile: DeviceProfile,
         taskViewWidth: Int,
         taskViewHeight: Int,
-        isPrimaryTaskSplitting: Boolean
+        isPrimaryTaskSplitting: Boolean,
     ) {
-        val snapshot = taskContainer.snapshotView
+        val taskContentView = taskContainer.taskContentView
         val iconView: View = taskContainer.iconView.asView()
-        if (!enableRefactorTaskThumbnail()) {
+        if (enableRefactorTaskThumbnail()) {
+            builder.add(
+                AnimatedFloat { v -> taskContainer.taskView.splitSplashAlpha = v }
+                    .animateToValue(1f)
+            )
+        } else {
             val thumbnailViewDeprecated = taskContainer.thumbnailViewDeprecated
             builder.add(
                 ObjectAnimator.ofFloat(
                     thumbnailViewDeprecated,
                     TaskThumbnailViewDeprecated.SPLASH_ALPHA,
-                    1f
+                    1f,
                 )
             )
             thumbnailViewDeprecated.setShowSplashForSplitSelection(true)
-        } else {
-            builder.add(
-                ValueAnimator.ofFloat(0f, 1f).apply {
-                    addUpdateListener {
-                        taskContainer.taskContainerData.thumbnailSplashProgress.value =
-                            it.animatedFraction
-                    }
-                }
-            )
         }
         // With the new `IconAppChipView`, we always want to keep the chip pinned to the
         // top left of the task / thumbnail.
         if (enableOverviewIconMenu()) {
             builder.add(
                 ObjectAnimator.ofFloat(
-                    (iconView as IconAppChipView).splitTranslationX,
+                    (iconView as IconAppChipView).getSplitTranslationX(),
                     MULTI_PROPERTY_VALUE,
-                    0f
+                    0f,
                 )
             )
             builder.add(
-                ObjectAnimator.ofFloat(iconView.splitTranslationY, MULTI_PROPERTY_VALUE, 0f)
+                ObjectAnimator.ofFloat(iconView.getSplitTranslationY(), MULTI_PROPERTY_VALUE, 0f)
             )
         }
 
@@ -244,7 +241,11 @@
             val centerThumbnailTranslationX: Float = (taskViewWidth - snapshotViewSize.x) / 2f
             val finalScaleX: Float = taskViewWidth.toFloat() / snapshotViewSize.x
             builder.add(
-                ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_X, centerThumbnailTranslationX)
+                ObjectAnimator.ofFloat(
+                    taskContentView,
+                    View.TRANSLATION_X,
+                    centerThumbnailTranslationX,
+                )
             )
             if (!enableOverviewIconMenu()) {
                 // icons are anchored from Gravity.END, so need to use negative translation
@@ -253,15 +254,17 @@
                     ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, -centerIconTranslationX)
                 )
             }
-            builder.add(ObjectAnimator.ofFloat(snapshot, View.SCALE_X, finalScaleX))
+            builder.add(ObjectAnimator.ofFloat(taskContentView, View.SCALE_X, finalScaleX))
 
             // Reset other dimensions
             // TODO(b/271468547), can't set Y translate to 0, need to account for top space
-            snapshot.scaleY = 1f
+            taskContentView.scaleY = 1f
             val translateYResetVal: Float =
                 if (!isPrimaryTaskSplitting) 0f
                 else deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
-            builder.add(ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_Y, translateYResetVal))
+            builder.add(
+                ObjectAnimator.ofFloat(taskContentView, View.TRANSLATION_Y, translateYResetVal)
+            )
         } else {
             val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
             // Center view first so scaling happens uniformly, alternatively we can move pivotY to 0
@@ -284,18 +287,22 @@
             }
             val finalScaleY: Float = thumbnailSize.toFloat() / snapshotViewSize.y
             builder.add(
-                ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_Y, centerThumbnailTranslationY)
+                ObjectAnimator.ofFloat(
+                    taskContentView,
+                    View.TRANSLATION_Y,
+                    centerThumbnailTranslationY,
+                )
             )
 
             if (!enableOverviewIconMenu()) {
                 // icons are anchored from Gravity.END, so need to use negative translation
                 builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, 0f))
             }
-            builder.add(ObjectAnimator.ofFloat(snapshot, View.SCALE_Y, finalScaleY))
+            builder.add(ObjectAnimator.ofFloat(taskContentView, View.SCALE_Y, finalScaleY))
 
             // Reset other dimensions
-            snapshot.scaleX = 1f
-            builder.add(ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_X, 0f))
+            taskContentView.scaleX = 1f
+            builder.add(ObjectAnimator.ofFloat(taskContentView, View.TRANSLATION_X, 0f))
         }
     }
 
@@ -306,7 +313,7 @@
     fun addScrimBehindAnim(
         pendingAnimation: PendingAnimation,
         container: RecentsViewContainer,
-        context: Context
+        context: Context,
     ): View {
         val scrim = View(context)
         val recentsView = container.getOverviewPanel<RecentsView<*, *>>()
@@ -334,8 +341,8 @@
             Interpolators.clampToProgress(
                 timings.backingScrimFadeInterpolator,
                 timings.backingScrimFadeInStartOffset,
-                timings.backingScrimFadeInEndOffset
-            )
+                timings.backingScrimFadeInEndOffset,
+            ),
         )
 
         return scrim
@@ -358,7 +365,7 @@
     fun createPlaceholderDismissAnim(
         container: RecentsViewContainer,
         splitDismissEvent: EventEnum,
-        duration: Long?
+        duration: Long?,
     ): AnimatorSet {
         val animatorSet = AnimatorSet()
         duration?.let { animatorSet.duration = it }
@@ -375,7 +382,7 @@
             Rect(0, 0, floatingTask.width, floatingTask.height),
             false,
             null,
-            onScreenRectF
+            onScreenRectF,
         )
         // Get the part of the floatingTask that intersects with the DragLayer (i.e. the
         // on-screen portion)
@@ -383,7 +390,7 @@
             dragLayer.left.toFloat(),
             dragLayer.top.toFloat(),
             dragLayer.right.toFloat(),
-            dragLayer.bottom.toFloat()
+            dragLayer.bottom.toFloat(),
         )
         animatorSet.play(
             ObjectAnimator.ofFloat(
@@ -393,8 +400,8 @@
                     floatingTask,
                     onScreenRectF,
                     floatingTask.stagePosition,
-                    container.deviceProfile
-                )
+                    container.deviceProfile,
+                ),
             )
         )
         animatorSet.addListener(
@@ -403,7 +410,7 @@
                     splitSelectStateController.resetState()
                     safeRemoveViewFromDragLayer(
                         container,
-                        splitSelectStateController.splitInstructionsView
+                        splitSelectStateController.splitInstructionsView,
                     )
                 }
             }
@@ -429,8 +436,8 @@
             Interpolators.clampToProgress(
                 Interpolators.LINEAR,
                 timings.instructionsContainerFadeInStartOffset,
-                timings.instructionsContainerFadeInEndOffset
-            )
+                timings.instructionsContainerFadeInEndOffset,
+            ),
         )
         anim.addFloat(
             splitInstructionsView,
@@ -440,8 +447,8 @@
             Interpolators.clampToProgress(
                 Interpolators.EMPHASIZED_DECELERATE,
                 timings.instructionsUnfoldStartOffset,
-                timings.instructionsUnfoldEndOffset
-            )
+                timings.instructionsUnfoldEndOffset,
+            ),
         )
         return anim
     }
@@ -459,7 +466,7 @@
     fun playAnimPlaceholderToFullscreen(
         container: RecentsViewContainer,
         view: View,
-        resetCallback: Optional<Runnable>
+        resetCallback: Optional<Runnable>,
     ) {
         val stagedTaskView = view as FloatingTaskView
 
@@ -481,16 +488,12 @@
             RectF(firstTaskStartingBounds),
             firstTaskEndingBounds,
             false /* fadeWithThumbnail */,
-            true /* isStagedTask */
+            true, /* isStagedTask */
         )
 
         pendingAnimation.addEndListener {
             splitSelectStateController.launchInitialAppFullscreen {
-                if (FeatureFlags.enableSplitContextually()) {
-                    splitSelectStateController.resetState()
-                } else if (resetCallback.isPresent) {
-                    resetCallback.get().run()
-                }
+                splitSelectStateController.resetState()
             }
         }
 
@@ -515,7 +518,7 @@
         info: TransitionInfo?,
         t: Transaction?,
         finishCallback: Runnable,
-        cornerRadius: Float
+        cornerRadius: Float,
     ) {
         if (info == null && t == null) {
             // (Legacy animation) Tapping a split tile in Overview
@@ -534,7 +537,7 @@
                 nonApps,
                 stateManager,
                 depthController,
-                finishCallback
+                finishCallback,
             )
 
             return
@@ -552,7 +555,7 @@
                 depthController,
                 info,
                 t,
-                finishCallback
+                finishCallback,
             )
         } else if (launchingIconView != null) {
             // Tapping an app pair icon
@@ -567,7 +570,7 @@
                     info,
                     t,
                     finishCallback,
-                    cornerRadius
+                    cornerRadius,
                 )
             } else {
                 composeFullscreenIconSplitLaunchAnimator(
@@ -575,7 +578,7 @@
                     info,
                     t,
                     finishCallback,
-                    appPairLaunchingAppIndex
+                    appPairLaunchingAppIndex,
                 )
             }
         } else {
@@ -591,7 +594,7 @@
                 info,
                 t,
                 finishCallback,
-                cornerRadius
+                cornerRadius,
             )
         }
     }
@@ -607,7 +610,7 @@
         depthController: DepthController?,
         info: TransitionInfo,
         t: Transaction,
-        finishCallback: Runnable
+        finishCallback: Runnable,
     ) {
         TaskViewUtils.composeRecentsSplitLaunchAnimator(
             launchingTaskView,
@@ -615,7 +618,7 @@
             depthController,
             info,
             t,
-            finishCallback
+            finishCallback,
         )
     }
 
@@ -633,7 +636,7 @@
         nonApps: Array<RemoteAnimationTarget>,
         stateManager: StateManager<*, *>,
         depthController: DepthController?,
-        finishCallback: Runnable
+        finishCallback: Runnable,
     ) {
         TaskViewUtils.composeRecentsSplitLaunchAnimatorLegacy(
             launchingTaskView,
@@ -644,7 +647,7 @@
             nonApps,
             stateManager,
             depthController,
-            finishCallback
+            finishCallback,
         )
     }
 
@@ -655,7 +658,7 @@
      */
     fun hasChangesForBothAppPairs(
         launchingIconView: AppPairIcon,
-        transitionInfo: TransitionInfo
+        transitionInfo: TransitionInfo,
     ): Int {
         val intent1 = launchingIconView.info.getFirstApp().intent.component?.packageName
         val intent2 = launchingIconView.info.getSecondApp().intent.component?.packageName
@@ -716,7 +719,7 @@
         transitionInfo: TransitionInfo,
         t: Transaction,
         finishCallback: Runnable,
-        windowRadius: Float
+        windowRadius: Float,
     ) {
         // If launching an app pair from Taskbar inside of an app context (no access to Launcher),
         // use the scale-up animation
@@ -725,7 +728,7 @@
                 transitionInfo,
                 t,
                 finishCallback,
-                WINDOWING_MODE_MULTI_WINDOW
+                WINDOWING_MODE_MULTI_WINDOW,
             )
             return
         }
@@ -753,12 +756,11 @@
         // launcher side animation)
         val leftTopApp =
             leafRoots.single { change ->
-                (isLeftRightSplit && change.endAbsBounds.left == 0) ||
-                    (!isLeftRightSplit && change.endAbsBounds.top == 0)
+                (isLeftRightSplit && change.endAbsBounds.left <= 0) ||
+                    (!isLeftRightSplit && change.endAbsBounds.top <= 0)
             }
         val dividerPos =
-            if (isLeftRightSplit) leftTopApp.endAbsBounds.right
-            else leftTopApp.endAbsBounds.bottom
+            if (isLeftRightSplit) leftTopApp.endAbsBounds.right else leftTopApp.endAbsBounds.bottom
 
         // Create a new floating view in Launcher, positioned above the launching icon
         val drawableArea = launchingIconView.iconDrawableArea
@@ -773,7 +775,7 @@
                 drawableArea,
                 appIcon1,
                 appIcon2,
-                dividerPos
+                dividerPos,
             )
         floatingView.bringToFront()
 
@@ -784,7 +786,7 @@
                 finishCallback,
                 launcher,
                 floatingView,
-                mainRootCandidate
+                mainRootCandidate,
             )
         iconLaunchValueAnimator.addListener(
             object : AnimatorListenerAdapter() {
@@ -810,7 +812,7 @@
         transitionInfo: TransitionInfo,
         t: Transaction,
         finishCallback: Runnable,
-        launchFullscreenIndex: Int
+        launchFullscreenIndex: Int,
     ) {
         // If launching an app pair from Taskbar inside of an app context (no access to Launcher),
         // use the scale-up animation
@@ -819,7 +821,7 @@
                 transitionInfo,
                 t,
                 finishCallback,
-                WINDOWING_MODE_FULLSCREEN
+                WINDOWING_MODE_FULLSCREEN,
             )
             return
         }
@@ -871,7 +873,7 @@
                 drawableArea,
                 appIcon,
                 null /*appIcon2*/,
-                0 /*dividerPos*/
+                0, /*dividerPos*/
             )
         floatingView.bringToFront()
         launchAnimation.play(
@@ -886,7 +888,7 @@
         finishCallback: Runnable,
         launcher: QuickstepLauncher,
         floatingView: FloatingAppPairView,
-        rootCandidate: Change
+        rootCandidate: Change,
     ): ValueAnimator {
         val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
         val timings = AnimUtils.getDeviceAppPairLaunchTimings(dp.isTablet)
@@ -900,7 +902,7 @@
                     Interpolators.LINEAR,
                     valueAnimator.animatedFraction,
                     timings.appRevealStartOffset,
-                    timings.appRevealEndOffset
+                    timings.appRevealEndOffset,
                 )
 
             // Set the alpha of the shell layer (2 apps + divider)
@@ -917,8 +919,8 @@
                         Interpolators.clampToProgress(
                             timings.getStagedRectXInterpolator(),
                             timings.stagedRectSlideStartOffset,
-                            timings.stagedRectSlideEndOffset
-                        )
+                            timings.stagedRectSlideEndOffset,
+                        ),
                     )
                 var mDy =
                     FloatProp(
@@ -927,8 +929,8 @@
                         Interpolators.clampToProgress(
                             Interpolators.EMPHASIZED,
                             timings.stagedRectSlideStartOffset,
-                            timings.stagedRectSlideEndOffset
-                        )
+                            timings.stagedRectSlideEndOffset,
+                        ),
                     )
                 var mScaleX =
                     FloatProp(
@@ -937,8 +939,8 @@
                         Interpolators.clampToProgress(
                             Interpolators.EMPHASIZED,
                             timings.stagedRectSlideStartOffset,
-                            timings.stagedRectSlideEndOffset
-                        )
+                            timings.stagedRectSlideEndOffset,
+                        ),
                     )
                 var mScaleY =
                     FloatProp(
@@ -947,8 +949,8 @@
                         Interpolators.clampToProgress(
                             Interpolators.EMPHASIZED,
                             timings.stagedRectSlideStartOffset,
-                            timings.stagedRectSlideEndOffset
-                        )
+                            timings.stagedRectSlideEndOffset,
+                        ),
                     )
 
                 override fun onUpdate(percent: Float, initOnly: Boolean) {
@@ -983,42 +985,21 @@
         transitionInfo: TransitionInfo,
         t: Transaction,
         finishCallback: Runnable,
-        windowingMode: Int
+        windowingMode: Int,
     ) {
         val launchAnimation = AnimatorSet()
         val progressUpdater = ValueAnimator.ofFloat(0f, 1f)
         progressUpdater.setDuration(QuickstepTransitionManager.APP_LAUNCH_DURATION)
         progressUpdater.interpolator = Interpolators.EMPHASIZED
 
-        var rootCandidate: Change? = null
-
-        for (change in transitionInfo.changes) {
-            val taskInfo: RunningTaskInfo = change.taskInfo ?: continue
-
-            // TODO (b/316490565): Replace this logic when SplitBounds is available to
-            //  startAnimation() and we can know the precise taskIds of launching tasks.
-            if (
-                taskInfo.windowingMode == windowingMode &&
-                    (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT)
-            ) {
-                // Found one!
-                rootCandidate = change
-                break
-            }
-        }
-
-        // If we could not find a proper root candidate, something went wrong.
-        check(rootCandidate != null) { "Could not find a split root candidate" }
-
-        // Recurse up the tree until parent is null, then we've found our root.
-        var parentToken: WindowContainerToken? = rootCandidate.parent
-        while (parentToken != null) {
-            rootCandidate = transitionInfo.getChange(parentToken) ?: break
-            parentToken = rootCandidate.parent
-        }
-
-        // Make sure nothing weird happened, like getChange() returning null.
-        check(rootCandidate != null) { "Failed to find a root leash" }
+        val splitTree: Pair<Change, List<Change>>? = extractTopParentAndChildren(transitionInfo)
+        check(splitTree != null) { "Could not find a split root candidate" }
+        val rootCandidate = splitTree.first
+        val stageRootTaskIds: Set<Int> = splitTree.second.map { it.taskInfo!!.taskId }.toSet()
+        val leafTasks: List<Change> =
+            transitionInfo.changes
+                .filter { it.taskInfo != null && it.taskInfo!!.parentTaskId in stageRootTaskIds }
+                .toList()
 
         // Starting position is a 34% size tile centered in the middle of the screen.
         // Ending position is the full device screen.
@@ -1052,6 +1033,42 @@
                 override fun onAnimationEnd(animation: Animator) {
                     finishCallback.run()
                 }
+
+                override fun onAnimationStart(animation: Animator) {
+                    // Reset leaf and stage root tasks, animation can begin from freeform windows
+                    for (leaf in leafTasks) {
+                        val endAbsBounds = leaf.endAbsBounds
+
+                        t.setAlpha(leaf.leash, 1f)
+                        t.setCrop(
+                            leaf.leash,
+                            0f,
+                            0f,
+                            endAbsBounds.width().toFloat(),
+                            endAbsBounds.height().toFloat(),
+                        )
+                        t.setPosition(leaf.leash, 0f, 0f)
+                    }
+
+                    for (stageRoot in splitTree.second) {
+                        val endAbsBounds = stageRoot.endAbsBounds
+
+                        t.setAlpha(stageRoot.leash, 1f)
+                        t.setCrop(
+                            stageRoot.leash,
+                            0f,
+                            0f,
+                            endAbsBounds.width().toFloat(),
+                            endAbsBounds.height().toFloat(),
+                        )
+                        t.setPosition(
+                            stageRoot.leash,
+                            endAbsBounds.left.toFloat(),
+                            endAbsBounds.top.toFloat(),
+                        )
+                    }
+                    t.apply()
+                }
             }
         )
 
@@ -1070,7 +1087,7 @@
         transitionInfo: TransitionInfo,
         t: Transaction,
         finishCallback: Runnable,
-        cornerRadius: Float
+        cornerRadius: Float,
     ) {
         var splitRoot1: Change? = null
         var splitRoot2: Change? = null
@@ -1135,7 +1152,7 @@
                     Interpolators.LINEAR,
                     valueAnimator.animatedFraction,
                     0.8f,
-                    1f
+                    1f,
                 )
             for (leash in openingTargets) {
                 animTransaction.setAlpha(leash, progress)
diff --git a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
index d982e81..7787e30 100644
--- a/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitScreenUtils.kt
@@ -33,38 +33,34 @@
         // TODO(b/254378592): Remove these methods when the two classes are reunited
         /** Converts the shell version of SplitBounds to the launcher version */
         @JvmStatic
-        fun convertShellSplitBoundsToLauncher(
-            shellSplitBounds: SplitBounds?
-        ): SplitConfigurationOptions.SplitBounds? {
-            return if (shellSplitBounds == null) {
-                null
-            } else {
-                SplitConfigurationOptions.SplitBounds(
-                    shellSplitBounds.leftTopBounds,
-                    shellSplitBounds.rightBottomBounds,
-                    shellSplitBounds.leftTopTaskId,
-                    shellSplitBounds.rightBottomTaskId,
-                    shellSplitBounds.snapPosition
-                )
-            }
-        }
+        fun convertShellSplitBoundsToLauncher(shellSplitBounds: SplitBounds) =
+            SplitConfigurationOptions.SplitBounds(
+                shellSplitBounds.leftTopBounds,
+                shellSplitBounds.rightBottomBounds,
+                shellSplitBounds.leftTopTaskId,
+                shellSplitBounds.rightBottomTaskId,
+                shellSplitBounds.snapPosition,
+            )
 
         /**
          * Given a TransitionInfo, generates the tree structure for those changes and extracts out
-         * the top most root and it's two immediate children.
-         * Changes can be provided in any order.
+         * the top most root and it's two immediate children. Changes can be provided in any order.
          *
-         * @return a [Pair] where first -> top most split root,
+         * @return null if no root is found, otherwise a [Pair] where first -> top most split root,
          *         second -> [List] of 2, leftTop/bottomRight stage roots
          */
-        fun extractTopParentAndChildren(transitionInfo: TransitionInfo):
-                Pair<Change, List<Change>>? {
+        fun extractTopParentAndChildren(
+            transitionInfo: TransitionInfo
+        ): Pair<Change, List<Change>>? {
             val parentToChildren = mutableMapOf<Change, MutableList<Change>>()
             val hasParent = mutableSetOf<Change>()
             // filter out anything that isn't opening and the divider
-            val taskChanges: List<Change> = transitionInfo.changes
-                    .filter { change -> (change.mode == TRANSIT_OPEN ||
-                            change.mode == TRANSIT_TO_FRONT) && change.flags < FLAG_FIRST_CUSTOM}
+            val taskChanges: List<Change> =
+                transitionInfo.changes
+                    .filter { change ->
+                        (change.mode == TRANSIT_OPEN || change.mode == TRANSIT_TO_FRONT) &&
+                            change.flags < FLAG_FIRST_CUSTOM
+                    }
                     .toList()
 
             // 1. Build Parent-Child Relationships
@@ -73,8 +69,8 @@
                 //  startAnimation() and we can know the precise taskIds of launching tasks.
                 change.parent?.let { parent ->
                     parentToChildren
-                            .getOrPut(transitionInfo.getChange(parent)!!) { mutableListOf() }
-                            .add(change)
+                        .getOrPut(transitionInfo.getChange(parent)!!) { mutableListOf() }
+                        .add(change)
                     hasParent.add(change)
                 }
             }
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index c524286..fd8b356 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -71,7 +71,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.apppairs.AppPairIcon;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.IconProvider;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
@@ -169,7 +168,7 @@
     private final BackPressHandler mSplitBackHandler = new BackPressHandler() {
         @Override
         public boolean canHandleBack() {
-            return FeatureFlags.enableSplitContextually() && isSplitSelectActive();
+            return isSplitSelectActive();
         }
 
         @Override
@@ -260,7 +259,7 @@
                     GroupTask groupTask = taskGroups.get(i);
                     if (isInstanceOfAppPair(
                             groupTask, componentKeys.get(0), componentKeys.get(1))) {
-                        lastActiveTasks[0] = groupTask.task1;
+                        lastActiveTasks[0] = ((SplitTask) groupTask).getTopLeftTask();
                         break;
                     }
                 }
@@ -314,11 +313,15 @@
      */
     public boolean isInstanceOfAppPair(GroupTask groupTask, @NonNull ComponentKey componentKey1,
             @NonNull ComponentKey componentKey2) {
-        return ((isInstanceOfComponent(groupTask.task1, componentKey1)
-                && isInstanceOfComponent(groupTask.task2, componentKey2))
-                ||
-                (isInstanceOfComponent(groupTask.task1, componentKey2)
-                        && isInstanceOfComponent(groupTask.task2, componentKey1)));
+        if (groupTask instanceof SplitTask splitTask) {
+            return ((isInstanceOfComponent(splitTask.getTopLeftTask(), componentKey1)
+                    && isInstanceOfComponent(splitTask.getBottomRightTask(), componentKey2))
+                    ||
+                    (isInstanceOfComponent(splitTask.getTopLeftTask(), componentKey2)
+                            && isInstanceOfComponent(splitTask.getBottomRightTask(),
+                            componentKey1)));
+        }
+        return false;
     }
 
     /**
@@ -678,7 +681,7 @@
         }
 
         @Override
-        public void startAnimation(IBinder transition, TransitionInfo info,
+        public void startAnimation(IBinder transition, TransitionInfo transitionInfo,
                 SurfaceControl.Transaction t,
                 IRemoteTransitionFinishedCallback finishedCallback) {
             final Runnable finishAdapter = () ->  {
@@ -708,7 +711,7 @@
                         null /* nonApps */,
                         mStateManager,
                         mDepthController,
-                        info, t, () -> {
+                        transitionInfo, t, () -> {
                             finishAdapter.run();
                             cleanup(true /*success*/);
                         },
@@ -739,8 +742,7 @@
     }
 
     /**
-     * To be called whenever we exit split selection state. If
-     * {@link FeatureFlags#enableSplitContextually()} is set, this should be the
+     * To be called whenever we exit split selection state. This should be the
      * central way split is getting reset, which should then go through the callbacks to reset
      * other state.
      */
@@ -920,7 +922,7 @@
 
             @Override
             public void onRecentsAnimationStart(RecentsAnimationController controller,
-                    RecentsAnimationTargets targets) {
+                    RecentsAnimationTargets targets, TransitionInfo transitionInfo) {
                 StatsLogManager.LauncherEvent launcherDesktopSplitEvent =
                         mSplitPosition == STAGE_POSITION_BOTTOM_OR_RIGHT ?
                         LAUNCHER_DESKTOP_MODE_SPLIT_RIGHT_BOTTOM :
diff --git a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
index bdfaa48..d3390b4 100644
--- a/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitToWorkspaceController.java
@@ -16,6 +16,7 @@
 
 package com.android.quickstep.util;
 
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
 import android.animation.Animator;
@@ -89,7 +90,7 @@
         MODEL_EXECUTOR.execute(() -> {
             PackageItemInfo infoInOut = new PackageItemInfo(pendingIntent.getCreatorPackage(),
                     pendingIntent.getCreatorUserHandle());
-            mIconCache.getTitleAndIconForApp(infoInOut, false);
+            mIconCache.getTitleAndIconForApp(infoInOut, DEFAULT_LOOKUP_FLAG);
             Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
 
             view.post(() -> {
@@ -218,6 +219,6 @@
     }
 
     private boolean shouldIgnoreSecondSplitLaunch() {
-        return !FeatureFlags.enableSplitContextually() || !mController.isSplitSelectActive();
+        return !mController.isSplitSelectActive();
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
index 0ba4083..0e27139 100644
--- a/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitWithKeyboardShortcutController.java
@@ -16,7 +16,6 @@
 
 package com.android.quickstep.util;
 
-import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_RIGHT_BOTTOM;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -30,6 +29,7 @@
 import android.app.ActivityOptions;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.window.TransitionInfo;
 
 import androidx.annotation.BinderThread;
 
@@ -40,7 +40,6 @@
 import com.android.quickstep.OverviewComponentObserver;
 import com.android.quickstep.RecentsAnimationCallbacks;
 import com.android.quickstep.RecentsAnimationController;
-import com.android.quickstep.RecentsAnimationDeviceState;
 import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SystemUiProxy;
@@ -55,18 +54,15 @@
 
     private final QuickstepLauncher mLauncher;
     private final SplitSelectStateController mController;
-    private final RecentsAnimationDeviceState mDeviceState;
     private final OverviewComponentObserver mOverviewComponentObserver;
 
     private final int mSplitPlaceholderSize;
     private final int mSplitPlaceholderInset;
 
-    public SplitWithKeyboardShortcutController(QuickstepLauncher launcher,
-            SplitSelectStateController controller,
-            RecentsAnimationDeviceState deviceState) {
+    public SplitWithKeyboardShortcutController(
+            QuickstepLauncher launcher, SplitSelectStateController controller) {
         mLauncher = launcher;
         mController = controller;
-        mDeviceState = deviceState;
         mOverviewComponentObserver = OverviewComponentObserver.INSTANCE.get(launcher);
 
         mSplitPlaceholderSize = mLauncher.getResources().getDimensionPixelSize(
@@ -77,9 +73,8 @@
 
     @BinderThread
     public void enterStageSplit(boolean leftOrTop) {
-        if (!enableSplitContextually() ||
-                // Do not enter stage split from keyboard shortcuts if the user is already in split
-                TopTaskTracker.INSTANCE.get(mLauncher).getRunningSplitTaskIds().length == 2) {
+        if (TopTaskTracker.INSTANCE.get(mLauncher).getRunningSplitTaskIds().length == 2) {
+            // Do not enter stage split from keyboard shortcuts if the user is already in split
             return;
         }
         RecentsAnimationCallbacks callbacks = new RecentsAnimationCallbacks(
@@ -104,10 +99,6 @@
         });
     }
 
-    public void onDestroy() {
-        mDeviceState.destroy();
-    }
-
     private class SplitWithKeyboardShortcutRecentsAnimationListener implements
             RecentsAnimationCallbacks.RecentsAnimationListener {
 
@@ -122,7 +113,7 @@
 
         @Override
         public void onRecentsAnimationStart(RecentsAnimationController controller,
-                RecentsAnimationTargets targets) {
+                RecentsAnimationTargets targets, TransitionInfo transitionInfo) {
             mController.setInitialTaskSelect(mRunningTaskInfo,
                     mLeftOrTop ? STAGE_POSITION_TOP_OR_LEFT : STAGE_POSITION_BOTTOM_OR_RIGHT,
                     null /* itemInfo */,
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 12ca257..a2856a6 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -81,8 +81,10 @@
 
     public StaggeredWorkspaceAnim(QuickstepLauncher launcher, float velocity,
             boolean animateOverviewScrim, @Nullable View ignoredView, boolean staggerWorkspace) {
+        boolean isPinnedTaskbarAndNotInDesktopMode = DisplayController.isPinnedTaskbar(launcher)
+                && !DisplayController.isInDesktopMode(launcher);
         mTaskbarDurationInMs = QuickstepTransitionManager.getTaskbarToHomeDuration(
-                DisplayController.isPinnedTaskbar(launcher));
+                isPinnedTaskbarAndNotInDesktopMode);
         prepareToAnimate(launcher, animateOverviewScrim);
 
         mIgnoredView = ignoredView;
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index 828322b..d2f9652 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -18,6 +18,7 @@
 
 import android.animation.Animator;
 import android.animation.RectEvaluator;
+import android.app.PictureInPictureParams;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -25,6 +26,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.Log;
+import android.util.Rational;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.View;
@@ -50,8 +52,6 @@
 
     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;
@@ -158,9 +158,8 @@
             // not a valid rectangle to use for cropping app surface
             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) {
+        } else if (!PictureInPictureParams.isSameAspectRatio(sourceRectHint,
+                new Rational(destinationBounds.width(), destinationBounds.height()))) {
             // The source rect hint does not aspect ratio
             reasonForCreateOverlay = "Source rect hint does not match aspect ratio "
                     + sourceRectHint + " aspect ratio " + aspectRatio;
@@ -169,9 +168,6 @@
 
         if (sourceRectHint.isEmpty()) {
             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.
             mPipContentOverlay = new PipContentOverlay.PipAppIconOverlay(view.getContext(),
                     mAppBounds, mDestinationBounds,
                     new IconProvider(context).getIcon(mActivityInfo), appIconSizePx);
diff --git a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
index f3b984b8..d92cc86 100644
--- a/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
+++ b/quickstep/src/com/android/quickstep/util/SystemWindowManagerProxy.java
@@ -27,32 +27,33 @@
 import android.view.WindowMetrics;
 
 import com.android.internal.policy.SystemBarUtils;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.util.window.CachedDisplayInfo;
 import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.quickstep.SystemUiProxy;
+import com.android.window.flags.Flags;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 
 import java.util.List;
 import java.util.Set;
 
+import javax.inject.Inject;
+
 /**
  * Extension of {@link WindowManagerProxy} with some assumption for the default system Launcher
  */
+@LauncherAppSingleton
 public class SystemWindowManagerProxy extends WindowManagerProxy {
 
-    private final TISBindHelper mTISBindHelper;
+    private final DesktopVisibilityController mDesktopVisibilityController;
 
-    public SystemWindowManagerProxy(Context context) {
+
+    @Inject
+    public SystemWindowManagerProxy(DesktopVisibilityController desktopVisibilityController) {
         super(true);
-        mTISBindHelper = new TISBindHelper(context, binder -> {});
-    }
-
-    @Override
-    public void close() {
-        super.close();
-        mTISBindHelper.onDestroy();
+        mDesktopVisibilityController = desktopVisibilityController;
     }
 
     @Override
@@ -62,10 +63,18 @@
     }
 
     @Override
-    public boolean isInDesktopMode() {
-        DesktopVisibilityController desktopController =
-                mTISBindHelper.getDesktopVisibilityController();
-        return desktopController != null && desktopController.areDesktopTasksVisible();
+    public void registerDesktopVisibilityListener(DesktopVisibilityListener listener) {
+        mDesktopVisibilityController.registerDesktopVisibilityListener(listener);
+    }
+
+    @Override
+    public void unregisterDesktopVisibilityListener(DesktopVisibilityListener listener) {
+        mDesktopVisibilityController.unregisterDesktopVisibilityListener(listener);
+    }
+
+    @Override
+    public boolean isInDesktopMode(int displayId) {
+        return mDesktopVisibilityController.isInDesktopMode(displayId);
     }
 
     @Override
@@ -82,6 +91,25 @@
     }
 
     @Override
+    public boolean showDesktopTaskbarForFreeformDisplay(Context displayInfoContext) {
+        if (!DesktopModeStatus.canEnterDesktopMode(displayInfoContext)) {
+            return false;
+        }
+
+        if (!DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(displayInfoContext)) {
+            return false;
+        }
+
+        if (!Flags.enableDesktopTaskbarOnFreeformDisplays()) {
+            return false;
+        }
+
+        final boolean isFreeformDisplay = displayInfoContext.getResources().getConfiguration()
+                .windowConfiguration.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+        return isFreeformDisplay;
+    }
+
+    @Override
     public boolean isHomeVisible(Context context) {
         return SystemUiProxy.INSTANCE.get(context).getHomeVisibilityState().isHomeVisible();
     }
diff --git a/quickstep/src/com/android/quickstep/util/TISBindHelper.java b/quickstep/src/com/android/quickstep/util/TISBindHelper.java
index b573604..027dc08 100644
--- a/quickstep/src/com/android/quickstep/util/TISBindHelper.java
+++ b/quickstep/src/com/android/quickstep/util/TISBindHelper.java
@@ -21,11 +21,11 @@
 import android.content.ServiceConnection;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.util.Log;
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.taskbar.TaskbarManager;
 import com.android.quickstep.OverviewCommandHelper;
 import com.android.quickstep.TouchInteractionService;
@@ -46,7 +46,7 @@
     // Max backoff caps at 5 mins
     private static final long MAX_BACKOFF_MILLIS = 10 * 60 * 1000;
 
-    private final Handler mHandler = new Handler();
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final Runnable mConnectionRunnable = this::internalBindToTIS;
     private final Context mContext;
     private final Consumer<TISBinder> mConnectionCallback;
@@ -109,11 +109,6 @@
         return mBinder == null ? null : mBinder.getTaskbarManager();
     }
 
-    @Nullable
-    public DesktopVisibilityController getDesktopVisibilityController() {
-        return mBinder == null ? null : mBinder.getDesktopVisibilityController();
-    }
-
     /**
      * Sets flag whether a predictive back-to-home animation is in progress
      */
diff --git a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java
deleted file mode 100644
index 498078b..0000000
--- a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.java
+++ /dev/null
@@ -1,136 +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.quickstep.util;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import androidx.annotation.IntDef;
-
-import com.android.launcher3.util.IntArray;
-
-import java.lang.annotation.Retention;
-import java.util.List;
-
-/**
- * Helper class for navigating RecentsView grid tasks via arrow keys and tab.
- */
-public class TaskGridNavHelper {
-    public static final int CLEAR_ALL_PLACEHOLDER_ID = -1;
-
-    public static final int DIRECTION_UP = 0;
-    public static final int DIRECTION_DOWN = 1;
-    public static final int DIRECTION_LEFT = 2;
-    public static final int DIRECTION_RIGHT = 3;
-    public static final int DIRECTION_TAB = 4;
-
-    @Retention(SOURCE)
-    @IntDef({DIRECTION_UP, DIRECTION_DOWN, DIRECTION_LEFT, DIRECTION_RIGHT, DIRECTION_TAB})
-    public @interface TASK_NAV_DIRECTION {}
-
-    private final IntArray mOriginalTopRowIds;
-    private IntArray mTopRowIds;
-    private IntArray mBottomRowIds;
-
-    public TaskGridNavHelper(IntArray topIds, IntArray bottomIds,
-            List<Integer> largeTileIds) {
-        mOriginalTopRowIds = topIds.clone();
-        generateTaskViewIdGrid(topIds, bottomIds, largeTileIds);
-    }
-
-    private void generateTaskViewIdGrid(IntArray topRowIdArray, IntArray bottomRowIdArray,
-            List<Integer> largeTileIds) {
-
-        int maxSize = Math.max(topRowIdArray.size(), bottomRowIdArray.size())
-                + largeTileIds.size();
-        int minSize = Math.min(topRowIdArray.size(), bottomRowIdArray.size())
-                + largeTileIds.size();
-
-        // Add Large tile task views first at the beginning
-        for (int i = 0; i < largeTileIds.size(); i++) {
-            topRowIdArray.add(i, largeTileIds.get(i));
-            bottomRowIdArray.add(i, largeTileIds.get(i));
-        }
-
-        // Fill in the shorter array with the ids from the longer one.
-        for (int i = minSize; i < maxSize; i++) {
-            if (i >= topRowIdArray.size()) {
-                topRowIdArray.add(bottomRowIdArray.get(i));
-            } else {
-                bottomRowIdArray.add(topRowIdArray.get(i));
-            }
-        }
-
-        // Add the clear all button to the end of both arrays
-        topRowIdArray.add(CLEAR_ALL_PLACEHOLDER_ID);
-        bottomRowIdArray.add(CLEAR_ALL_PLACEHOLDER_ID);
-
-        mTopRowIds = topRowIdArray;
-        mBottomRowIds = bottomRowIdArray;
-    }
-
-    /**
-     * Returns the id of the next page in the grid or -1 for the clear all button.
-     */
-    public int getNextGridPage(int currentPageTaskViewId, int delta,
-            @TASK_NAV_DIRECTION int direction, boolean cycle) {
-        boolean inTop = mTopRowIds.contains(currentPageTaskViewId);
-        int index = inTop ? mTopRowIds.indexOf(currentPageTaskViewId)
-                : mBottomRowIds.indexOf(currentPageTaskViewId);
-        int maxSize = Math.max(mTopRowIds.size(), mBottomRowIds.size());
-        int nextIndex = index + delta;
-
-        switch (direction) {
-            case DIRECTION_UP:
-            case DIRECTION_DOWN: {
-                return inTop ? mBottomRowIds.get(index) : mTopRowIds.get(index);
-            }
-            case DIRECTION_LEFT: {
-                int boundedIndex = cycle ? nextIndex % maxSize : Math.min(nextIndex, maxSize - 1);
-                return inTop ? mTopRowIds.get(boundedIndex)
-                        : mBottomRowIds.get(boundedIndex);
-            }
-            case DIRECTION_RIGHT: {
-                int boundedIndex =
-                        cycle ? (nextIndex < 0 ? maxSize - 1 : nextIndex) : Math.max(
-                                nextIndex, 0);
-                boolean inOriginalTop = mOriginalTopRowIds.contains(currentPageTaskViewId);
-                return inOriginalTop ? mTopRowIds.get(boundedIndex)
-                        : mBottomRowIds.get(boundedIndex);
-            }
-            case DIRECTION_TAB: {
-                int boundedIndex =
-                        cycle ? nextIndex < 0 ? maxSize - 1 : nextIndex % maxSize : Math.min(
-                                nextIndex, maxSize - 1);
-                if (delta >= 0) {
-                    return inTop && mTopRowIds.get(index) != mBottomRowIds.get(index)
-                            ? mBottomRowIds.get(index)
-                            : mTopRowIds.get(boundedIndex);
-                } else {
-                    if (mTopRowIds.contains(currentPageTaskViewId)) {
-                        return mBottomRowIds.get(boundedIndex);
-                    } else {
-                        // Go up to top if there is task above
-                        return mTopRowIds.get(index) != mBottomRowIds.get(index)
-                                ? mTopRowIds.get(index)
-                                : mBottomRowIds.get(boundedIndex);
-                    }
-                }
-            }
-            default:
-                return currentPageTaskViewId;
-        }
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.kt b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.kt
new file mode 100644
index 0000000..0e78801
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/TaskGridNavHelper.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util
+
+import com.android.launcher3.util.IntArray
+import kotlin.math.abs
+import kotlin.math.max
+
+/** Helper class for navigating RecentsView grid tasks via arrow keys and tab. */
+class TaskGridNavHelper(
+    private val topIds: IntArray,
+    bottomIds: IntArray,
+    largeTileIds: List<Int>,
+    hasAddDesktopButton: Boolean,
+) {
+    private val topRowIds = mutableListOf<Int>()
+    private val bottomRowIds = mutableListOf<Int>()
+
+    init {
+        // Add AddDesktopButton and lage tiles to both rows.
+        if (hasAddDesktopButton) {
+            topRowIds += ADD_DESK_PLACEHOLDER_ID
+            bottomRowIds += ADD_DESK_PLACEHOLDER_ID
+        }
+        topRowIds += largeTileIds
+        bottomRowIds += largeTileIds
+
+        // Add row ids to their respective rows.
+        topRowIds += topIds
+        bottomRowIds += bottomIds
+
+        // Fill in the shorter array with the ids from the longer one.
+        topRowIds += bottomRowIds.takeLast(max(bottomRowIds.size - topRowIds.size, 0))
+        bottomRowIds += topRowIds.takeLast(max(topRowIds.size - bottomRowIds.size, 0))
+
+        // Add the clear all button to the end of both arrays.
+        topRowIds += CLEAR_ALL_PLACEHOLDER_ID
+        bottomRowIds += CLEAR_ALL_PLACEHOLDER_ID
+    }
+
+    /** Returns the id of the next page in the grid or -1 for the clear all button. */
+    fun getNextGridPage(
+        currentPageTaskViewId: Int,
+        delta: Int,
+        direction: TaskNavDirection,
+        cycle: Boolean,
+    ): Int {
+        val inTop = topRowIds.contains(currentPageTaskViewId)
+        val index =
+            if (inTop) topRowIds.indexOf(currentPageTaskViewId)
+            else bottomRowIds.indexOf(currentPageTaskViewId)
+        val maxSize = max(topRowIds.size, bottomRowIds.size)
+        val nextIndex = index + delta
+
+        return when (direction) {
+            TaskNavDirection.UP,
+            TaskNavDirection.DOWN -> {
+                if (inTop) bottomRowIds[index] else topRowIds[index]
+            }
+            TaskNavDirection.LEFT -> {
+                val boundedIndex =
+                    if (cycle) nextIndex % maxSize else nextIndex.coerceAtMost(maxSize - 1)
+                if (inTop) topRowIds[boundedIndex] else bottomRowIds[boundedIndex]
+            }
+            TaskNavDirection.RIGHT -> {
+                val boundedIndex =
+                    if (cycle) (if (nextIndex < 0) maxSize - 1 else nextIndex)
+                    else nextIndex.coerceAtLeast(0)
+                val inOriginalTop = topIds.contains(currentPageTaskViewId)
+                if (inOriginalTop) topRowIds[boundedIndex] else bottomRowIds[boundedIndex]
+            }
+            TaskNavDirection.TAB -> {
+                val boundedIndex =
+                    if (cycle) (if (nextIndex < 0) maxSize - 1 else nextIndex % maxSize)
+                    else nextIndex.coerceAtMost(maxSize - 1)
+                if (delta >= 0) {
+                    if (inTop && topRowIds[index] != bottomRowIds[index]) bottomRowIds[index]
+                    else topRowIds[boundedIndex]
+                } else {
+                    if (topRowIds.contains(currentPageTaskViewId)) {
+                        if (boundedIndex < 0) {
+                            // If no cycling, always return the first task.
+                            topRowIds[0]
+                        } else {
+                            bottomRowIds[boundedIndex]
+                        }
+                    } else {
+                        // Go up to top if there is task above
+                        if (topRowIds[index] != bottomRowIds[index]) topRowIds[index]
+                        else bottomRowIds[boundedIndex]
+                    }
+                }
+            }
+            else -> currentPageTaskViewId
+        }
+    }
+
+    /**
+     * Returns a sequence of pairs of (TaskView ID, offset) in the grid, ordered according to tab
+     * navigation, starting from the initial TaskView ID, towards the start or end of the grid.
+     *
+     * <p>A positive delta moves forward in the tab order towards the end of the grid, while a
+     * negative value moves backward towards the beginning. The offset is the distance between
+     * columns the tasks are in.
+     */
+    fun gridTaskViewIdOffsetPairInTabOrderSequence(
+        initialTaskViewId: Int,
+        towardsStart: Boolean,
+    ): Sequence<Pair<Int, Int>> = sequence {
+        val draggedTaskViewColumn = getColumn(initialTaskViewId)
+        var nextTaskViewId: Int = initialTaskViewId
+        var previousTaskViewId: Int = Int.MIN_VALUE
+        while (nextTaskViewId != previousTaskViewId && nextTaskViewId >= 0) {
+            previousTaskViewId = nextTaskViewId
+            nextTaskViewId =
+                getNextGridPage(
+                    nextTaskViewId,
+                    if (towardsStart) -1 else 1,
+                    TaskNavDirection.TAB,
+                    cycle = false,
+                )
+            if (nextTaskViewId >= 0 && nextTaskViewId != previousTaskViewId) {
+                val columnOffset = abs(getColumn(nextTaskViewId) - draggedTaskViewColumn)
+                yield(Pair(nextTaskViewId, columnOffset))
+            }
+        }
+    }
+
+    /** Returns the column of a task's id in the grid. */
+    private fun getColumn(taskViewId: Int): Int =
+        if (topRowIds.contains(taskViewId)) topRowIds.indexOf(taskViewId)
+        else bottomRowIds.indexOf(taskViewId)
+
+    enum class TaskNavDirection {
+        UP,
+        DOWN,
+        LEFT,
+        RIGHT,
+        TAB,
+    }
+
+    companion object {
+        const val CLEAR_ALL_PLACEHOLDER_ID: Int = -1
+        const val ADD_DESK_PLACEHOLDER_ID: Int = -2
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java
index 69137cc..43ef39c 100644
--- a/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java
+++ b/quickstep/src/com/android/quickstep/util/TaskKeyByLastActiveTimeCache.java
@@ -17,6 +17,7 @@
 
 import android.util.Log;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.systemui.shared.recents.model.Task;
@@ -94,6 +95,7 @@
      * Gets the entry if it is still valid
      */
     @Override
+    @Nullable
     public synchronized V getAndInvalidateIfModified(Task.TaskKey key) {
         Entry<V> entry = mMap.get(key.id);
         if (entry != null && entry.mKey.windowingMode == key.windowingMode
diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyCache.java
index 8ee78ab..9df0993 100644
--- a/quickstep/src/com/android/quickstep/util/TaskKeyCache.java
+++ b/quickstep/src/com/android/quickstep/util/TaskKeyCache.java
@@ -15,6 +15,8 @@
  */
 package com.android.quickstep.util;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.shared.recents.model.Task;
 
 import java.util.function.Predicate;
@@ -44,6 +46,7 @@
     /**
      * Gets the entry if it is still valid.
      */
+    @Nullable
     V getAndInvalidateIfModified(Task.TaskKey key);
 
     /**
diff --git a/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java b/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java
index 89f5d41..9fe8cc9 100644
--- a/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java
+++ b/quickstep/src/com/android/quickstep/util/TaskKeyLruCache.java
@@ -17,6 +17,8 @@
 
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 
 import java.util.LinkedHashMap;
@@ -59,6 +61,7 @@
     /**
      * Gets the entry if it is still valid
      */
+    @Nullable
     public synchronized V getAndInvalidateIfModified(TaskKey key) {
         Entry<V> entry = mMap.get(key.id);
 
diff --git a/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java b/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java
index 91e8376..6e2d469 100644
--- a/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java
+++ b/quickstep/src/com/android/quickstep/util/TaskRestartedDuringLaunchListener.java
@@ -16,16 +16,11 @@
 
 package com.android.quickstep.util;
 
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-
-import android.app.Activity;
 import android.app.ActivityManager;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
 
-import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
-import com.android.quickstep.RecentsModel;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
 
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 706cfe4..661fe89 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -41,7 +41,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.app.animation.Interpolators;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
@@ -98,11 +97,11 @@
     private final FullscreenDrawParams mCurrentFullscreenParams;
     public final AnimatedFloat taskPrimaryTranslation = new AnimatedFloat();
     public final AnimatedFloat taskSecondaryTranslation = new AnimatedFloat();
+    public final AnimatedFloat taskGridTranslationX = new AnimatedFloat();
+    public final AnimatedFloat taskGridTranslationY = new AnimatedFloat();
 
     // Carousel properties
     public final AnimatedFloat carouselScale = new AnimatedFloat();
-    public final AnimatedFloat carouselPrimaryTranslation = new AnimatedFloat();
-    public final AnimatedFloat carouselSecondaryTranslation = new AnimatedFloat();
 
     // RecentsView properties
     public final AnimatedFloat recentsViewScale = new AnimatedFloat();
@@ -118,11 +117,14 @@
     private Boolean mDrawsBelowRecents = null;
     private boolean mIsGridTask;
     private final boolean mIsDesktopTask;
-    private boolean mScaleToCarouselTaskSize = false;
+    private boolean mIsAnimatingToCarousel = false;
     private int mTaskRectTranslationX;
     private int mTaskRectTranslationY;
     private int mDesktopTaskIndex = 0;
 
+    @Nullable
+    private Matrix mTaskRectTransform = null;
+
     public TaskViewSimulator(Context context, BaseContainerInterface sizeStrategy,
             boolean isDesktop, int desktopTaskIndex) {
         mContext = context;
@@ -149,6 +151,9 @@
         mDp = dp;
         mLayoutValid = false;
         mOrientationState.setDeviceProfile(dp);
+        if (enableGridOnlyOverview()) {
+            mIsGridTask = dp.isTablet && !mIsDesktopTask;
+        }
         calculateTaskSize();
     }
 
@@ -160,14 +165,16 @@
         if (mIsGridTask) {
             mSizeStrategy.calculateGridTaskSize(mContext, mDp, mFullTaskSize,
                     mOrientationState.getOrientationHandler());
+            if (enableGridOnlyOverview()) {
+                mSizeStrategy.calculateTaskSize(mContext, mDp, mCarouselTaskSize,
+                        mOrientationState.getOrientationHandler());
+            }
         } else {
             mSizeStrategy.calculateTaskSize(mContext, mDp, mFullTaskSize,
                     mOrientationState.getOrientationHandler());
-        }
-
-        if (enableGridOnlyOverview()) {
-            mSizeStrategy.calculateCarouselTaskSize(mContext, mDp, mCarouselTaskSize,
-                    mOrientationState.getOrientationHandler());
+            if (enableGridOnlyOverview()) {
+                mCarouselTaskSize.set(mFullTaskSize);
+            }
         }
 
         if (mSplitBounds != null) {
@@ -222,12 +229,7 @@
         }
         // Copy mFullTaskSize instead of updating it directly so it could be reused next time
         // without recalculating
-        Rect scaleRect = new Rect();
-        if (mScaleToCarouselTaskSize) {
-            scaleRect.set(mCarouselTaskSize);
-        } else {
-            scaleRect.set(mFullTaskSize);
-        }
+        Rect scaleRect = new Rect(mIsAnimatingToCarousel ? mCarouselTaskSize : mFullTaskSize);
         scaleRect.offset(mTaskRectTranslationX, mTaskRectTranslationY);
         float scale = mOrientationState.getFullScreenScaleAndPivot(scaleRect, mDp, mPivot);
         if (mPivotOverride != null) {
@@ -313,66 +315,16 @@
     }
 
     /**
-     * Adds animation for all the components corresponding to transition from an app to overview.
+     * Adds animation for all the components corresponding to transition from an app to carousel.
      */
-    public void addAppToOverviewAnim(PendingAnimation pa, Interpolator interpolator) {
+    public void addAppToCarouselAnim(PendingAnimation pa, Interpolator interpolator) {
         pa.addFloat(fullScreenProgress, AnimatedFloat.VALUE, 1, 0, interpolator);
-        float fullScreenScale;
         if (enableGridOnlyOverview() && mDp.isTablet && mDp.isGestureMode) {
-            // Move pivot to top right edge of the screen, to avoid task scaling down in opposite
-            // direction of app window movement, otherwise the animation will wiggle left and right.
-            // Also translate the app window to top right edge of the screen to simplify
-            // calculations.
-            taskPrimaryTranslation.value = mIsRecentsRtl
-                    ? mDp.widthPx - mFullTaskSize.right
-                    : -mFullTaskSize.left;
-            taskSecondaryTranslation.value = -mFullTaskSize.top;
-            mPivotOverride = new PointF(mIsRecentsRtl ? mDp.widthPx : 0, 0);
-
-            // Scale down to the carousel and use the carousel Rect to calculate fullScreenScale.
-            mScaleToCarouselTaskSize = true;
+            mIsAnimatingToCarousel = true;
             carouselScale.value = mCarouselTaskSize.width() / (float) mFullTaskSize.width();
-            fullScreenScale = getFullScreenScale();
-
-            float carouselPrimaryTranslationTarget = mIsRecentsRtl
-                    ? mCarouselTaskSize.right - mDp.widthPx
-                    : mCarouselTaskSize.left;
-            float carouselSecondaryTranslationTarget = mCarouselTaskSize.top;
-
-            // Expected carousel position's center is in the middle, and invariant of
-            // recentsViewScale.
-            float exceptedCarouselCenterX = mCarouselTaskSize.centerX();
-            // Animating carousel translations linearly will result in a curved path, therefore
-            // we'll need to calculate the expected translation at each recentsView scale. Luckily
-            // primary and secondary follow the same translation, and primary is used here due to
-            // it being simpler.
-            Interpolator carouselTranslationInterpolator = t -> {
-                // recentsViewScale is calculated rather than using recentsViewScale.value, so that
-                // this interpolator works independently even if recentsViewScale don't animate.
-                float recentsViewScale =
-                        Utilities.mapToRange(t, 0, 1, fullScreenScale, 1, Interpolators.LINEAR);
-                // Without the translation, the app window will animate from fullscreen into top
-                // right corner.
-                float expectedTaskCenterX = mIsRecentsRtl
-                        ? mDp.widthPx - mCarouselTaskSize.width() * recentsViewScale / 2f
-                        : mCarouselTaskSize.width() * recentsViewScale / 2f;
-                // Calculate the expected translation, then work back the animatedFraction that
-                // results in this value.
-                float carouselPrimaryTranslation =
-                        (exceptedCarouselCenterX - expectedTaskCenterX) / recentsViewScale;
-                return carouselPrimaryTranslation / carouselPrimaryTranslationTarget;
-            };
-
-            // Use addAnimatedFloat so this animation can later be canceled and animate to a
-            // different value in RecentsView.onPrepareGestureEndAnimation.
-            pa.addAnimatedFloat(carouselPrimaryTranslation, 0, carouselPrimaryTranslationTarget,
-                    carouselTranslationInterpolator);
-            pa.addAnimatedFloat(carouselSecondaryTranslation, 0, carouselSecondaryTranslationTarget,
-                    carouselTranslationInterpolator);
-        } else {
-            fullScreenScale = getFullScreenScale();
         }
-        pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, fullScreenScale, 1, interpolator);
+        pa.addFloat(recentsViewScale, AnimatedFloat.VALUE, getFullScreenScale(), 1,
+                interpolator);
     }
 
     /**
@@ -417,6 +369,38 @@
     }
 
     /**
+     * Sets a matrix used to transform the position of tasks. If set, this matrix is applied to
+     * the task rect after the task has been scaled and positioned inside the fulltask, but
+     * before scaling and translation of the whole recents view is performed.
+     */
+    public void setTaskRectTransform(@Nullable Matrix taskRectTransform) {
+        mTaskRectTransform = taskRectTransform;
+    }
+
+    /**
+     * Calculates the crop rect for desktop tasks given the current matrix.
+     */
+    private void calculateDesktopTaskCropRect() {
+        // The approach here is to map a rect that represents the untransformed thumbnail position
+        // using the current matrix. This will give us a rect that can be intersected with
+        // [mFullTaskSize]. Using the intersection, we then compute how much of the task window that
+        // needs to be cropped (which will be nothing if the window is entirely within the desktop).
+        mTempRectF.set(0, 0, mThumbnailPosition.width(), mThumbnailPosition.height());
+        mMatrix.mapRect(mTempRectF);
+
+        float offsetX = mTempRectF.left;
+        float offsetY = mTempRectF.top;
+        float scale = mThumbnailPosition.width() / mTempRectF.width();
+
+        if (mTempRectF.intersect(mFullTaskSize.left, mFullTaskSize.top, mFullTaskSize.right,
+                mFullTaskSize.bottom)) {
+            mTempRectF.offset(-offsetX, -offsetY);
+            mTempRectF.scale(scale);
+            mTempRectF.round(mTmpCropRect);
+        }
+    }
+
+    /**
      * Applies the rotation on the matrix to so that it maps from launcher coordinate space to
      * window coordinate space.
      */
@@ -477,18 +461,29 @@
 
         mMatrix.set(mPositionHelper.getMatrix());
 
-        // Apply TaskView matrix: taskRect, translate
+        // Apply TaskView matrix: taskRect, optional transform, translate
         mMatrix.postTranslate(mTaskRect.left, mTaskRect.top);
+        if (mTaskRectTransform != null) {
+            mMatrix.postConcat(mTaskRectTransform);
+
+            // Calculate cropping for desktop tasks. The order is important since it uses the
+            // current matrix. Therefore we calculate it here, after applying the task rect
+            // transform, but before applying scaling/translation that affects the whole
+            // recentsview.
+            if (mIsDesktopTask) {
+                calculateDesktopTaskCropRect();
+            }
+        }
+
         mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE,
                 taskPrimaryTranslation.value);
         mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
                 taskSecondaryTranslation.value);
+        mMatrix.postTranslate(taskGridTranslationX.value, taskGridTranslationY.value);
 
-        mMatrix.postScale(carouselScale.value, carouselScale.value, mPivot.x, mPivot.y);
-        mOrientationState.getOrientationHandler().setPrimary(mMatrix, MATRIX_POST_TRANSLATE,
-                carouselPrimaryTranslation.value);
-        mOrientationState.getOrientationHandler().setSecondary(mMatrix, MATRIX_POST_TRANSLATE,
-                carouselSecondaryTranslation.value);
+        mMatrix.postScale(carouselScale.value, carouselScale.value,
+                mIsRecentsRtl ? mCarouselTaskSize.right : mCarouselTaskSize.left,
+                mCarouselTaskSize.top);
 
         mOrientationState.getOrientationHandler().setPrimary(
                 mMatrix, MATRIX_POST_TRANSLATE, recentsViewScroll.value);
@@ -524,8 +519,8 @@
                 + " taskRect: " + mTaskRect
                 + " taskPrimaryT: " + taskPrimaryTranslation.value
                 + " taskSecondaryT: " + taskSecondaryTranslation.value
-                + " carouselPrimaryT: " + carouselPrimaryTranslation.value
-                + " carouselSecondaryT: " + carouselSecondaryTranslation.value
+                + " taskGridTranslationX: " + taskGridTranslationX.value
+                + " taskGridTranslationY: " + taskGridTranslationY.value
                 + " recentsPrimaryT: " + recentsViewPrimaryTranslation.value
                 + " recentsSecondaryT: " + recentsViewSecondaryTranslation.value
                 + " recentsScroll: " + recentsViewScroll.value
diff --git a/quickstep/src/com/android/quickstep/util/TransformParams.java b/quickstep/src/com/android/quickstep/util/TransformParams.java
index 401eccc..716803a 100644
--- a/quickstep/src/com/android/quickstep/util/TransformParams.java
+++ b/quickstep/src/com/android/quickstep/util/TransformParams.java
@@ -19,9 +19,16 @@
 
 import android.util.FloatProperty;
 import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+
+import androidx.annotation.VisibleForTesting;
 
 import com.android.quickstep.RemoteAnimationTargets;
 import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
+import com.android.window.flags.Flags;
+
+import java.util.function.Supplier;
 
 public class TransformParams {
 
@@ -56,15 +63,24 @@
     private float mTargetAlpha;
     private float mCornerRadius;
     private RemoteAnimationTargets mTargetSet;
+    private TransitionInfo mTransitionInfo;
+    private boolean mCornerRadiusIsOverridden;
     private SurfaceTransactionApplier mSyncTransactionApplier;
+    private Supplier<SurfaceTransaction> mSurfaceTransactionSupplier;
 
     private BuilderProxy mHomeBuilderProxy = BuilderProxy.ALWAYS_VISIBLE;
     private BuilderProxy mBaseBuilderProxy = BuilderProxy.ALWAYS_VISIBLE;
 
     public TransformParams() {
+        this(SurfaceTransaction::new);
+    }
+
+    @VisibleForTesting
+    public TransformParams(Supplier<SurfaceTransaction> surfaceTransactionSupplier) {
         mProgress = 0;
         mTargetAlpha = 1;
         mCornerRadius = -1;
+        mSurfaceTransactionSupplier = surfaceTransactionSupplier;
     }
 
     /**
@@ -107,6 +123,15 @@
     }
 
     /**
+     * Provides the {@code TransitionInfo} of the transition that this transformation stems from.
+     */
+    public TransformParams setTransitionInfo(TransitionInfo transitionInfo) {
+        mTransitionInfo = transitionInfo;
+        mCornerRadiusIsOverridden = false;
+        return this;
+    }
+
+    /**
      * Sets the SyncRtSurfaceTransactionApplierCompat that will apply the SurfaceParams that
      * are computed based on these TransformParams.
      */
@@ -136,7 +161,7 @@
     /** Builds the SurfaceTransaction from the given BuilderProxy params. */
     public SurfaceTransaction createSurfaceParams(BuilderProxy proxy) {
         RemoteAnimationTargets targets = mTargetSet;
-        SurfaceTransaction transaction = new SurfaceTransaction();
+        SurfaceTransaction transaction = mSurfaceTransactionSupplier.get();
         if (targets == null) {
             return transaction;
         }
@@ -152,7 +177,15 @@
                 builder.setAlpha(getTargetAlpha());
             }
             targetProxy.onBuildTargetParams(builder, app, this);
+            // Override the corner radius for {@code app} with the leash used by Shell, so that it
+            // doesn't interfere with the window clip and corner radius applied here.
+            // Only override the corner radius once - so that we don't accidentally override at the
+            // end of transition after WM Shell has reset the corner radius of the task.
+            if (!mCornerRadiusIsOverridden) {
+                overrideFreeformChangeLeashCornerRadiusToZero(app, transaction.getTransaction());
+            }
         }
+        mCornerRadiusIsOverridden = true;
 
         // always put wallpaper layer to bottom.
         final int wallpaperLength = targets.wallpapers != null ? targets.wallpapers.length : 0;
@@ -163,7 +196,33 @@
         return transaction;
     }
 
-    // Pubic getters so outside packages can read the values.
+    private void overrideFreeformChangeLeashCornerRadiusToZero(
+            RemoteAnimationTarget app, SurfaceControl.Transaction transaction) {
+        if (!Flags.enableDesktopRecentsTransitionsCornersBugfix()) {
+            return;
+        }
+        if (app.taskInfo == null || !app.taskInfo.isFreeform()) {
+            return;
+        }
+
+        SurfaceControl changeLeash = getChangeLeashForApp(app);
+        if (changeLeash != null) {
+            transaction.setCornerRadius(changeLeash, 0);
+        }
+    }
+
+    private SurfaceControl getChangeLeashForApp(RemoteAnimationTarget app) {
+        if (mTransitionInfo == null) return null;
+        for (TransitionInfo.Change change : mTransitionInfo.getChanges()) {
+            if (change.getTaskInfo() == null) continue;
+            if (change.getTaskInfo().taskId == app.taskId) {
+                return change.getLeash();
+            }
+        }
+        return null;
+    }
+
+    // Public getters so outside packages can read the values.
 
     public float getProgress() {
         return mProgress;
diff --git a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
index f973dd0..37359a1 100644
--- a/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
+++ b/quickstep/src/com/android/quickstep/views/AddDesktopButton.kt
@@ -17,8 +17,19 @@
 package com.android.quickstep.views
 
 import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Rect
 import android.util.AttributeSet
+import android.util.FloatProperty
 import android.widget.ImageButton
+import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X
+import com.android.launcher3.R
+import com.android.launcher3.util.KFloatProperty
+import com.android.launcher3.util.MultiPropertyDelegate
+import com.android.launcher3.util.MultiPropertyFactory
+import com.android.launcher3.util.MultiValueAlpha
+import com.android.quickstep.util.BorderAnimator
+import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator
 
 /**
  * Button for supporting multiple desktop sessions. The button will be next to the first TaskView
@@ -26,5 +37,78 @@
  */
 class AddDesktopButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
     ImageButton(context, attrs) {
-    // TODO(b/382057498): add this button the overview.
+
+    private val addDeskButtonAlpha = MultiValueAlpha(this, Alpha.entries.size)
+    var contentAlpha by MultiPropertyDelegate(addDeskButtonAlpha, Alpha.CONTENT)
+    var visibilityAlpha by MultiPropertyDelegate(addDeskButtonAlpha, Alpha.VISIBILITY)
+
+    private val multiTranslationX =
+        MultiPropertyFactory(this, VIEW_TRANSLATE_X, TranslationX.entries.size) { a: Float, b: Float
+            ->
+            a + b
+        }
+    var gridTranslationX by MultiPropertyDelegate(multiTranslationX, TranslationX.GRID)
+    var offsetTranslationX by MultiPropertyDelegate(multiTranslationX, TranslationX.OFFSET)
+
+    private val focusBorderAnimator: BorderAnimator =
+        createSimpleBorderAnimator(
+            context.resources.getDimensionPixelSize(R.dimen.add_desktop_button_size),
+            context.resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width),
+            this::getBorderBounds,
+            this,
+            context
+                .obtainStyledAttributes(attrs, R.styleable.AddDesktopButton)
+                .getColor(
+                    R.styleable.AddDesktopButton_focusBorderColor,
+                    BorderAnimator.DEFAULT_BORDER_COLOR,
+                ),
+        )
+
+    var borderEnabled = false
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            focusBorderAnimator.setBorderVisibility(visible = field && isFocused, animated = true)
+        }
+
+    public override fun onFocusChanged(
+        gainFocus: Boolean,
+        direction: Int,
+        previouslyFocusedRect: Rect?,
+    ) {
+        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
+        if (borderEnabled) {
+            focusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true)
+        }
+    }
+
+    private fun getBorderBounds(bounds: Rect) {
+        bounds.set(0, 0, width, height)
+        val outlinePadding =
+            context.resources.getDimensionPixelSize(R.dimen.add_desktop_button_outline_padding)
+        bounds.inset(-outlinePadding, -outlinePadding)
+    }
+
+    override fun draw(canvas: Canvas) {
+        focusBorderAnimator.drawBorder(canvas)
+        super.draw(canvas)
+    }
+
+    companion object {
+        private enum class Alpha {
+            CONTENT,
+            VISIBILITY,
+        }
+
+        private enum class TranslationX {
+            GRID,
+            OFFSET,
+        }
+
+        @JvmField
+        val VISIBILITY_ALPHA: FloatProperty<AddDesktopButton> =
+            KFloatProperty(AddDesktopButton::visibilityAlpha)
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.java b/quickstep/src/com/android/quickstep/views/ClearAllButton.java
deleted file mode 100644
index c3efc3c..0000000
--- a/quickstep/src/com/android/quickstep/views/ClearAllButton.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.views;
-
-import static com.android.launcher3.Flags.enableGridOnlyOverview;
-import static com.android.quickstep.util.BorderAnimator.DEFAULT_BORDER_COLOR;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.util.FloatProperty;
-import android.widget.Button;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.Flags;
-import com.android.launcher3.R;
-import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
-import com.android.quickstep.util.BorderAnimator;
-
-import kotlin.Unit;
-
-public class ClearAllButton extends Button {
-
-    public static final FloatProperty<ClearAllButton> VISIBILITY_ALPHA =
-            new FloatProperty<ClearAllButton>("visibilityAlpha") {
-                @Override
-                public Float get(ClearAllButton view) {
-                    return view.mVisibilityAlpha;
-                }
-
-                @Override
-                public void setValue(ClearAllButton view, float v) {
-                    view.setVisibilityAlpha(v);
-                }
-            };
-
-    public static final FloatProperty<ClearAllButton> DISMISS_ALPHA =
-            new FloatProperty<ClearAllButton>("dismissAlpha") {
-                @Override
-                public Float get(ClearAllButton view) {
-                    return view.mDismissAlpha;
-                }
-
-                @Override
-                public void setValue(ClearAllButton view, float v) {
-                    view.setDismissAlpha(v);
-                }
-            };
-
-    private final RecentsViewContainer mContainer;
-    private float mScrollAlpha = 1;
-    private float mContentAlpha = 1;
-    private float mVisibilityAlpha = 1;
-    private float mDismissAlpha = 1;
-    private float mFullscreenProgress = 1;
-    private float mGridProgress = 1;
-
-    private boolean mIsRtl;
-    private float mNormalTranslationPrimary;
-    private float mFullscreenTranslationPrimary;
-    private float mGridTranslationPrimary;
-    private float mGridScrollOffset;
-    private float mScrollOffsetPrimary;
-
-    private int mSidePadding;
-    private int mOutlinePadding;
-    private boolean mBorderEnabled;
-    @Nullable
-    private final BorderAnimator mFocusBorderAnimator;
-
-    public ClearAllButton(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
-        mContainer = RecentsViewContainer.containerFromContext(context);
-
-        if (Flags.enableFocusOutline()) {
-            TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
-                    R.styleable.ClearAllButton);
-            Resources resources = getResources();
-            mOutlinePadding = resources.getDimensionPixelSize(
-                    R.dimen.recents_clear_all_outline_padding);
-            mFocusBorderAnimator =
-                    BorderAnimator.createSimpleBorderAnimator(
-                            /* borderRadiusPx= */ resources.getDimensionPixelSize(
-                                    R.dimen.recents_clear_all_outline_radius),
-                            /* borderWidthPx= */ context.getResources().getDimensionPixelSize(
-                                    R.dimen.keyboard_quick_switch_border_width),
-                            /* boundsBuilder= */ this::updateBorderBounds,
-                            /* targetView= */ this,
-                            /* borderColor= */ styledAttrs.getColor(
-                                    R.styleable.ClearAllButton_focusBorderColor,
-                                    DEFAULT_BORDER_COLOR));
-            styledAttrs.recycle();
-        } else {
-            mFocusBorderAnimator = null;
-        }
-    }
-
-    private Unit updateBorderBounds(@NonNull Rect bounds) {
-        bounds.set(0, 0, getWidth(), getHeight());
-        // Make the value negative to form a padding between button and outline
-        bounds.inset(-mOutlinePadding, -mOutlinePadding);
-        return Unit.INSTANCE;
-    }
-
-    @Override
-    public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
-        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
-        if (mFocusBorderAnimator != null && mBorderEnabled) {
-            mFocusBorderAnimator.setBorderVisibility(gainFocus, /* animated= */ true);
-        }
-    }
-
-    /**
-     * Enable or disable showing border on focus change
-     */
-    public void setBorderEnabled(boolean enabled) {
-        if (mBorderEnabled == enabled) {
-            return;
-        }
-
-        mBorderEnabled = enabled;
-        if (mFocusBorderAnimator != null) {
-            mFocusBorderAnimator.setBorderVisibility(/* visible= */
-                    enabled && isFocused(), /* animated= */true);
-        }
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        if (mFocusBorderAnimator != null) {
-            mFocusBorderAnimator.drawBorder(canvas);
-        }
-        super.draw(canvas);
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-        RecentsPagedOrientationHandler orientationHandler =
-                getRecentsView().getPagedOrientationHandler();
-        mSidePadding = orientationHandler.getClearAllSidePadding(getRecentsView(), mIsRtl);
-    }
-
-    private RecentsView getRecentsView() {
-        return (RecentsView) getParent();
-    }
-
-    @Override
-    public void onRtlPropertiesChanged(int layoutDirection) {
-        super.onRtlPropertiesChanged(layoutDirection);
-        mIsRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
-    }
-
-    @Override
-    public boolean hasOverlappingRendering() {
-        return false;
-    }
-
-    public float getScrollAlpha() {
-        return mScrollAlpha;
-    }
-
-    public void setContentAlpha(float alpha) {
-        if (mContentAlpha != alpha) {
-            mContentAlpha = alpha;
-            updateAlpha();
-        }
-    }
-
-    public void setVisibilityAlpha(float alpha) {
-        if (mVisibilityAlpha != alpha) {
-            mVisibilityAlpha = alpha;
-            updateAlpha();
-        }
-    }
-
-    public void setDismissAlpha(float alpha) {
-        if (mDismissAlpha != alpha) {
-            mDismissAlpha = alpha;
-            updateAlpha();
-        }
-    }
-
-    public void onRecentsViewScroll(int scroll, boolean gridEnabled) {
-        RecentsView recentsView = getRecentsView();
-        if (recentsView == null) {
-            return;
-        }
-
-        RecentsPagedOrientationHandler orientationHandler =
-                recentsView.getPagedOrientationHandler();
-        float orientationSize = orientationHandler.getPrimaryValue(getWidth(), getHeight());
-        if (orientationSize == 0) {
-            return;
-        }
-
-        int clearAllScroll = recentsView.getClearAllScroll();
-        int adjustedScrollFromEdge = Math.abs(scroll - clearAllScroll);
-        float shift = Math.min(adjustedScrollFromEdge, orientationSize);
-        mNormalTranslationPrimary = mIsRtl ? -shift : shift;
-        if (!gridEnabled) {
-            mNormalTranslationPrimary += mSidePadding;
-        }
-        applyPrimaryTranslation();
-        applySecondaryTranslation();
-        float clearAllSpacing =
-                recentsView.getPageSpacing() + recentsView.getClearAllExtraPageSpacing();
-        clearAllSpacing = mIsRtl ? -clearAllSpacing : clearAllSpacing;
-        mScrollAlpha = Math.max((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing, 0);
-        updateAlpha();
-    }
-
-    private void updateAlpha() {
-        final float alpha = mScrollAlpha * mContentAlpha * mVisibilityAlpha * mDismissAlpha;
-        setAlpha(alpha);
-        setClickable(Math.min(alpha, 1) == 1);
-    }
-
-    public void setFullscreenTranslationPrimary(float fullscreenTranslationPrimary) {
-        mFullscreenTranslationPrimary = fullscreenTranslationPrimary;
-        applyPrimaryTranslation();
-    }
-
-    public void setGridTranslationPrimary(float gridTranslationPrimary) {
-        mGridTranslationPrimary = gridTranslationPrimary;
-        applyPrimaryTranslation();
-    }
-
-    public void setGridScrollOffset(float gridScrollOffset) {
-        mGridScrollOffset = gridScrollOffset;
-    }
-
-    public void setScrollOffsetPrimary(float scrollOffsetPrimary) {
-        mScrollOffsetPrimary = scrollOffsetPrimary;
-    }
-
-    public float getScrollAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
-        float scrollAdjustment = 0;
-        if (fullscreenEnabled) {
-            scrollAdjustment += mFullscreenTranslationPrimary;
-        }
-        if (gridEnabled) {
-            scrollAdjustment += mGridTranslationPrimary + mGridScrollOffset;
-        }
-        scrollAdjustment += mScrollOffsetPrimary;
-        return scrollAdjustment;
-    }
-
-    public float getOffsetAdjustment(boolean fullscreenEnabled, boolean gridEnabled) {
-        return getScrollAdjustment(fullscreenEnabled, gridEnabled);
-    }
-
-    /**
-     * Adjust translation when this TaskView is about to be shown fullscreen.
-     *
-     * @param progress: 0 = no translation; 1 = translate according to TaskVIew translations.
-     */
-    public void setFullscreenProgress(float progress) {
-        mFullscreenProgress = progress;
-        applyPrimaryTranslation();
-    }
-
-    /**
-     * Moves ClearAllButton between carousel and 2 row grid.
-     *
-     * @param gridProgress 0 = carousel; 1 = 2 row grid.
-     */
-    public void setGridProgress(float gridProgress) {
-        mGridProgress = gridProgress;
-        applyPrimaryTranslation();
-    }
-
-    private void applyPrimaryTranslation() {
-        RecentsView recentsView = getRecentsView();
-        if (recentsView == null) {
-            return;
-        }
-
-        RecentsPagedOrientationHandler orientationHandler =
-                recentsView.getPagedOrientationHandler();
-        orientationHandler.getPrimaryViewTranslate().set(this,
-                orientationHandler.getPrimaryValue(0f, getOriginalTranslationY())
-                        + mNormalTranslationPrimary + getFullscreenTrans(
-                        mFullscreenTranslationPrimary) + getGridTrans(mGridTranslationPrimary));
-    }
-
-    private void applySecondaryTranslation() {
-        RecentsView recentsView = getRecentsView();
-        if (recentsView == null) {
-            return;
-        }
-
-        RecentsPagedOrientationHandler orientationHandler =
-                recentsView.getPagedOrientationHandler();
-        orientationHandler.getSecondaryViewTranslate().set(this,
-                orientationHandler.getSecondaryValue(0f, getOriginalTranslationY()));
-    }
-
-    private float getFullscreenTrans(float endTranslation) {
-        return mFullscreenProgress > 0 ? endTranslation : 0;
-    }
-
-    private float getGridTrans(float endTranslation) {
-        return mGridProgress > 0 ? endTranslation : 0;
-    }
-
-    /**
-     * Get the Y translation that is set in the original layout position, before scrolling.
-     */
-    private float getOriginalTranslationY() {
-        DeviceProfile deviceProfile = mContainer.getDeviceProfile();
-        if (deviceProfile.isTablet) {
-            if (enableGridOnlyOverview()) {
-                return (getRecentsView().getLastComputedTaskSize().height()
-                        + deviceProfile.overviewTaskThumbnailTopMarginPx) / 2.0f
-                        + deviceProfile.overviewRowSpacing;
-            } else {
-                return deviceProfile.overviewRowSpacing;
-            }
-        }
-        return deviceProfile.overviewTaskThumbnailTopMarginPx / 2.0f;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/views/ClearAllButton.kt b/quickstep/src/com/android/quickstep/views/ClearAllButton.kt
new file mode 100644
index 0000000..69c85ee
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/ClearAllButton.kt
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.util.FloatProperty
+import android.widget.Button
+import com.android.launcher3.Flags.enableFocusOutline
+import com.android.launcher3.R
+import com.android.launcher3.util.KFloatProperty
+import com.android.launcher3.util.MultiPropertyDelegate
+import com.android.launcher3.util.MultiValueAlpha
+import com.android.quickstep.util.BorderAnimator
+import com.android.quickstep.util.BorderAnimator.Companion.createSimpleBorderAnimator
+import kotlin.math.abs
+import kotlin.math.min
+
+class ClearAllButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+    Button(context, attrs) {
+
+    private val clearAllButtonAlpha =
+        object : MultiValueAlpha(this, Alpha.entries.size) {
+            override fun apply(value: Float) {
+                super.apply(value)
+                isClickable = value >= 1f
+            }
+        }
+    var scrollAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.SCROLL)
+    var contentAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.CONTENT)
+    var visibilityAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.VISIBILITY)
+    var dismissAlpha by MultiPropertyDelegate(clearAllButtonAlpha, Alpha.DISMISS)
+
+    var fullscreenProgress = 1f
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            applyPrimaryTranslation()
+        }
+
+    /**
+     * Moves ClearAllButton between carousel and 2 row grid.
+     *
+     * 0 = carousel; 1 = 2 row grid.
+     */
+    var gridProgress = 1f
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            applyPrimaryTranslation()
+        }
+
+    private var normalTranslationPrimary = 0f
+    var fullscreenTranslationPrimary = 0f
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            applyPrimaryTranslation()
+        }
+
+    var gridTranslationPrimary = 0f
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            applyPrimaryTranslation()
+        }
+
+    /** Used to put the button at the middle in the secondary coordinate. */
+    var taskAlignmentTranslationY = 0f
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            applySecondaryTranslation()
+        }
+
+    var gridScrollOffset = 0f
+    var scrollOffsetPrimary = 0f
+
+    private var sidePadding = 0
+    var borderEnabled = false
+        set(value) {
+            if (field == value) {
+                return
+            }
+            field = value
+            focusBorderAnimator?.setBorderVisibility(visible = field && isFocused, animated = true)
+        }
+
+    private val focusBorderAnimator: BorderAnimator? =
+        if (enableFocusOutline())
+            createSimpleBorderAnimator(
+                context.resources.getDimensionPixelSize(R.dimen.recents_clear_all_outline_radius),
+                context.resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width),
+                this::getBorderBounds,
+                this,
+                context
+                    .obtainStyledAttributes(attrs, R.styleable.ClearAllButton)
+                    .getColor(
+                        R.styleable.ClearAllButton_focusBorderColor,
+                        BorderAnimator.DEFAULT_BORDER_COLOR,
+                    ),
+            )
+        else null
+
+    private fun getBorderBounds(bounds: Rect) {
+        bounds.set(0, 0, width, height)
+        val outlinePadding =
+            context.resources.getDimensionPixelSize(R.dimen.recents_clear_all_outline_padding)
+        // Make the value negative to form a padding between button and outline
+        bounds.inset(-outlinePadding, -outlinePadding)
+    }
+
+    public override fun onFocusChanged(
+        gainFocus: Boolean,
+        direction: Int,
+        previouslyFocusedRect: Rect?,
+    ) {
+        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect)
+        if (borderEnabled) {
+            focusBorderAnimator?.setBorderVisibility(gainFocus, /* animated= */ true)
+        }
+    }
+
+    override fun draw(canvas: Canvas) {
+        focusBorderAnimator?.drawBorder(canvas)
+        super.draw(canvas)
+    }
+
+    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+        super.onLayout(changed, left, top, right, bottom)
+        sidePadding =
+            recentsView?.let { it.pagedOrientationHandler?.getClearAllSidePadding(it, isLayoutRtl) }
+                ?: 0
+    }
+
+    private val recentsView: RecentsView<*, *>?
+        get() = parent as? RecentsView<*, *>?
+
+    override fun hasOverlappingRendering() = false
+
+    fun onRecentsViewScroll(scroll: Int, gridEnabled: Boolean) {
+        val recentsView = recentsView ?: return
+
+        val orientationSize =
+            recentsView.pagedOrientationHandler.getPrimaryValue(width, height).toFloat()
+        if (orientationSize == 0f) {
+            return
+        }
+
+        val clearAllScroll = recentsView.clearAllScroll
+        val adjustedScrollFromEdge = abs((scroll - clearAllScroll)).toFloat()
+        val shift = min(adjustedScrollFromEdge, orientationSize)
+        normalTranslationPrimary = if (isLayoutRtl) -shift else shift
+        if (!gridEnabled) {
+            normalTranslationPrimary += sidePadding.toFloat()
+        }
+        applyPrimaryTranslation()
+        applySecondaryTranslation()
+        var clearAllSpacing = recentsView.pageSpacing + recentsView.clearAllExtraPageSpacing
+        clearAllSpacing = if (isLayoutRtl) -clearAllSpacing else clearAllSpacing
+        scrollAlpha =
+            ((clearAllScroll + clearAllSpacing - scroll) / clearAllSpacing.toFloat()).coerceAtLeast(
+                0f
+            )
+    }
+
+    fun getScrollAdjustment(fullscreenEnabled: Boolean, gridEnabled: Boolean): Float {
+        var scrollAdjustment = 0f
+        if (fullscreenEnabled) {
+            scrollAdjustment += fullscreenTranslationPrimary
+        }
+        if (gridEnabled) {
+            scrollAdjustment += gridTranslationPrimary + gridScrollOffset
+        }
+        scrollAdjustment += scrollOffsetPrimary
+        return scrollAdjustment
+    }
+
+    fun getOffsetAdjustment(fullscreenEnabled: Boolean, gridEnabled: Boolean) =
+        getScrollAdjustment(fullscreenEnabled, gridEnabled)
+
+    private fun applyPrimaryTranslation() {
+        val recentsView = recentsView ?: return
+        val orientationHandler = recentsView.pagedOrientationHandler
+        orientationHandler.primaryViewTranslate.set(
+            this,
+            (orientationHandler.getPrimaryValue(0f, taskAlignmentTranslationY) +
+                normalTranslationPrimary +
+                getFullscreenTrans(fullscreenTranslationPrimary) +
+                getGridTrans(gridTranslationPrimary)),
+        )
+    }
+
+    private fun applySecondaryTranslation() {
+        val recentsView = recentsView ?: return
+        val orientationHandler = recentsView.pagedOrientationHandler
+        orientationHandler.secondaryViewTranslate.set(
+            this,
+            orientationHandler.getSecondaryValue(0f, taskAlignmentTranslationY),
+        )
+    }
+
+    private fun getFullscreenTrans(endTranslation: Float) =
+        if (fullscreenProgress > 0) endTranslation else 0f
+
+    private fun getGridTrans(endTranslation: Float) = if (gridProgress > 0) endTranslation else 0f
+
+    companion object {
+        private enum class Alpha {
+            SCROLL,
+            CONTENT,
+            VISIBILITY,
+            DISMISS,
+        }
+
+        @JvmField
+        val VISIBILITY_ALPHA: FloatProperty<ClearAllButton> =
+            KFloatProperty(ClearAllButton::visibilityAlpha)
+
+        @JvmField
+        val DISMISS_ALPHA: FloatProperty<ClearAllButton> =
+            KFloatProperty(ClearAllButton::dismissAlpha)
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 576a56e..27657b4 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -17,32 +17,49 @@
 
 import android.annotation.SuppressLint
 import android.content.Context
-import android.graphics.Point
+import android.graphics.Matrix
 import android.graphics.PointF
 import android.graphics.Rect
+import android.graphics.Rect.intersects
+import android.graphics.RectF
 import android.util.AttributeSet
 import android.util.Log
+import android.util.Size
 import android.view.Gravity
 import android.view.View
+import android.view.ViewStub
 import androidx.core.content.res.ResourcesCompat
 import androidx.core.view.updateLayoutParams
+import com.android.internal.hidden_from_bootclasspath.com.android.window.flags.Flags.enableDesktopRecentsTransitionsCornersBugfix
+import com.android.launcher3.Flags.enableDesktopExplodedView
+import com.android.launcher3.Flags.enableOverviewIconMenu
 import com.android.launcher3.Flags.enableRefactorTaskThumbnail
 import com.android.launcher3.R
+import com.android.launcher3.statehandlers.DesktopVisibilityController
 import com.android.launcher3.testing.TestLogging
 import com.android.launcher3.testing.shared.TestProtocol
 import com.android.launcher3.util.RunnableList
 import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.launcher3.util.TransformingTouchDelegate
 import com.android.launcher3.util.ViewPool
+import com.android.launcher3.util.rects.lerpRect
 import com.android.launcher3.util.rects.set
 import com.android.quickstep.BaseContainerInterface
 import com.android.quickstep.DesktopFullscreenDrawParams
 import com.android.quickstep.FullscreenDrawParams
+import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle
 import com.android.quickstep.TaskOverlayFactory
 import com.android.quickstep.ViewUtils
+import com.android.quickstep.recents.di.RecentsDependencies
+import com.android.quickstep.recents.di.get
+import com.android.quickstep.recents.domain.model.DesktopTaskBoundsData
+import com.android.quickstep.recents.ui.viewmodel.DesktopTaskViewModel
+import com.android.quickstep.recents.ui.viewmodel.TaskData
+import com.android.quickstep.task.thumbnail.TaskContentView
 import com.android.quickstep.task.thumbnail.TaskThumbnailView
+import com.android.quickstep.util.DesktopTask
 import com.android.quickstep.util.RecentsOrientedState
-import com.android.systemui.shared.recents.model.Task
+import kotlin.math.roundToInt
 
 /** TaskView that contains all tasks that are part of the desktop. */
 class DesktopTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
@@ -52,40 +69,58 @@
         type = TaskViewType.DESKTOP,
         thumbnailFullscreenParams = DesktopFullscreenDrawParams(context),
     ) {
+    var deskId = DesktopVisibilityController.INACTIVE_DESK_ID
+
     private val contentViewFullscreenParams = FullscreenDrawParams(context)
 
-    private val taskThumbnailViewDeprecatedPool =
-        if (!enableRefactorTaskThumbnail()) {
-            ViewPool<TaskThumbnailViewDeprecated>(
-                context,
-                this,
-                R.layout.task_thumbnail_deprecated,
-                VIEW_POOL_MAX_SIZE,
-                VIEW_POOL_INITIAL_SIZE,
-            )
-        } else null
-
-    private val taskThumbnailViewPool =
-        if (enableRefactorTaskThumbnail()) {
-            ViewPool<TaskThumbnailView>(
-                context,
-                this,
-                R.layout.task_thumbnail,
-                VIEW_POOL_MAX_SIZE,
-                VIEW_POOL_INITIAL_SIZE,
-            )
-        } else null
+    private val taskContentViewPool =
+        ViewPool<TaskContentView>(
+            context,
+            this,
+            R.layout.task_content_view,
+            VIEW_POOL_MAX_SIZE,
+            VIEW_POOL_INITIAL_SIZE,
+        )
 
     private val tempPointF = PointF()
-    private val tempRect = Rect()
+    private val lastComputedTaskSize = Rect()
     private lateinit var iconView: TaskViewIcon
     private lateinit var contentView: DesktopTaskContentView
     private lateinit var backgroundView: View
 
+    private var viewModel: DesktopTaskViewModel? = null
+
+    /**
+     * Holds the default (user placed) positions of task windows. This can be moved into the
+     * viewModel once RefactorTaskThumbnail has been launched.
+     */
+    private var fullscreenTaskPositions: List<DesktopTaskBoundsData> = emptyList()
+
+    /**
+     * When enableDesktopExplodedView is enabled, this controls the gradual transition from the
+     * default positions to the organized non-overlapping positions.
+     */
+    var explodeProgress = 0.0f
+        set(value) {
+            field = value
+            positionTaskWindows()
+        }
+
+    var remoteTargetHandles: Array<RemoteTargetHandle>? = null
+        set(value) {
+            field = value
+            positionTaskWindows()
+        }
+
+    private fun getRemoteTargetHandle(taskId: Int): RemoteTargetHandle? =
+        remoteTargetHandles?.firstOrNull {
+            it.transformParams.targetSet.firstAppTargetTaskId == taskId
+        }
+
     override fun onFinishInflate() {
         super.onFinishInflate()
         iconView =
-            getOrInflateIconView(R.id.icon).apply {
+            (findViewById<View>(R.id.icon) as TaskViewIcon).apply {
                 setIcon(
                     this,
                     ResourcesCompat.getDrawable(
@@ -109,33 +144,175 @@
             }
     }
 
+    override fun inflateViewStubs() {
+        findViewById<ViewStub>(R.id.icon)
+            ?.apply {
+                layoutResource =
+                    if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
+                    else R.layout.icon_view
+            }
+            ?.inflate()
+    }
+
+    private fun positionTaskWindows() {
+        if (taskContainers.isEmpty()) {
+            return
+        }
+
+        val thumbnailTopMarginPx = container.deviceProfile.overviewTaskThumbnailTopMarginPx
+
+        val taskViewWidth = layoutParams.width
+        val taskViewHeight = layoutParams.height - thumbnailTopMarginPx
+
+        BaseContainerInterface.getTaskDimension(mContext, container.deviceProfile, tempPointF)
+
+        val screenWidth = tempPointF.x.toInt()
+        val screenHeight = tempPointF.y.toInt()
+        val screenRect = Rect(0, 0, screenWidth, screenHeight)
+        val scaleWidth = taskViewWidth / screenWidth.toFloat()
+        val scaleHeight = taskViewHeight / screenHeight.toFloat()
+
+        taskContainers.forEach {
+            val taskId = it.task.key.id
+            val fullscreenTaskPosition =
+                fullscreenTaskPositions.firstOrNull { it.taskId == taskId } ?: return
+            val overviewTaskPosition =
+                if (enableDesktopExplodedView()) {
+                    viewModel!!
+                        .organizedDesktopTaskPositions
+                        .firstOrNull { it.taskId == taskId }
+                        ?.let { organizedPosition ->
+                            TEMP_OVERVIEW_TASK_POSITION.apply {
+                                lerpRect(
+                                    fullscreenTaskPosition.bounds,
+                                    organizedPosition.bounds,
+                                    explodeProgress,
+                                )
+                            }
+                        } ?: fullscreenTaskPosition.bounds
+                } else {
+                    fullscreenTaskPosition.bounds
+                }
+
+            if (enableDesktopExplodedView()) {
+                getRemoteTargetHandle(taskId)?.let { remoteTargetHandle ->
+                    val fromRect =
+                        TEMP_FROM_RECTF.apply {
+                            set(fullscreenTaskPosition.bounds)
+                            scale(scaleWidth)
+                            offset(
+                                lastComputedTaskSize.left.toFloat(),
+                                lastComputedTaskSize.top.toFloat(),
+                            )
+                        }
+                    val toRect =
+                        TEMP_TO_RECTF.apply {
+                            set(overviewTaskPosition)
+                            scale(scaleWidth)
+                            offset(
+                                lastComputedTaskSize.left.toFloat(),
+                                lastComputedTaskSize.top.toFloat(),
+                            )
+                        }
+                    val transform = Matrix()
+                    transform.setRectToRect(fromRect, toRect, Matrix.ScaleToFit.FILL)
+                    remoteTargetHandle.taskViewSimulator.setTaskRectTransform(transform)
+                    remoteTargetHandle.taskViewSimulator.apply(remoteTargetHandle.transformParams)
+                }
+            }
+
+            val taskLeft = overviewTaskPosition.left * scaleWidth
+            val taskTop = overviewTaskPosition.top * scaleHeight
+            val taskWidth = overviewTaskPosition.width() * scaleWidth
+            val taskHeight = overviewTaskPosition.height() * scaleHeight
+            // TODO(b/394660950): Revisit the choice to update the layout when explodeProgress == 1.
+            // To run the explode animation in reverse, it may be simpler to use translation/scale
+            // for all cases where the progress is non-zero.
+            if (explodeProgress == 0.0f || explodeProgress == 1.0f) {
+                // Reset scaling and translation that may have been applied during animation.
+                it.taskContentView.apply {
+                    scaleX = 1.0f
+                    scaleY = 1.0f
+                    translationX = 0.0f
+                    translationY = 0.0f
+                }
+
+                // Position the task to the same position as it would be on the desktop
+                it.taskContentView?.updateLayoutParams<LayoutParams> {
+                    gravity = Gravity.LEFT or Gravity.TOP
+                    width = taskWidth.toInt()
+                    height = taskHeight.toInt()
+                    leftMargin = taskLeft.toInt()
+                    topMargin = taskTop.toInt()
+                }
+
+                if (
+                    enableDesktopRecentsTransitionsCornersBugfix() && enableRefactorTaskThumbnail()
+                ) {
+                    it.taskContentView?.outlineBounds =
+                        if (intersects(overviewTaskPosition, screenRect))
+                            Rect(overviewTaskPosition).apply {
+                                intersectUnchecked(screenRect)
+                                // Offset to 0,0 to transform into TaskThumbnailView's coordinate
+                                // system.
+                                offset(-overviewTaskPosition.left, -overviewTaskPosition.top)
+                                left = (left * scaleWidth).roundToInt()
+                                top = (top * scaleHeight).roundToInt()
+                                right = (right * scaleWidth).roundToInt()
+                                bottom = (bottom * scaleHeight).roundToInt()
+                            }
+                        else null
+                }
+            } else {
+                // During the animation, apply translation and scale such that the view is
+                // transformed to where we want, without triggering layout.
+                it.taskContentView.apply {
+                    pivotX = 0.0f
+                    pivotY = 0.0f
+                    translationX = taskLeft - left
+                    translationY = taskTop - top
+                    scaleX = taskWidth / width.toFloat()
+                    scaleY = taskHeight / height.toFloat()
+                }
+            }
+        }
+    }
+
     /** Updates this desktop task to the gives task list defined in `tasks` */
     fun bind(
-        tasks: List<Task>,
+        desktopTask: DesktopTask,
         orientedState: RecentsOrientedState,
         taskOverlayFactory: TaskOverlayFactory,
     ) {
+        deskId = desktopTask.deskId
+        // TODO(b/370495260): Minimized tasks should not be filtered with desktop exploded view
+        // support.
+        // Minimized tasks should not be shown in Overview.
+        val tasks = desktopTask.tasks.filterNot { it.isMinimized }
         if (DEBUG) {
             val sb = StringBuilder()
             sb.append("bind tasks=").append(tasks.size).append("\n")
             tasks.forEach { sb.append(" key=${it.key}\n") }
             Log.d(TAG, sb.toString())
         }
+
         cancelPendingLoadTasks()
         val backgroundViewIndex = contentView.indexOfChild(backgroundView)
         taskContainers =
             tasks.map { task ->
+                val taskContentView = taskContentViewPool.view
+                contentView.addView(taskContentView, backgroundViewIndex + 1)
                 val snapshotView =
                     if (enableRefactorTaskThumbnail()) {
-                        taskThumbnailViewPool!!.view
+                        taskContentView.findViewById<TaskThumbnailView>(R.id.snapshot)
                     } else {
-                        taskThumbnailViewDeprecatedPool!!.view
+                        taskContentView.findViewById<TaskThumbnailViewDeprecated>(R.id.snapshot)
                     }
-                contentView.addView(snapshotView, backgroundViewIndex + 1)
 
                 TaskContainer(
                     this,
                     task,
+                    taskContentView,
                     snapshotView,
                     iconView,
                     TransformingTouchDelegate(iconView.asView()),
@@ -148,85 +325,35 @@
         onBind(orientedState)
     }
 
-    override fun onRecycle() {
-        super.onRecycle()
-        visibility = VISIBLE
-        taskContainers.forEach {
-            contentView.removeView(it.snapshotView)
-            if (enableRefactorTaskThumbnail()) {
-                taskThumbnailViewPool!!.recycle(it.thumbnailView)
-            } else {
-                taskThumbnailViewDeprecatedPool!!.recycle(it.thumbnailViewDeprecated)
-            }
+    override fun onBind(orientedState: RecentsOrientedState) {
+        super.onBind(orientedState)
+
+        if (enableRefactorTaskThumbnail()) {
+            viewModel =
+                DesktopTaskViewModel(organizeDesktopTasksUseCase = RecentsDependencies.get(context))
         }
     }
 
+    override fun onRecycle() {
+        super.onRecycle()
+        deskId = DesktopVisibilityController.INACTIVE_DESK_ID
+        explodeProgress = 0.0f
+        viewModel = null
+        visibility = VISIBLE
+        taskContainers.forEach { removeAndRecycleThumbnailView(it) }
+    }
+
+    override fun setOrientationState(orientationState: RecentsOrientedState) {
+        super.setOrientationState(orientationState)
+        iconView.setIconOrientation(orientationState, isGridTask)
+    }
+
     @SuppressLint("RtlHardcoded")
-    override fun updateTaskSize(
-        lastComputedTaskSize: Rect,
-        lastComputedGridTaskSize: Rect,
-        lastComputedCarouselTaskSize: Rect,
-    ) {
-        super.updateTaskSize(
-            lastComputedTaskSize,
-            lastComputedGridTaskSize,
-            lastComputedCarouselTaskSize,
-        )
-        if (taskContainers.isEmpty()) {
-            return
-        }
+    override fun updateTaskSize(lastComputedTaskSize: Rect, lastComputedGridTaskSize: Rect) {
+        super.updateTaskSize(lastComputedTaskSize, lastComputedGridTaskSize)
+        this.lastComputedTaskSize.set(lastComputedTaskSize)
 
-        val thumbnailTopMarginPx = container.deviceProfile.overviewTaskThumbnailTopMarginPx
-
-        val containerWidth = layoutParams.width
-        val containerHeight = layoutParams.height - thumbnailTopMarginPx
-
-        BaseContainerInterface.getTaskDimension(mContext, container.deviceProfile, tempPointF)
-
-        val windowWidth = tempPointF.x.toInt()
-        val windowHeight = tempPointF.y.toInt()
-        val scaleWidth = containerWidth / windowWidth.toFloat()
-        val scaleHeight = containerHeight / windowHeight.toFloat()
-
-        if (DEBUG) {
-            Log.d(
-                TAG,
-                "onMeasure: container=[$containerWidth,$containerHeight]" +
-                    "window=[$windowWidth,$windowHeight] scale=[$scaleWidth,$scaleHeight]",
-            )
-        }
-
-        // Desktop tile is a shrunk down version of launcher and freeform task thumbnails.
-        taskContainers.forEach {
-            // Default to quarter of the desktop if we did not get app bounds.
-            val taskSize =
-                it.task.appBounds
-                    ?: tempRect.apply {
-                        left = 0
-                        top = 0
-                        right = windowWidth / 4
-                        bottom = windowHeight / 4
-                    }
-            val positionInParent = it.task.positionInParent ?: ORIGIN
-
-            // Position the task to the same position as it would be on the desktop
-            it.snapshotView.updateLayoutParams<LayoutParams> {
-                gravity = Gravity.LEFT or Gravity.TOP
-                width = (taskSize.width() * scaleWidth).toInt()
-                height = (taskSize.height() * scaleHeight).toInt()
-                leftMargin = (positionInParent.x * scaleWidth).toInt()
-                topMargin = (positionInParent.y * scaleHeight).toInt()
-            }
-            if (DEBUG) {
-                with(it.snapshotView.layoutParams as LayoutParams) {
-                    Log.d(
-                        TAG,
-                        "onMeasure: task=${it.task.key} size=[$width,$height]" +
-                            " margin=[$leftMargin,$topMargin]",
-                    )
-                }
-            }
-        }
+        updateTaskPositions()
     }
 
     override fun onTaskListVisibilityChanged(visible: Boolean, changes: Int) {
@@ -241,6 +368,10 @@
         taskContainer.snapshotView.contentDescription = taskContainer.task.titleDescription
     }
 
+    override fun setIconState(container: TaskContainer, state: TaskData?) {
+        container.snapshotView.contentDescription = (state as? TaskData.Data)?.titleDescription
+    }
+
     // Ignoring [onIconUnloaded] as all tasks shares the same Desktop icon
     override fun onIconUnloaded(taskContainer: TaskContainer) {}
 
@@ -308,6 +439,52 @@
         ViewUtils.addAccessibleChildToList(backgroundView, outChildren)
     }
 
+    fun removeTaskFromExplodedView(taskId: Int, animate: Boolean) {
+        if (!enableDesktopExplodedView()) {
+            Log.e(
+                TAG,
+                "removeTaskFromExplodedView called when enableDesktopExplodedView flag is false",
+            )
+            return
+        }
+
+        // Remove the task's [taskContainer] and its associated Views.
+        val taskContainer = getTaskContainerById(taskId) ?: return
+        removeAndRecycleThumbnailView(taskContainer)
+        taskContainer.destroy()
+        taskContainers = taskContainers.filterNot { it == taskContainer }
+
+        // Dismiss the current DesktopTaskView if all its windows are closed.
+        if (taskContainers.isEmpty()) {
+            recentsView?.dismissTaskView(this, animate, /* removeTask= */ true)
+        } else {
+            // Otherwise, re-position the remaining task windows.
+            // TODO(b/353949276): Implement the re-layout animations.
+            updateTaskPositions()
+        }
+    }
+
+    private fun removeAndRecycleThumbnailView(taskContainer: TaskContainer) {
+        contentView.removeView(taskContainer.taskContentView)
+        taskContentViewPool.recycle(taskContainer.taskContentView)
+    }
+
+    private fun updateTaskPositions() {
+        BaseContainerInterface.getTaskDimension(mContext, container.deviceProfile, tempPointF)
+        val desktopSize = Size(tempPointF.x.toInt(), tempPointF.y.toInt())
+        DEFAULT_BOUNDS.set(0, 0, desktopSize.width / 4, desktopSize.height / 4)
+
+        fullscreenTaskPositions =
+            taskContainers.map {
+                DesktopTaskBoundsData(it.task.key.id, it.task.appBounds ?: DEFAULT_BOUNDS)
+            }
+
+        if (enableDesktopExplodedView()) {
+            viewModel?.organizeDesktopTasks(desktopSize, fullscreenTaskPositions)
+        }
+        positionTaskWindows()
+    }
+
     companion object {
         private const val TAG = "DesktopTaskView"
         private const val DEBUG = false
@@ -315,6 +492,10 @@
 
         // As DesktopTaskView is inflated in background, use initialSize=0 to avoid initPool.
         private const val VIEW_POOL_INITIAL_SIZE = 0
-        private val ORIGIN = Point(0, 0)
+        private val DEFAULT_BOUNDS = Rect()
+        // Temporaries used for various purposes to avoid allocations.
+        private val TEMP_OVERVIEW_TASK_POSITION = Rect()
+        private val TEMP_FROM_RECTF = RectF()
+        private val TEMP_TO_RECTF = RectF()
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt
index c07b7fb..5c4a35d 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.kt
@@ -189,11 +189,11 @@
                 SplitBannerConfig.SPLIT_GRID_BANNER_LARGE
             // For landscape grid, for 30% width we only show icon, otherwise show icon and time
             task.key.id == splitBounds.leftTopTaskId ->
-                if (splitBounds.leftTaskPercent < THRESHOLD_LEFT_ICON_ONLY)
+                if (splitBounds.leftTopTaskPercent < THRESHOLD_LEFT_ICON_ONLY)
                     SplitBannerConfig.SPLIT_GRID_BANNER_SMALL
                 else SplitBannerConfig.SPLIT_GRID_BANNER_LARGE
             else ->
-                if (splitBounds.leftTaskPercent > THRESHOLD_RIGHT_ICON_ONLY)
+                if (splitBounds.leftTopTaskPercent > THRESHOLD_RIGHT_ICON_ONLY)
                     SplitBannerConfig.SPLIT_GRID_BANNER_SMALL
                 else SplitBannerConfig.SPLIT_GRID_BANNER_LARGE
         }
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 0d9583d..71a4dde 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -21,11 +21,11 @@
 import android.util.AttributeSet
 import android.util.Log
 import android.view.View
+import android.view.ViewStub
 import com.android.internal.jank.Cuj
 import com.android.launcher3.Flags.enableOverviewIconMenu
 import com.android.launcher3.R
 import com.android.launcher3.Utilities
-import com.android.launcher3.config.FeatureFlags
 import com.android.launcher3.util.RunnableList
 import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
@@ -36,6 +36,7 @@
 import com.android.quickstep.util.SplitSelectStateController
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper
+import com.android.wm.shell.Flags.enableFlexibleTwoAppSplit
 import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition
 
 /**
@@ -50,6 +51,15 @@
  */
 class GroupedTaskView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
     TaskView(context, attrs, type = TaskViewType.GROUPED) {
+
+    private val MINIMUM_RATIO_TO_SHOW_ICON = 0.2f
+
+    val leftTopTaskContainer: TaskContainer
+        get() = taskContainers[0]
+
+    val rightBottomTaskContainer: TaskContainer
+        get() = taskContainers[1]
+
     // TODO(b/336612373): Support new TTV for GroupedTaskView
     var splitBoundsConfig: SplitConfigurationOptions.SplitBounds? = null
         private set
@@ -67,8 +77,8 @@
         val splitBoundsConfig = splitBoundsConfig ?: return
         val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID
         pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
-            taskContainers[0].snapshotView,
-            taskContainers[1].snapshotView,
+            leftTopTaskContainer.taskContentView,
+            rightBottomTaskContainer.taskContentView,
             widthSize,
             heightSize,
             splitBoundsConfig,
@@ -82,6 +92,20 @@
         }
     }
 
+    override fun inflateViewStubs() {
+        super.inflateViewStubs()
+        findViewById<ViewStub>(R.id.bottomright_task_content_view)
+            ?.apply { layoutResource = R.layout.task_content_view }
+            ?.inflate()
+        findViewById<ViewStub>(R.id.bottomRight_icon)
+            ?.apply {
+                layoutResource =
+                    if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
+                    else R.layout.icon_view
+            }
+            ?.inflate()
+    }
+
     override fun onRecycle() {
         super.onRecycle()
         splitBoundsConfig = null
@@ -99,6 +123,7 @@
             listOf(
                 createTaskContainer(
                     primaryTask,
+                    R.id.task_content_view,
                     R.id.snapshot,
                     R.id.icon,
                     R.id.show_windows,
@@ -108,7 +133,8 @@
                 ),
                 createTaskContainer(
                     secondaryTask,
-                    R.id.bottomright_snapshot,
+                    R.id.bottomright_task_content_view,
+                    R.id.snapshot,
                     R.id.bottomRight_icon,
                     R.id.show_windows_right,
                     R.id.bottomRight_digital_wellbeing_toast,
@@ -142,12 +168,10 @@
                 val iconMargins = (iconViewMarginStart + iconViewBackgroundMarginStart) * 2
                 // setMaxWidth() needs to be called before mIconView.setIconOrientation which is
                 // called in the super below.
-                (taskContainers[0].iconView as IconAppChipView).setMaxWidth(
+                (leftTopTaskContainer.iconView as IconAppChipView).maxWidth =
                     groupedTaskViewSizes.first.x - iconMargins
-                )
-                (taskContainers[1].iconView as IconAppChipView).setMaxWidth(
+                (rightBottomTaskContainer.iconView as IconAppChipView).maxWidth =
                     groupedTaskViewSizes.second.x - iconMargins
-                )
             }
         }
         super.setOrientationState(orientationState)
@@ -156,44 +180,72 @@
 
     private fun updateIconPlacement() {
         val splitBoundsConfig = splitBoundsConfig ?: return
-        val taskIconHeight = container.deviceProfile.overviewTaskIconSizePx
+        val deviceProfile = container.deviceProfile
+        val taskIconHeight = deviceProfile.overviewTaskIconSizePx
         val isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
         val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID
+        var oneIconHiddenDueToSmallWidth = false
+
+        if (enableFlexibleTwoAppSplit()) {
+            // Update values for both icons' setFlexSplitAlpha. Mainly, we want to hide an icon if
+            // its app tile is too small. But we also have to set the alphas back if we go to
+            // split selection.
+            val hideLeftTopIcon: Boolean
+            val hideRightBottomIcon: Boolean
+            if (inSplitSelection) {
+                hideLeftTopIcon =
+                    getThisTaskCurrentlyInSplitSelection() == splitBoundsConfig.leftTopTaskId
+                hideRightBottomIcon =
+                    getThisTaskCurrentlyInSplitSelection() == splitBoundsConfig.rightBottomTaskId
+            } else {
+                hideLeftTopIcon = splitBoundsConfig.leftTopTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON
+                hideRightBottomIcon =
+                    splitBoundsConfig.rightBottomTaskPercent < MINIMUM_RATIO_TO_SHOW_ICON
+                if (hideLeftTopIcon || hideRightBottomIcon) {
+                    oneIconHiddenDueToSmallWidth = true
+                }
+            }
+
+            leftTopTaskContainer.iconView.setFlexSplitAlpha(if (hideLeftTopIcon) 0f else 1f)
+            rightBottomTaskContainer.iconView.setFlexSplitAlpha(if (hideRightBottomIcon) 0f else 1f)
+        }
 
         if (enableOverviewIconMenu()) {
             val groupedTaskViewSizes =
                 pagedOrientationHandler.getGroupedTaskViewSizes(
-                    container.deviceProfile,
+                    deviceProfile,
                     splitBoundsConfig,
                     layoutParams.width,
                     layoutParams.height,
                 )
             pagedOrientationHandler.setSplitIconParams(
-                taskContainers[0].iconView.asView(),
-                taskContainers[1].iconView.asView(),
+                leftTopTaskContainer.iconView.asView(),
+                rightBottomTaskContainer.iconView.asView(),
                 taskIconHeight,
                 groupedTaskViewSizes.first.x,
                 groupedTaskViewSizes.first.y,
                 layoutParams.height,
                 layoutParams.width,
                 isRtl,
-                container.deviceProfile,
+                deviceProfile,
                 splitBoundsConfig,
                 inSplitSelection,
+                oneIconHiddenDueToSmallWidth,
             )
         } else {
             pagedOrientationHandler.setSplitIconParams(
-                taskContainers[0].iconView.asView(),
-                taskContainers[1].iconView.asView(),
+                leftTopTaskContainer.iconView.asView(),
+                rightBottomTaskContainer.iconView.asView(),
                 taskIconHeight,
-                taskContainers[0].snapshotView.measuredWidth,
-                taskContainers[0].snapshotView.measuredHeight,
+                leftTopTaskContainer.taskContentView.measuredWidth,
+                leftTopTaskContainer.taskContentView.measuredHeight,
                 measuredHeight,
                 measuredWidth,
                 isRtl,
-                container.deviceProfile,
+                deviceProfile,
                 splitBoundsConfig,
                 inSplitSelection,
+                oneIconHiddenDueToSmallWidth,
             )
         }
     }
@@ -247,8 +299,8 @@
         recentsView?.let {
             it.splitSelectController.launchExistingSplitPair(
                 if (launchingExistingTaskView) this else null,
-                taskContainers[0].task.key.id,
-                taskContainers[1].task.key.id,
+                leftTopTaskContainer.task.key.id,
+                rightBottomTaskContainer.task.key.id,
                 STAGE_POSITION_TOP_OR_LEFT,
                 callback,
                 isQuickSwitch,
@@ -278,14 +330,14 @@
             // checks below aren't reliable since both of those views may be gone/transformed
             val initSplitTaskId = getThisTaskCurrentlyInSplitSelection()
             if (initSplitTaskId != INVALID_TASK_ID) {
-                return if (initSplitTaskId == taskContainers[0].task.key.id) 1 else 0
+                return if (initSplitTaskId == leftTopTaskContainer.task.key.id) 1 else 0
             }
         }
 
         // Check which of the two apps was selected
         if (
-            taskContainers[1].iconView.asView().containsPoint(lastTouchDownPosition) ||
-                taskContainers[1].snapshotView.containsPoint(lastTouchDownPosition)
+            rightBottomTaskContainer.iconView.asView().containsPoint(lastTouchDownPosition) ||
+                rightBottomTaskContainer.snapshotView.containsPoint(lastTouchDownPosition)
         ) {
             return 1
         }
@@ -298,14 +350,6 @@
         return Utilities.pointInView(this, localPos[0], localPos[1], 0f /* slop */)
     }
 
-    override fun setOverlayEnabled(overlayEnabled: Boolean) {
-        if (FeatureFlags.enableAppPairs()) {
-            super.setOverlayEnabled(overlayEnabled)
-        } else {
-            // Intentional no-op to prevent setting smart actions overlay on thumbnails
-        }
-    }
-
     companion object {
         private const val TAG = "GroupedTaskView"
     }
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.java b/quickstep/src/com/android/quickstep/views/IconAppChipView.java
deleted file mode 100644
index ba42594..0000000
--- a/quickstep/src/com/android/quickstep/views/IconAppChipView.java
+++ /dev/null
@@ -1,464 +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.quickstep.views;
-
-import static com.android.app.animation.Interpolators.EMPHASIZED;
-import static com.android.app.animation.Interpolators.LINEAR;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
-import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.RectEvaluator;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Outline;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.ViewAnimationUtils;
-import android.view.ViewOutlineProvider;
-import android.widget.FrameLayout;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.util.MultiPropertyFactory;
-import com.android.launcher3.util.MultiValueAlpha;
-import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
-import com.android.quickstep.util.RecentsOrientedState;
-
-/**
- * An icon app menu view which can be used in place of an IconView in overview TaskViews.
- */
-public class IconAppChipView extends FrameLayout implements TaskViewIcon {
-
-    private static final int MENU_BACKGROUND_REVEAL_DURATION = 417;
-    private static final int MENU_BACKGROUND_HIDE_DURATION = 333;
-
-    private static final int NUM_ALPHA_CHANNELS = 3;
-    private static final int INDEX_CONTENT_ALPHA = 0;
-    private static final int INDEX_COLOR_FILTER_ALPHA = 1;
-    private static final int INDEX_MODAL_ALPHA = 2;
-
-    private final MultiValueAlpha mMultiValueAlpha;
-
-    private View mMenuAnchorView;
-    private IconView mIconView;
-    // Two textview so we can ellipsize the collapsed view and crossfade on expand to the full name.
-    private TextView mIconTextCollapsedView;
-    private TextView mIconTextExpandedView;
-    private ImageView mIconArrowView;
-    private final Rect mBackgroundRelativeLtrLocation = new Rect();
-    final RectEvaluator mBackgroundAnimationRectEvaluator =
-            new RectEvaluator(mBackgroundRelativeLtrLocation);
-    private final int mCollapsedMenuDefaultWidth;
-    private final int mExpandedMenuDefaultWidth;
-    private final int mCollapsedMenuDefaultHeight;
-    private final int mExpandedMenuDefaultHeight;
-    private final int mIconMenuMarginTopStart;
-    private final int mMenuToChipGap;
-    private final int mBackgroundMarginTopStart;
-    private final int mAppNameHorizontalMargin;
-    private final int mIconViewMarginStart;
-    private final int mAppIconSize;
-    private final int mArrowSize;
-    private final int mIconViewDrawableExpandedSize;
-    private final int mArrowMarginEnd;
-    private AnimatorSet mAnimator;
-
-    private int mMaxWidth = Integer.MAX_VALUE;
-
-    private static final int INDEX_SPLIT_TRANSLATION = 0;
-    private static final int INDEX_MENU_TRANSLATION = 1;
-    private static final int INDEX_COUNT_TRANSLATION = 2;
-
-    private final MultiPropertyFactory<View> mViewTranslationX;
-    private final MultiPropertyFactory<View> mViewTranslationY;
-
-    /**
-     * Gets the view split x-axis translation
-     */
-    public MultiPropertyFactory<View>.MultiProperty getSplitTranslationX() {
-        return mViewTranslationX.get(INDEX_SPLIT_TRANSLATION);
-    }
-
-    /**
-     * Sets the view split x-axis translation
-     * @param translationX x-axis translation
-     */
-    public void setSplitTranslationX(float translationX) {
-        getSplitTranslationX().setValue(translationX);
-    }
-
-    /**
-     * Gets the view split y-axis translation
-     */
-    public MultiPropertyFactory<View>.MultiProperty getSplitTranslationY() {
-        return mViewTranslationY.get(INDEX_SPLIT_TRANSLATION);
-    }
-
-    /**
-     * Sets the view split y-axis translation
-     * @param translationY y-axis translation
-     */
-    public void setSplitTranslationY(float translationY) {
-        getSplitTranslationY().setValue(translationY);
-    }
-
-    /**
-     * Gets the menu x-axis translation for split task
-     */
-    public MultiPropertyFactory<View>.MultiProperty getMenuTranslationX() {
-        return mViewTranslationX.get(INDEX_MENU_TRANSLATION);
-    }
-
-    /**
-     * Gets the menu y-axis translation for split task
-     */
-    public MultiPropertyFactory<View>.MultiProperty getMenuTranslationY() {
-        return mViewTranslationY.get(INDEX_MENU_TRANSLATION);
-    }
-
-    public IconAppChipView(Context context) {
-        this(context, null);
-    }
-
-    public IconAppChipView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public IconAppChipView(Context context, AttributeSet attrs, int defStyleAttr) {
-        this(context, attrs, defStyleAttr, 0);
-    }
-
-    public IconAppChipView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-        Resources res = getResources();
-        mMultiValueAlpha = new MultiValueAlpha(this, NUM_ALPHA_CHANNELS);
-        mMultiValueAlpha.setUpdateVisibility(/* updateVisibility= */ true);
-
-        // Menu dimensions
-        mCollapsedMenuDefaultWidth =
-                res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_collapsed_width);
-        mExpandedMenuDefaultWidth =
-                res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_expanded_width);
-        mCollapsedMenuDefaultHeight =
-                res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_collapsed_height);
-        mExpandedMenuDefaultHeight =
-                res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_expanded_height);
-        mIconMenuMarginTopStart = res.getDimensionPixelSize(
-                R.dimen.task_thumbnail_icon_menu_expanded_top_start_margin);
-        mMenuToChipGap = res.getDimensionPixelSize(
-                R.dimen.task_thumbnail_icon_menu_expanded_gap);
-
-        // Background dimensions
-        mBackgroundMarginTopStart = res.getDimensionPixelSize(
-                R.dimen.task_thumbnail_icon_menu_background_margin_top_start);
-
-        // Contents dimensions
-        mAppNameHorizontalMargin = res.getDimensionPixelSize(
-                R.dimen.task_thumbnail_icon_menu_app_name_margin_horizontal_collapsed);
-        mArrowMarginEnd = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_arrow_margin);
-        mIconViewMarginStart = res.getDimensionPixelSize(
-                R.dimen.task_thumbnail_icon_view_start_margin);
-        mAppIconSize = res.getDimensionPixelSize(
-                R.dimen.task_thumbnail_icon_menu_app_icon_collapsed_size);
-        mArrowSize = res.getDimensionPixelSize(
-                R.dimen.task_thumbnail_icon_menu_arrow_size);
-        mIconViewDrawableExpandedSize = res.getDimensionPixelSize(
-                R.dimen.task_thumbnail_icon_menu_app_icon_expanded_size);
-
-        mViewTranslationX = new MultiPropertyFactory<>(this, VIEW_TRANSLATE_X,
-                INDEX_COUNT_TRANSLATION,
-                Float::sum);
-        mViewTranslationY = new MultiPropertyFactory<>(this, VIEW_TRANSLATE_Y,
-                INDEX_COUNT_TRANSLATION,
-                Float::sum);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mIconView = findViewById(R.id.icon_view);
-        mIconTextCollapsedView = findViewById(R.id.icon_text_collapsed);
-        mIconTextExpandedView = findViewById(R.id.icon_text_expanded);
-        mIconArrowView = findViewById(R.id.icon_arrow);
-        mMenuAnchorView = findViewById(R.id.icon_view_menu_anchor);
-    }
-
-    protected IconView getIconView() {
-        return mIconView;
-    }
-
-    @Override
-    public void setText(CharSequence text) {
-        if (mIconTextCollapsedView != null) {
-            mIconTextCollapsedView.setText(text);
-        }
-        if (mIconTextExpandedView != null) {
-            mIconTextExpandedView.setText(text);
-        }
-    }
-
-    @Override
-    public Drawable getDrawable() {
-        return mIconView == null ? null : mIconView.getDrawable();
-    }
-
-    @Override
-    public void setDrawable(Drawable icon) {
-        if (mIconView != null) {
-            mIconView.setDrawable(icon);
-        }
-    }
-
-    @Override
-    public void setDrawableSize(int iconWidth, int iconHeight) {
-        if (mIconView != null) {
-            mIconView.setDrawableSize(iconWidth, iconHeight);
-        }
-    }
-
-    /**
-     * Sets the maximum width of this Icon Menu. This is usually used when space is limited for
-     * split screen.
-     */
-    public void setMaxWidth(int maxWidth) {
-        // Width showing only the app icon and arrow. Max width should not be set to less than this.
-        int minimumMaxWidth = mIconViewMarginStart + mAppIconSize + mArrowSize + mArrowMarginEnd;
-        mMaxWidth = Math.max(maxWidth, minimumMaxWidth);
-    }
-
-    @Override
-    public void setIconOrientation(RecentsOrientedState orientationState, boolean isGridTask) {
-        RecentsPagedOrientationHandler orientationHandler =
-                orientationState.getOrientationHandler();
-        // Layout params for anchor view
-        LayoutParams anchorLayoutParams = (LayoutParams) mMenuAnchorView.getLayoutParams();
-        anchorLayoutParams.topMargin = mExpandedMenuDefaultHeight + mMenuToChipGap;
-        mMenuAnchorView.setLayoutParams(anchorLayoutParams);
-
-        // Layout Params for the Menu View (this)
-        LayoutParams iconMenuParams = (LayoutParams) getLayoutParams();
-        iconMenuParams.width = mExpandedMenuDefaultWidth;
-        iconMenuParams.height = mExpandedMenuDefaultHeight;
-        orientationHandler.setIconAppChipMenuParams(this, iconMenuParams, mIconMenuMarginTopStart,
-                mIconMenuMarginTopStart);
-        setLayoutParams(iconMenuParams);
-
-        // Layout params for the background
-        Rect collapsedBackgroundBounds = getCollapsedBackgroundLtrBounds();
-        mBackgroundRelativeLtrLocation.set(collapsedBackgroundBounds);
-        setOutlineProvider(new ViewOutlineProvider() {
-            final Rect mRtlAppliedOutlineBounds = new Rect();
-            @Override
-            public void getOutline(View view, Outline outline) {
-                mRtlAppliedOutlineBounds.set(mBackgroundRelativeLtrLocation);
-                if (isLayoutRtl()) {
-                    int width = getWidth();
-                    mRtlAppliedOutlineBounds.left = width - mBackgroundRelativeLtrLocation.right;
-                    mRtlAppliedOutlineBounds.right = width - mBackgroundRelativeLtrLocation.left;
-                }
-                outline.setRoundRect(
-                        mRtlAppliedOutlineBounds, mRtlAppliedOutlineBounds.height() / 2f);
-            }
-        });
-
-        // Layout Params for the Icon View
-        LayoutParams iconParams = (LayoutParams) mIconView.getLayoutParams();
-        int iconMarginStartRelativeToParent = mIconViewMarginStart + mBackgroundMarginTopStart;
-        orientationHandler.setIconAppChipChildrenParams(
-                iconParams, iconMarginStartRelativeToParent);
-
-        mIconView.setLayoutParams(iconParams);
-        mIconView.setDrawableSize(mAppIconSize, mAppIconSize);
-
-        // Layout Params for the collapsed Icon Text View
-        int textMarginStart =
-                iconMarginStartRelativeToParent + mAppIconSize + mAppNameHorizontalMargin;
-        LayoutParams iconTextCollapsedParams =
-                (LayoutParams) mIconTextCollapsedView.getLayoutParams();
-        orientationHandler.setIconAppChipChildrenParams(iconTextCollapsedParams, textMarginStart);
-        int collapsedTextWidth = collapsedBackgroundBounds.width() - mIconViewMarginStart
-                - mAppIconSize - mArrowSize - mAppNameHorizontalMargin - mArrowMarginEnd;
-        iconTextCollapsedParams.width = collapsedTextWidth;
-        mIconTextCollapsedView.setLayoutParams(iconTextCollapsedParams);
-        mIconTextCollapsedView.setAlpha(1f);
-
-        // Layout Params for the expanded Icon Text View
-        LayoutParams iconTextExpandedParams =
-                (LayoutParams) mIconTextExpandedView.getLayoutParams();
-        orientationHandler.setIconAppChipChildrenParams(iconTextExpandedParams, textMarginStart);
-        mIconTextExpandedView.setLayoutParams(iconTextExpandedParams);
-        mIconTextExpandedView.setAlpha(0f);
-        mIconTextExpandedView.setRevealClip(true, 0, mAppIconSize / 2f, collapsedTextWidth);
-
-        // Layout Params for the Icon Arrow View
-        LayoutParams iconArrowParams = (LayoutParams) mIconArrowView.getLayoutParams();
-        int arrowMarginStart = collapsedBackgroundBounds.right - mArrowMarginEnd - mArrowSize;
-        orientationHandler.setIconAppChipChildrenParams(iconArrowParams, arrowMarginStart);
-        mIconArrowView.setPivotY(iconArrowParams.height / 2f);
-        mIconArrowView.setLayoutParams(iconArrowParams);
-
-        // This method is called twice sometimes (like when rotating split tasks). It is called
-        // once before onMeasure and onLayout, and again after onMeasure but before onLayout with
-        // a new width. This happens because we update widths on rotation and on measure of
-        // grouped task views. Calling requestLayout() does not guarantee a call to onMeasure if
-        // it has just measured, so we explicitly call it here.
-        measure(MeasureSpec.makeMeasureSpec(getLayoutParams().width, MeasureSpec.EXACTLY),
-                MeasureSpec.makeMeasureSpec(getLayoutParams().height, MeasureSpec.EXACTLY));
-    }
-
-    @Override
-    public void setIconColorTint(int color, float amount) {
-        // RecentsView's COLOR_TINT animates between 0 and 0.5f, we want to hide the app chip menu.
-        float colorTintAlpha = Utilities.mapToRange(amount, 0f, 0.5f, 1f, 0f, LINEAR);
-        mMultiValueAlpha.get(INDEX_COLOR_FILTER_ALPHA).setValue(colorTintAlpha);
-    }
-
-    @Override
-    public void setContentAlpha(float alpha) {
-        mMultiValueAlpha.get(INDEX_CONTENT_ALPHA).setValue(alpha);
-    }
-
-    @Override
-    public void setModalAlpha(float alpha) {
-        mMultiValueAlpha.get(INDEX_MODAL_ALPHA).setValue(alpha);
-    }
-
-    @Override
-    public int getDrawableWidth() {
-        return mIconView == null ? 0 : mIconView.getDrawableWidth();
-    }
-
-    @Override
-    public int getDrawableHeight() {
-        return mIconView == null ? 0 : mIconView.getDrawableHeight();
-    }
-
-    protected void revealAnim(boolean isRevealing) {
-        cancelInProgressAnimations();
-        final Rect collapsedBackgroundBounds = getCollapsedBackgroundLtrBounds();
-        final Rect expandedBackgroundBounds = getExpandedBackgroundLtrBounds();
-        final Rect initialBackground = new Rect(mBackgroundRelativeLtrLocation);
-        mAnimator = new AnimatorSet();
-
-        if (isRevealing) {
-            boolean isRtl = isLayoutRtl();
-            bringToFront();
-            // Clip expanded text with reveal animation so it doesn't go beyond the edge of the menu
-            Animator expandedTextRevealAnim = ViewAnimationUtils.createCircularReveal(
-                    mIconTextExpandedView, 0, mIconTextExpandedView.getHeight() / 2,
-                    mIconTextCollapsedView.getWidth(), mIconTextExpandedView.getWidth());
-            // Animate background clipping
-            ValueAnimator backgroundAnimator = ValueAnimator.ofObject(
-                    mBackgroundAnimationRectEvaluator,
-                    initialBackground,
-                    expandedBackgroundBounds);
-            backgroundAnimator.addUpdateListener(valueAnimator -> invalidateOutline());
-
-            float iconViewScaling = mIconViewDrawableExpandedSize / (float) mAppIconSize;
-            float arrowTranslationX =
-                    expandedBackgroundBounds.right - collapsedBackgroundBounds.right;
-            float iconCenterToTextCollapsed = mAppIconSize / 2f + mAppNameHorizontalMargin;
-            float iconCenterToTextExpanded =
-                    mIconViewDrawableExpandedSize / 2f + mAppNameHorizontalMargin;
-            float textTranslationX = iconCenterToTextExpanded - iconCenterToTextCollapsed;
-
-            float textTranslationXWithRtl = isRtl ? -textTranslationX : textTranslationX;
-            float arrowTranslationWithRtl = isRtl ? -arrowTranslationX : arrowTranslationX;
-
-            mAnimator.playTogether(
-                    expandedTextRevealAnim,
-                    backgroundAnimator,
-                    ObjectAnimator.ofFloat(mIconView, SCALE_X, iconViewScaling),
-                    ObjectAnimator.ofFloat(mIconView, SCALE_Y, iconViewScaling),
-                    ObjectAnimator.ofFloat(mIconTextCollapsedView, TRANSLATION_X,
-                            textTranslationXWithRtl),
-                    ObjectAnimator.ofFloat(mIconTextExpandedView, TRANSLATION_X,
-                            textTranslationXWithRtl),
-                    ObjectAnimator.ofFloat(mIconTextCollapsedView, ALPHA, 0),
-                    ObjectAnimator.ofFloat(mIconTextExpandedView, ALPHA, 1),
-                    ObjectAnimator.ofFloat(mIconArrowView, TRANSLATION_X, arrowTranslationWithRtl),
-                    ObjectAnimator.ofFloat(mIconArrowView, SCALE_Y, -1));
-            mAnimator.setDuration(MENU_BACKGROUND_REVEAL_DURATION);
-        } else {
-            // Clip expanded text with reveal animation so it doesn't go beyond the edge of the menu
-            Animator expandedTextClipAnim = ViewAnimationUtils.createCircularReveal(
-                    mIconTextExpandedView, 0, mIconTextExpandedView.getHeight() / 2,
-                    mIconTextExpandedView.getWidth(), mIconTextCollapsedView.getWidth());
-
-            // Animate background clipping
-            ValueAnimator backgroundAnimator = ValueAnimator.ofObject(
-                    mBackgroundAnimationRectEvaluator,
-                    initialBackground,
-                    collapsedBackgroundBounds);
-            backgroundAnimator.addUpdateListener(valueAnimator -> invalidateOutline());
-
-            mAnimator.playTogether(
-                    expandedTextClipAnim,
-                    backgroundAnimator,
-                    ObjectAnimator.ofFloat(mIconView, SCALE_PROPERTY, 1),
-                    ObjectAnimator.ofFloat(mIconTextCollapsedView, TRANSLATION_X, 0),
-                    ObjectAnimator.ofFloat(mIconTextExpandedView, TRANSLATION_X, 0),
-                    ObjectAnimator.ofFloat(mIconTextCollapsedView, ALPHA, 1),
-                    ObjectAnimator.ofFloat(mIconTextExpandedView, ALPHA, 0),
-                    ObjectAnimator.ofFloat(mIconArrowView, TRANSLATION_X, 0),
-                    ObjectAnimator.ofFloat(mIconArrowView, SCALE_Y, 1));
-            mAnimator.setDuration(MENU_BACKGROUND_HIDE_DURATION);
-        }
-
-        mAnimator.setInterpolator(EMPHASIZED);
-        mAnimator.start();
-    }
-
-    private Rect getCollapsedBackgroundLtrBounds() {
-        Rect bounds = new Rect(
-                0,
-                0,
-                Math.min(mMaxWidth, mCollapsedMenuDefaultWidth),
-                mCollapsedMenuDefaultHeight);
-        bounds.offset(mBackgroundMarginTopStart, mBackgroundMarginTopStart);
-        return bounds;
-    }
-
-    private Rect getExpandedBackgroundLtrBounds() {
-        return new Rect(0, 0, mExpandedMenuDefaultWidth, mExpandedMenuDefaultHeight);
-    }
-
-    private void cancelInProgressAnimations() {
-        // We null the `AnimatorSet` because it holds references to the `Animators` which aren't
-        // expecting to be mutable and will cause a crash if they are re-used.
-        if (mAnimator != null && mAnimator.isStarted()) {
-            mAnimator.cancel();
-            mAnimator = null;
-        }
-    }
-
-    @Override
-    public View asView() {
-        return this;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
new file mode 100644
index 0000000..c20aa11
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
@@ -0,0 +1,438 @@
+/*
+ * 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.quickstep.views
+
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.animation.RectEvaluator
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Outline
+import android.graphics.Rect
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewAnimationUtils
+import android.view.ViewOutlineProvider
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.TextView
+import com.android.app.animation.Interpolators
+import com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY
+import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X
+import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y
+import com.android.launcher3.R
+import com.android.launcher3.Utilities
+import com.android.launcher3.util.MultiPropertyFactory
+import com.android.launcher3.util.MultiPropertyFactory.FloatBiFunction
+import com.android.launcher3.util.MultiValueAlpha
+import com.android.quickstep.util.RecentsOrientedState
+import kotlin.math.max
+import kotlin.math.min
+
+/** An icon app menu view which can be used in place of an IconView in overview TaskViews. */
+class IconAppChipView
+@JvmOverloads
+constructor(
+    context: Context,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0,
+) : FrameLayout(context, attrs, defStyleAttr, defStyleRes), TaskViewIcon {
+
+    private var iconView: IconView? = null
+    private var iconArrowView: ImageView? = null
+    private var menuAnchorView: View? = null
+    // Two textview so we can ellipsize the collapsed view and crossfade on expand to the full name.
+    private var iconTextCollapsedView: TextView? = null
+    private var iconTextExpandedView: TextView? = null
+
+    private val backgroundRelativeLtrLocation = Rect()
+    private val backgroundAnimationRectEvaluator = RectEvaluator(backgroundRelativeLtrLocation)
+
+    // Menu dimensions
+    private val collapsedMenuDefaultWidth: Int =
+        resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_collapsed_width)
+    private val expandedMenuDefaultWidth: Int =
+        resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_expanded_width)
+    private val collapsedMenuDefaultHeight =
+        resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_collapsed_height)
+    private val expandedMenuDefaultHeight =
+        resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_expanded_height)
+    private val iconMenuMarginTopStart =
+        resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_expanded_top_start_margin)
+    private val menuToChipGap: Int =
+        resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_expanded_gap)
+
+    // Background dimensions
+    private val backgroundMarginTopStart: Int =
+        resources.getDimensionPixelSize(
+            R.dimen.task_thumbnail_icon_menu_background_margin_top_start
+        )
+
+    // Contents dimensions
+    private val appNameHorizontalMargin =
+        resources.getDimensionPixelSize(
+            R.dimen.task_thumbnail_icon_menu_app_name_margin_horizontal_collapsed
+        )
+    private val arrowMarginEnd =
+        resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_arrow_margin)
+    private val iconViewMarginStart =
+        resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_view_start_margin)
+    private val appIconSize =
+        resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_app_icon_collapsed_size)
+    private val arrowSize =
+        resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_arrow_size)
+    private val iconViewDrawableExpandedSize =
+        resources.getDimensionPixelSize(R.dimen.task_thumbnail_icon_menu_app_icon_expanded_size)
+
+    private var animator: AnimatorSet? = null
+
+    private val multiValueAlpha: MultiValueAlpha =
+        MultiValueAlpha(this, NUM_ALPHA_CHANNELS).apply { setUpdateVisibility(true) }
+
+    private val viewTranslationX: MultiPropertyFactory<View> =
+        MultiPropertyFactory(this, VIEW_TRANSLATE_X, INDEX_COUNT_TRANSLATION, SUM_AGGREGATOR)
+
+    private val viewTranslationY: MultiPropertyFactory<View> =
+        MultiPropertyFactory(this, VIEW_TRANSLATE_Y, INDEX_COUNT_TRANSLATION, SUM_AGGREGATOR)
+
+    var maxWidth = Int.MAX_VALUE
+        /**
+         * Sets the maximum width of this Icon Menu. This is usually used when space is limited for
+         * split screen.
+         */
+        set(value) {
+            // Width showing only the app icon and arrow. Max width should not be set to less than
+            // this.
+            val minMaxWidth = iconViewMarginStart + appIconSize + arrowSize + arrowMarginEnd
+            field = max(value, minMaxWidth)
+        }
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        iconView = findViewById(R.id.icon_view)
+        iconTextCollapsedView = findViewById(R.id.icon_text_collapsed)
+        iconTextExpandedView = findViewById(R.id.icon_text_expanded)
+        iconArrowView = findViewById(R.id.icon_arrow)
+        menuAnchorView = findViewById(R.id.icon_view_menu_anchor)
+    }
+
+    override fun setText(text: CharSequence?) {
+        iconTextCollapsedView?.text = text
+        iconTextExpandedView?.text = text
+    }
+
+    override fun getDrawable(): Drawable? = iconView?.drawable
+
+    override fun setDrawable(icon: Drawable?) {
+        iconView?.drawable = icon
+    }
+
+    override fun setDrawableSize(iconWidth: Int, iconHeight: Int) {
+        iconView?.setDrawableSize(iconWidth, iconHeight)
+    }
+
+    override fun setIconOrientation(orientationState: RecentsOrientedState, isGridTask: Boolean) {
+        val orientationHandler = orientationState.orientationHandler
+        // Layout params for anchor view
+        val anchorLayoutParams = menuAnchorView!!.layoutParams as LayoutParams
+        anchorLayoutParams.topMargin = expandedMenuDefaultHeight + menuToChipGap
+        menuAnchorView!!.layoutParams = anchorLayoutParams
+
+        // Layout Params for the Menu View (this)
+        val iconMenuParams = layoutParams as LayoutParams
+        iconMenuParams.width = expandedMenuDefaultWidth
+        iconMenuParams.height = expandedMenuDefaultHeight
+        orientationHandler.setIconAppChipMenuParams(
+            this,
+            iconMenuParams,
+            iconMenuMarginTopStart,
+            iconMenuMarginTopStart,
+        )
+        layoutParams = iconMenuParams
+
+        // Layout params for the background
+        val collapsedBackgroundBounds = getCollapsedBackgroundLtrBounds()
+        backgroundRelativeLtrLocation.set(collapsedBackgroundBounds)
+        outlineProvider =
+            object : ViewOutlineProvider() {
+                val mRtlAppliedOutlineBounds: Rect = Rect()
+
+                override fun getOutline(view: View, outline: Outline) {
+                    mRtlAppliedOutlineBounds.set(backgroundRelativeLtrLocation)
+                    if (isLayoutRtl) {
+                        val width = width
+                        mRtlAppliedOutlineBounds.left = width - backgroundRelativeLtrLocation.right
+                        mRtlAppliedOutlineBounds.right = width - backgroundRelativeLtrLocation.left
+                    }
+                    outline.setRoundRect(
+                        mRtlAppliedOutlineBounds,
+                        mRtlAppliedOutlineBounds.height() / 2f,
+                    )
+                }
+            }
+
+        // Layout Params for the Icon View
+        val iconParams = iconView!!.layoutParams as LayoutParams
+        val iconMarginStartRelativeToParent = iconViewMarginStart + backgroundMarginTopStart
+        orientationHandler.setIconAppChipChildrenParams(iconParams, iconMarginStartRelativeToParent)
+
+        iconView!!.layoutParams = iconParams
+        iconView!!.setDrawableSize(appIconSize, appIconSize)
+
+        // Layout Params for the collapsed Icon Text View
+        val textMarginStart =
+            iconMarginStartRelativeToParent + appIconSize + appNameHorizontalMargin
+        val iconTextCollapsedParams = iconTextCollapsedView!!.layoutParams as LayoutParams
+        orientationHandler.setIconAppChipChildrenParams(iconTextCollapsedParams, textMarginStart)
+        val collapsedTextWidth =
+            (collapsedBackgroundBounds.width() -
+                iconViewMarginStart -
+                appIconSize -
+                arrowSize -
+                appNameHorizontalMargin -
+                arrowMarginEnd)
+        iconTextCollapsedParams.width = collapsedTextWidth
+        iconTextCollapsedView!!.layoutParams = iconTextCollapsedParams
+        iconTextCollapsedView!!.alpha = 1f
+
+        // Layout Params for the expanded Icon Text View
+        val iconTextExpandedParams = iconTextExpandedView!!.layoutParams as LayoutParams
+        orientationHandler.setIconAppChipChildrenParams(iconTextExpandedParams, textMarginStart)
+        iconTextExpandedView!!.layoutParams = iconTextExpandedParams
+        iconTextExpandedView!!.alpha = 0f
+        iconTextExpandedView!!.setRevealClip(
+            true,
+            0f,
+            appIconSize / 2f,
+            collapsedTextWidth.toFloat(),
+        )
+
+        // Layout Params for the Icon Arrow View
+        val iconArrowParams = iconArrowView!!.layoutParams as LayoutParams
+        val arrowMarginStart = collapsedBackgroundBounds.right - arrowMarginEnd - arrowSize
+        orientationHandler.setIconAppChipChildrenParams(iconArrowParams, arrowMarginStart)
+        iconArrowView!!.pivotY = iconArrowParams.height / 2f
+        iconArrowView!!.layoutParams = iconArrowParams
+
+        // This method is called twice sometimes (like when rotating split tasks). It is called
+        // once before onMeasure and onLayout, and again after onMeasure but before onLayout with
+        // a new width. This happens because we update widths on rotation and on measure of
+        // grouped task views. Calling requestLayout() does not guarantee a call to onMeasure if
+        // it has just measured, so we explicitly call it here.
+        measure(
+            MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY),
+            MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY),
+        )
+    }
+
+    override fun setIconColorTint(color: Int, amount: Float) {
+        // RecentsView's COLOR_TINT animates between 0 and 0.5f, we want to hide the app chip menu.
+        val colorTintAlpha = Utilities.mapToRange(amount, 0f, 0.5f, 1f, 0f, Interpolators.LINEAR)
+        multiValueAlpha[INDEX_COLOR_FILTER_ALPHA].value = colorTintAlpha
+    }
+
+    override fun setContentAlpha(alpha: Float) {
+        multiValueAlpha[INDEX_CONTENT_ALPHA].value = alpha
+    }
+
+    override fun setModalAlpha(alpha: Float) {
+        multiValueAlpha[INDEX_MODAL_ALPHA].value = alpha
+    }
+
+    override fun setFlexSplitAlpha(alpha: Float) {
+        multiValueAlpha[INDEX_MINIMUM_RATIO_ALPHA].value = alpha
+    }
+
+    override fun getDrawableWidth(): Int = iconView?.drawableWidth ?: 0
+
+    override fun getDrawableHeight(): Int = iconView?.drawableHeight ?: 0
+
+    /** Gets the view split x-axis translation */
+    fun getSplitTranslationX(): MultiPropertyFactory<View>.MultiProperty =
+        viewTranslationX.get(INDEX_SPLIT_TRANSLATION)
+
+    /**
+     * Sets the view split x-axis translation
+     *
+     * @param value x-axis translation
+     */
+    fun setSplitTranslationX(value: Float) {
+        getSplitTranslationX().value = value
+    }
+
+    /** Gets the view split y-axis translation */
+    fun getSplitTranslationY(): MultiPropertyFactory<View>.MultiProperty =
+        viewTranslationY[INDEX_SPLIT_TRANSLATION]
+
+    /**
+     * Sets the view split y-axis translation
+     *
+     * @param value y-axis translation
+     */
+    fun setSplitTranslationY(value: Float) {
+        getSplitTranslationY().value = value
+    }
+
+    /** Gets the menu x-axis translation for split task */
+    fun getMenuTranslationX(): MultiPropertyFactory<View>.MultiProperty =
+        viewTranslationX[INDEX_MENU_TRANSLATION]
+
+    /** Gets the menu y-axis translation for split task */
+    fun getMenuTranslationY(): MultiPropertyFactory<View>.MultiProperty =
+        viewTranslationY[INDEX_MENU_TRANSLATION]
+
+    internal fun revealAnim(isRevealing: Boolean, animated: Boolean = true) {
+        cancelInProgressAnimations()
+        val collapsedBackgroundBounds = getCollapsedBackgroundLtrBounds()
+        val expandedBackgroundBounds = getExpandedBackgroundLtrBounds()
+        val initialBackground = Rect(backgroundRelativeLtrLocation)
+        animator = AnimatorSet()
+
+        if (isRevealing) {
+            val isRtl = isLayoutRtl
+            bringToFront()
+            // Clip expanded text with reveal animation so it doesn't go beyond the edge of the menu
+            val expandedTextRevealAnim =
+                ViewAnimationUtils.createCircularReveal(
+                    iconTextExpandedView,
+                    0,
+                    iconTextExpandedView!!.height / 2,
+                    iconTextCollapsedView!!.width.toFloat(),
+                    iconTextExpandedView!!.width.toFloat(),
+                )
+            // Animate background clipping
+            val backgroundAnimator =
+                ValueAnimator.ofObject(
+                    backgroundAnimationRectEvaluator,
+                    initialBackground,
+                    expandedBackgroundBounds,
+                )
+            backgroundAnimator.addUpdateListener { invalidateOutline() }
+
+            val iconViewScaling = iconViewDrawableExpandedSize / appIconSize.toFloat()
+            val arrowTranslationX =
+                (expandedBackgroundBounds.right - collapsedBackgroundBounds.right).toFloat()
+            val iconCenterToTextCollapsed = appIconSize / 2f + appNameHorizontalMargin
+            val iconCenterToTextExpanded =
+                iconViewDrawableExpandedSize / 2f + appNameHorizontalMargin
+            val textTranslationX = iconCenterToTextExpanded - iconCenterToTextCollapsed
+
+            val textTranslationXWithRtl = if (isRtl) -textTranslationX else textTranslationX
+            val arrowTranslationWithRtl = if (isRtl) -arrowTranslationX else arrowTranslationX
+
+            animator!!.playTogether(
+                expandedTextRevealAnim,
+                backgroundAnimator,
+                ObjectAnimator.ofFloat(iconView, SCALE_X, iconViewScaling),
+                ObjectAnimator.ofFloat(iconView, SCALE_Y, iconViewScaling),
+                ObjectAnimator.ofFloat(
+                    iconTextCollapsedView,
+                    TRANSLATION_X,
+                    textTranslationXWithRtl,
+                ),
+                ObjectAnimator.ofFloat(
+                    iconTextExpandedView,
+                    TRANSLATION_X,
+                    textTranslationXWithRtl,
+                ),
+                ObjectAnimator.ofFloat(iconTextCollapsedView, ALPHA, 0f),
+                ObjectAnimator.ofFloat(iconTextExpandedView, ALPHA, 1f),
+                ObjectAnimator.ofFloat(iconArrowView, TRANSLATION_X, arrowTranslationWithRtl),
+                ObjectAnimator.ofFloat(iconArrowView, SCALE_Y, -1f),
+            )
+            animator!!.setDuration(MENU_BACKGROUND_REVEAL_DURATION.toLong())
+        } else {
+            // Clip expanded text with reveal animation so it doesn't go beyond the edge of the menu
+            val expandedTextClipAnim =
+                ViewAnimationUtils.createCircularReveal(
+                    iconTextExpandedView,
+                    0,
+                    iconTextExpandedView!!.height / 2,
+                    iconTextExpandedView!!.width.toFloat(),
+                    iconTextCollapsedView!!.width.toFloat(),
+                )
+
+            // Animate background clipping
+            val backgroundAnimator =
+                ValueAnimator.ofObject(
+                    backgroundAnimationRectEvaluator,
+                    initialBackground,
+                    collapsedBackgroundBounds,
+                )
+            backgroundAnimator.addUpdateListener { valueAnimator: ValueAnimator? ->
+                invalidateOutline()
+            }
+
+            animator!!.playTogether(
+                expandedTextClipAnim,
+                backgroundAnimator,
+                ObjectAnimator.ofFloat(iconView, SCALE_PROPERTY, 1f),
+                ObjectAnimator.ofFloat(iconTextCollapsedView, TRANSLATION_X, 0f),
+                ObjectAnimator.ofFloat(iconTextExpandedView, TRANSLATION_X, 0f),
+                ObjectAnimator.ofFloat(iconTextCollapsedView, ALPHA, 1f),
+                ObjectAnimator.ofFloat(iconTextExpandedView, ALPHA, 0f),
+                ObjectAnimator.ofFloat(iconArrowView, TRANSLATION_X, 0f),
+                ObjectAnimator.ofFloat(iconArrowView, SCALE_Y, 1f),
+            )
+            animator!!.setDuration(MENU_BACKGROUND_HIDE_DURATION.toLong())
+        }
+
+        if (!animated) animator!!.duration = 0
+        animator!!.interpolator = Interpolators.EMPHASIZED
+        animator!!.start()
+    }
+
+    private fun getCollapsedBackgroundLtrBounds(): Rect {
+        val bounds =
+            Rect(0, 0, min(maxWidth, collapsedMenuDefaultWidth), collapsedMenuDefaultHeight)
+        bounds.offset(backgroundMarginTopStart, backgroundMarginTopStart)
+        return bounds
+    }
+
+    private fun getExpandedBackgroundLtrBounds() =
+        Rect(0, 0, expandedMenuDefaultWidth, expandedMenuDefaultHeight)
+
+    private fun cancelInProgressAnimations() {
+        // We null the `AnimatorSet` because it holds references to the `Animators` which aren't
+        // expecting to be mutable and will cause a crash if they are re-used.
+        if (animator != null && animator!!.isStarted) {
+            animator!!.cancel()
+            animator = null
+        }
+    }
+
+    override fun asView(): View = this
+
+    private companion object {
+        private val SUM_AGGREGATOR = FloatBiFunction { a: Float, b: Float -> a + b }
+
+        private const val MENU_BACKGROUND_REVEAL_DURATION = 417
+        private const val MENU_BACKGROUND_HIDE_DURATION = 333
+
+        private const val NUM_ALPHA_CHANNELS = 4
+        private const val INDEX_CONTENT_ALPHA = 0
+        private const val INDEX_COLOR_FILTER_ALPHA = 1
+        private const val INDEX_MODAL_ALPHA = 2
+        /** Used to hide the app chip for 90:10 flex split. */
+        private const val INDEX_MINIMUM_RATIO_ALPHA = 3
+
+        private const val INDEX_SPLIT_TRANSLATION = 0
+        private const val INDEX_MENU_TRANSLATION = 1
+        private const val INDEX_COUNT_TRANSLATION = 2
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/IconView.kt b/quickstep/src/com/android/quickstep/views/IconView.kt
index 583207f..cb69b22 100644
--- a/quickstep/src/com/android/quickstep/views/IconView.kt
+++ b/quickstep/src/com/android/quickstep/views/IconView.kt
@@ -25,10 +25,13 @@
 import android.widget.FrameLayout
 import androidx.core.view.updateLayoutParams
 import com.android.launcher3.DeviceProfile
+import com.android.launcher3.Flags
 import com.android.launcher3.Utilities
+import com.android.launcher3.util.MSDLPlayerWrapper
 import com.android.launcher3.util.MultiValueAlpha
 import com.android.launcher3.views.ActivityContext
 import com.android.quickstep.util.RecentsOrientedState
+import com.google.android.msdl.data.model.MSDLToken
 
 /**
  * A view which draws a drawable stretched to fit its size. Unlike ImageView, it avoids relayout
@@ -39,26 +42,44 @@
     private var drawable: Drawable? = null
     private var drawableWidth = 0
     private var drawableHeight = 0
+    private var msdlPlayerWrapper: MSDLPlayerWrapper? = null
 
-    constructor(context: Context) : super(context)
+    constructor(context: Context) : super(context) {
+        setUpHaptics()
+    }
 
-    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
+    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
+        setUpHaptics()
+    }
 
     constructor(
         context: Context,
         attrs: AttributeSet?,
         defStyleAttr: Int,
-    ) : super(context, attrs, defStyleAttr)
+    ) : super(context, attrs, defStyleAttr) {
+        setUpHaptics()
+    }
 
     init {
         multiValueAlpha.setUpdateVisibility(true)
     }
 
+    private fun setUpHaptics() {
+        msdlPlayerWrapper = MSDLPlayerWrapper.INSTANCE.get(context)
+        // Haptics are handled by the MSDLPlayerWrapper
+        isHapticFeedbackEnabled = !Flags.msdlFeedback() || msdlPlayerWrapper == null
+    }
+
+    override fun setOnLongClickListener(l: OnLongClickListener?) {
+        super.setOnLongClickListener(l?.withFeedback())
+    }
+
     /** Sets a [Drawable] to be displayed. */
     override fun setDrawable(d: Drawable?) {
         drawable?.callback = null
 
-        drawable = d
+        // Copy drawable so that mutations below do not affect other users of the drawable
+        drawable = d?.constantState?.newDrawable()?.mutate()
         drawable?.let {
             it.callback = this
             setDrawableSizeInternal(width, height)
@@ -117,6 +138,10 @@
         multiValueAlpha[INDEX_MODAL_ALPHA].setValue(alpha)
     }
 
+    override fun setFlexSplitAlpha(alpha: Float) {
+        multiValueAlpha[INDEX_FLEX_SPLIT_ALPHA].setValue(alpha)
+    }
+
     /**
      * Set the tint color of the icon, useful for scrimming or dimming.
      *
@@ -136,7 +161,7 @@
             taskIconMargin = deviceProfile.overviewTaskMarginPx,
             taskIconHeight = deviceProfile.overviewTaskIconSizePx,
             thumbnailTopMargin = deviceProfile.overviewTaskThumbnailTopMarginPx,
-            isRtl = layoutDirection == LAYOUT_DIRECTION_RTL
+            isRtl = layoutDirection == LAYOUT_DIRECTION_RTL,
         )
         updateLayoutParams<FrameLayout.LayoutParams> {
             height = deviceProfile.overviewTaskIconSizePx
@@ -151,9 +176,20 @@
 
     override fun asView(): View = this
 
+    private fun OnLongClickListener.withFeedback(): OnLongClickListener {
+        val delegate = this
+        return OnLongClickListener { v: View ->
+            if (Flags.msdlFeedback()) {
+                msdlPlayerWrapper?.playToken(MSDLToken.LONG_PRESS)
+            }
+            delegate.onLongClick(v)
+        }
+    }
+
     companion object {
-        private const val NUM_ALPHA_CHANNELS = 2
+        private const val NUM_ALPHA_CHANNELS = 3
         private const val INDEX_CONTENT_ALPHA = 0
         private const val INDEX_MODAL_ALPHA = 1
+        private const val INDEX_FLEX_SPLIT_ALPHA = 2
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index bbb8cc8..0f1c294 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -36,7 +36,6 @@
 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;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.statehandlers.DepthController;
@@ -47,9 +46,9 @@
 import com.android.launcher3.util.PendingSplitSelectInfo;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
+import com.android.quickstep.BaseContainerInterface;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.LauncherActivityInterface;
-import com.android.quickstep.RotationTouchHelper;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.util.AnimUtils;
 import com.android.quickstep.util.SplitSelectStateController;
@@ -73,7 +72,7 @@
     }
 
     public LauncherRecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr, LauncherActivityInterface.INSTANCE);
+        super(context, attrs, defStyleAttr);
         getStateManager().addStateListener(this);
     }
 
@@ -127,9 +126,11 @@
         // If Launcher needs to return to split select state, do it now, after the icon has updated.
         if (mContainer.hasPendingSplitSelectInfo()) {
             PendingSplitSelectInfo recoveryData = mContainer.getPendingSplitSelectInfo();
-            if (recoveryData.getStagedTaskId() == taskId) {
+            TaskContainer taskContainer;
+            if (recoveryData != null && recoveryData.getStagedTaskId() == taskId && (taskContainer =
+                    mUtils.getTaskContainerById(taskId)) != null) {
                 initiateSplitSelect(
-                        getTaskViewByTaskId(recoveryData.getStagedTaskId()),
+                        taskContainer,
                         recoveryData.getStagePosition(), recoveryData.getSource()
                 );
                 mContainer.finishSplitSelectRecovery();
@@ -163,13 +164,12 @@
         }
 
         setFreezeViewVisibility(true);
-        if (mContainer.getDesktopVisibilityController() != null) {
-            mContainer.getDesktopVisibilityController().onLauncherStateChanged(toState);
-        }
     }
 
     @Override
     public void onStateTransitionComplete(LauncherState finalState) {
+        DesktopVisibilityController.INSTANCE.get(mContainer).onLauncherStateChanged(finalState);
+
         if (!finalState.isRecentsViewVisible) {
             // Clean-up logic that occurs when recents is no longer in use/visible.
             reset();
@@ -225,6 +225,11 @@
     }
 
     @Override
+    protected BaseContainerInterface<LauncherState, ?> getContainerInterface(int displayId) {
+        return LauncherActivityInterface.INSTANCE;
+    }
+
+    @Override
     protected void onDismissAnimationEnds() {
         super.onDismissAnimationEnds();
         if (mContainer.isInState(OVERVIEW_SPLIT_SELECT)) {
@@ -236,10 +241,10 @@
     }
 
     @Override
-    public void initiateSplitSelect(TaskView taskView,
+    public void initiateSplitSelect(TaskContainer taskContainer,
             @SplitConfigurationOptions.StagePosition int stagePosition,
             StatsLogManager.EventEnum splitEvent) {
-        super.initiateSplitSelect(taskView, stagePosition, splitEvent);
+        super.initiateSplitSelect(taskContainer, stagePosition, splitEvent);
         getStateManager().goToState(LauncherState.OVERVIEW_SPLIT_SELECT);
     }
 
@@ -251,45 +256,33 @@
 
     @Override
     public boolean canLaunchFullscreenTask() {
-        if (FeatureFlags.enableSplitContextually()) {
-            return !mSplitSelectStateController.isSplitSelectActive();
-        } else {
-            return !mContainer.isInState(OVERVIEW_SPLIT_SELECT);
-        }
+        return !mSplitSelectStateController.isSplitSelectActive();
     }
 
     @Override
-    public void onGestureAnimationStart(Task[] runningTasks,
-            RotationTouchHelper rotationTouchHelper) {
-        super.onGestureAnimationStart(runningTasks, rotationTouchHelper);
-        DesktopVisibilityController desktopVisibilityController =
-                mContainer.getDesktopVisibilityController();
-        if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
-                && desktopVisibilityController != null) {
+    public void onGestureAnimationStart(Task[] runningTasks) {
+        super.onGestureAnimationStart(runningTasks);
+        if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) {
             // TODO: b/333533253 - Remove after flag rollout
-            desktopVisibilityController.setRecentsGestureStart();
+            DesktopVisibilityController.INSTANCE.get(mContainer).setRecentsGestureStart();
         }
     }
 
     @Override
     public void onGestureAnimationEnd() {
-        DesktopVisibilityController desktopVisibilityController =
-                mContainer.getDesktopVisibilityController();
+        final DesktopVisibilityController desktopVisibilityController =
+                DesktopVisibilityController.INSTANCE.get(mContainer);
         boolean showDesktopApps = false;
-        GestureState.GestureEndTarget endTarget = null;
-        if (desktopVisibilityController != null) {
-            desktopVisibilityController = mContainer.getDesktopVisibilityController();
-            endTarget = mCurrentGestureEndTarget;
-            if (endTarget == GestureState.GestureEndTarget.LAST_TASK
-                    && desktopVisibilityController.areDesktopTasksVisible()) {
-                // Recents gesture was cancelled and we are returning to the previous task.
-                // After super class has handled clean up, show desktop apps on top again
-                showDesktopApps = true;
-            }
+        GestureState.GestureEndTarget endTarget = mCurrentGestureEndTarget;
+        if (endTarget == GestureState.GestureEndTarget.LAST_TASK
+                && desktopVisibilityController.isInDesktopModeAndNotInOverview(
+                        mContainer.getDisplayId())) {
+            // Recents gesture was cancelled and we are returning to the previous task.
+            // After super class has handled clean up, show desktop apps on top again
+            showDesktopApps = true;
         }
         super.onGestureAnimationEnd();
-        if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
-                && desktopVisibilityController != null) {
+        if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()) {
             // TODO: b/333533253 - Remove after flag rollout
             desktopVisibilityController.setRecentsGestureEnd(endTarget);
         }
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 4a2be2a..5f08209 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -40,6 +40,8 @@
 import com.android.launcher3.util.NavigationMode;
 import com.android.quickstep.TaskOverlayFactory.OverlayUICallbacks;
 import com.android.quickstep.util.LayoutUtils;
+import com.android.wm.shell.shared.TypefaceUtils;
+import com.android.wm.shell.shared.TypefaceUtils.FontFamily;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -157,6 +159,7 @@
         // Currently, the only grouped task action is "save app pairs".
         mActionButtons = findViewById(R.id.action_buttons);
         mSaveAppPairButton = findViewById(R.id.action_save_app_pair);
+        TypefaceUtils.setTypeface(mSaveAppPairButton, FontFamily.GSF_LABEL_LARGE);
         // Initialize a list to hold alphas for mActionButtons and any group action buttons.
         mMultiValueAlphas[ACTIONS_ALPHAS] = new MultiValueAlpha(mActionButtons, NUM_ALPHAS);
         mMultiValueAlphas[GROUP_ACTIONS_ALPHAS] =
diff --git a/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
new file mode 100644
index 0000000..4ce18f5
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/RecentsDismissUtils.kt
@@ -0,0 +1,507 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views
+
+import android.os.VibrationAttributes
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.FloatValueHolder
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.launcher3.Flags.enableGridOnlyOverview
+import com.android.launcher3.R
+import com.android.launcher3.Utilities.boundToRange
+import com.android.launcher3.touch.SingleAxisSwipeDetector
+import com.android.launcher3.util.DynamicResource
+import com.android.launcher3.util.MSDLPlayerWrapper
+import com.android.quickstep.util.TaskGridNavHelper
+import com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.InteractionProperties
+import kotlin.math.abs
+import kotlin.math.roundToInt
+
+/**
+ * Helper class for [RecentsView]. This util class contains refactored and extracted functions from
+ * RecentsView related to TaskView dismissal.
+ */
+class RecentsDismissUtils(private val recentsView: RecentsView<*, *>) {
+
+    /**
+     * Creates the spring animations which run when a dragged task view in overview is released.
+     *
+     * <p>When a task dismiss is cancelled, the task will return to its original position via a
+     * spring animation. As it passes the threshold of its settling state, its neighbors will spring
+     * in response to the perceived impact of the settling task.
+     */
+    fun createTaskDismissSettlingSpringAnimation(
+        draggedTaskView: TaskView?,
+        velocity: Float,
+        isDismissing: Boolean,
+        detector: SingleAxisSwipeDetector,
+        dismissLength: Int,
+        onEndRunnable: () -> Unit,
+    ): SpringAnimation? {
+        draggedTaskView ?: return null
+        val taskDismissFloatProperty =
+            FloatPropertyCompat.createFloatPropertyCompat(
+                draggedTaskView.secondaryDismissTranslationProperty
+            )
+        // Animate dragged task towards dismissal or rest state.
+        val draggedTaskViewSpringAnimation =
+            SpringAnimation(draggedTaskView, taskDismissFloatProperty)
+                .setSpring(createExpressiveDismissSpringForce())
+                .setStartVelocity(if (detector.isFling(velocity)) velocity else 0f)
+                .addUpdateListener { animation, value, _ ->
+                    if (isDismissing && abs(value) >= abs(dismissLength)) {
+                        animation.cancel()
+                    } else if (draggedTaskView.isRunningTask && recentsView.enableDrawingLiveTile) {
+                        recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+                            remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
+                                taskDismissFloatProperty.getValue(draggedTaskView)
+                        }
+                        recentsView.redrawLiveTile()
+                    }
+                }
+                .addEndListener { _, _, _, _ ->
+                    if (isDismissing) {
+                        if (!recentsView.showAsGrid() || enableGridOnlyOverview()) {
+                            runTaskGridReflowSpringAnimation(
+                                draggedTaskView,
+                                getDismissedTaskGapForReflow(draggedTaskView),
+                            )
+                        } else {
+                            recentsView.dismissTaskView(
+                                draggedTaskView,
+                                /* animateTaskView = */ false,
+                                /* removeTask = */ true,
+                            )
+                        }
+                    } else {
+                        recentsView.onDismissAnimationEnds()
+                    }
+                    onEndRunnable()
+                }
+        if (!isDismissing) {
+            addNeighboringSpringAnimationsForDismissCancel(
+                draggedTaskView,
+                draggedTaskViewSpringAnimation,
+            )
+        }
+        return draggedTaskViewSpringAnimation
+    }
+
+    private fun addNeighboringSpringAnimationsForDismissCancel(
+        draggedTaskView: TaskView,
+        draggedTaskViewSpringAnimation: SpringAnimation,
+    ) {
+        // Empty spring animation exists for conditional start, and to drive neighboring springs.
+        val neighborsToSettle =
+            SpringAnimation(FloatValueHolder()).setSpring(createExpressiveDismissSpringForce())
+        var lastPosition = 0f
+        var startSettling = false
+        draggedTaskViewSpringAnimation.addUpdateListener { _, value, velocity ->
+            // Start the settling animation the first time the dragged task passes the origin (from
+            // negative displacement to positive displacement). We do not check for an exact value
+            // to compare to, as the update listener does not necessarily hit every value (e.g. a
+            // value of zero). Do not check again once it has started settling, as a spring can
+            // bounce past the origin multiple times depending on the stiffness and damping ratio.
+            if (startSettling) return@addUpdateListener
+            if (lastPosition < 0 && value >= 0) {
+                startSettling = true
+            }
+            lastPosition = value
+            if (startSettling) {
+                neighborsToSettle.setStartVelocity(velocity).animateToFinalPosition(0f)
+                playDismissSettlingHaptic(velocity)
+            }
+        }
+
+        // Add tasks before dragged index, fanning out from the dragged task.
+        // The order they are added matters, as each spring drives the next.
+        var previousNeighbor = neighborsToSettle
+        getTasksOffsetPairAdjacentToDraggedTask(draggedTaskView, towardsStart = true).forEach {
+            (taskView, offset) ->
+            previousNeighbor =
+                createNeighboringTaskViewSpringAnimation(
+                    taskView,
+                    offset * ADDITIONAL_DISMISS_DAMPING_RATIO,
+                    previousNeighbor,
+                )
+        }
+        // Add tasks after dragged index, fanning out from the dragged task.
+        // The order they are added matters, as each spring drives the next.
+        previousNeighbor = neighborsToSettle
+        getTasksOffsetPairAdjacentToDraggedTask(draggedTaskView, towardsStart = false).forEach {
+            (taskView, offset) ->
+            previousNeighbor =
+                createNeighboringTaskViewSpringAnimation(
+                    taskView,
+                    offset * ADDITIONAL_DISMISS_DAMPING_RATIO,
+                    previousNeighbor,
+                )
+        }
+    }
+
+    /**
+     * Gets pairs of (TaskView, offset) adjacent the dragged task in visual order.
+     *
+     * <p>Gets tasks either before or after the dragged task along with their offset from it. The
+     * offset is the distance between indices for carousels, or distance between columns for grids.
+     */
+    private fun getTasksOffsetPairAdjacentToDraggedTask(
+        draggedTaskView: TaskView,
+        towardsStart: Boolean,
+    ): Sequence<Pair<TaskView, Int>> {
+        if (recentsView.showAsGrid()) {
+            val taskGridNavHelper =
+                TaskGridNavHelper(
+                    recentsView.mUtils.getTopRowIdArray(),
+                    recentsView.mUtils.getBottomRowIdArray(),
+                    recentsView.mUtils.getLargeTaskViewIds(),
+                    hasAddDesktopButton = false,
+                )
+            return taskGridNavHelper
+                .gridTaskViewIdOffsetPairInTabOrderSequence(
+                    draggedTaskView.taskViewId,
+                    towardsStart,
+                )
+                .mapNotNull { (taskViewId, columnOffset) ->
+                    recentsView.getTaskViewFromTaskViewId(taskViewId)?.let { taskView ->
+                        Pair(taskView, columnOffset)
+                    }
+                }
+        } else {
+            val taskViewList = recentsView.mUtils.taskViews.toList()
+            val draggedTaskViewIndex = taskViewList.indexOf(draggedTaskView)
+
+            return if (towardsStart) {
+                taskViewList
+                    .take(draggedTaskViewIndex)
+                    .reversed()
+                    .mapIndexed { index, taskView -> Pair(taskView, index + 1) }
+                    .asSequence()
+            } else {
+                taskViewList
+                    .takeLast(taskViewList.size - draggedTaskViewIndex - 1)
+                    .mapIndexed { index, taskView -> Pair(taskView, index + 1) }
+                    .asSequence()
+            }
+        }
+    }
+
+    /** Creates a neighboring task view spring, driven by the spring of its neighbor. */
+    private fun createNeighboringTaskViewSpringAnimation(
+        taskView: TaskView,
+        dampingOffsetRatio: Float,
+        previousNeighborSpringAnimation: SpringAnimation,
+    ): SpringAnimation {
+        val neighboringTaskViewSpringAnimation =
+            SpringAnimation(
+                    taskView,
+                    FloatPropertyCompat.createFloatPropertyCompat(
+                        taskView.secondaryDismissTranslationProperty
+                    ),
+                )
+                .setSpring(createExpressiveDismissSpringForce(dampingOffsetRatio))
+        // Update live tile on spring animation.
+        if (taskView.isRunningTask && recentsView.enableDrawingLiveTile) {
+            neighboringTaskViewSpringAnimation.addUpdateListener { _, _, _ ->
+                recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+                    remoteTargetHandle.taskViewSimulator.taskSecondaryTranslation.value =
+                        taskView.secondaryDismissTranslationProperty.get(taskView)
+                }
+                recentsView.redrawLiveTile()
+            }
+        }
+        // Drive current neighbor's spring with the previous neighbor's.
+        previousNeighborSpringAnimation.addUpdateListener { _, value, _ ->
+            neighboringTaskViewSpringAnimation.animateToFinalPosition(value)
+        }
+        return neighboringTaskViewSpringAnimation
+    }
+
+    private fun createExpressiveDismissSpringForce(dampingRatioOffset: Float = 0f): SpringForce {
+        val resourceProvider = DynamicResource.provider(recentsView.mContainer)
+        return SpringForce()
+            .setDampingRatio(
+                resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_y_damping_ratio) +
+                    dampingRatioOffset
+            )
+            .setStiffness(
+                resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_y_stiffness)
+            )
+    }
+
+    private fun createExpressiveGridReflowSpringForce(
+        finalPosition: Float = Float.MAX_VALUE
+    ): SpringForce {
+        val resourceProvider = DynamicResource.provider(recentsView.mContainer)
+        return SpringForce(finalPosition)
+            .setDampingRatio(
+                resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_x_damping_ratio)
+            )
+            .setStiffness(
+                resourceProvider.getFloat(R.dimen.expressive_dismiss_task_trans_x_stiffness)
+            )
+    }
+
+    /**
+     * Plays a haptic as the dragged task view settles back into its rest state.
+     *
+     * <p>Haptic intensity is proportional to velocity.
+     */
+    private fun playDismissSettlingHaptic(velocity: Float) {
+        val maxDismissSettlingVelocity =
+            recentsView.pagedOrientationHandler.getSecondaryDimension(recentsView)
+        MSDLPlayerWrapper.INSTANCE.get(recentsView.context)
+            .playToken(
+                MSDLToken.CANCEL,
+                InteractionProperties.DynamicVibrationScale(
+                    boundToRange(velocity / maxDismissSettlingVelocity, 0f, 1f),
+                    VibrationAttributes.Builder()
+                        .setUsage(VibrationAttributes.USAGE_TOUCH)
+                        .setFlags(VibrationAttributes.FLAG_PIPELINED_EFFECT)
+                        .build(),
+                ),
+            )
+    }
+
+    /** Animates RecentsView's scale to the provided value, using spring animations. */
+    fun animateRecentsScale(scale: Float): SpringAnimation {
+        val resourceProvider = DynamicResource.provider(recentsView.mContainer)
+        val dampingRatio = resourceProvider.getFloat(R.dimen.swipe_up_rect_scale_damping_ratio)
+        val stiffness = resourceProvider.getFloat(R.dimen.swipe_up_rect_scale_stiffness)
+
+        // Spring which sets the Recents scale on update. This is needed, as the SpringAnimation
+        // struggles to animate small values like changing recents scale from 0.9 to 1. So
+        // we animate over a larger range (e.g. 900 to 1000) and convert back to the required value.
+        // (This is instead of converting RECENTS_SCALE_PROPERTY to a FloatPropertyCompat and
+        // animating it directly via springs.)
+        val initialRecentsScaleSpringValue =
+            RECENTS_SCALE_SPRING_MULTIPLIER * RECENTS_SCALE_PROPERTY.get(recentsView)
+        return SpringAnimation(FloatValueHolder(initialRecentsScaleSpringValue))
+            .setSpring(
+                SpringForce(initialRecentsScaleSpringValue)
+                    .setDampingRatio(dampingRatio)
+                    .setStiffness(stiffness)
+            )
+            .addUpdateListener { _, value, _ ->
+                RECENTS_SCALE_PROPERTY.setValue(
+                    recentsView,
+                    value / RECENTS_SCALE_SPRING_MULTIPLIER,
+                )
+            }
+            .apply { animateToFinalPosition(RECENTS_SCALE_SPRING_MULTIPLIER * scale) }
+    }
+
+    /** Animates with springs the TaskViews beyond the dismissed task to fill the gap it left. */
+    private fun runTaskGridReflowSpringAnimation(
+        dismissedTaskView: TaskView,
+        dismissedTaskGap: Float,
+    ) {
+        // Empty spring animation exists for conditional start, and to drive neighboring springs.
+        val springAnimationDriver =
+            SpringAnimation(FloatValueHolder())
+                .setSpring(createExpressiveGridReflowSpringForce(finalPosition = dismissedTaskGap))
+        val towardsStart = if (recentsView.isRtl) dismissedTaskGap < 0 else dismissedTaskGap > 0
+
+        // Build the chains of Spring Animations
+        when {
+            !recentsView.showAsGrid() -> {
+                buildDismissReflowSpringAnimationChain(
+                    getTasksToReflow(
+                        recentsView.mUtils.taskViews.toList(),
+                        dismissedTaskView,
+                        towardsStart,
+                    ),
+                    dismissedTaskGap,
+                    previousSpring = springAnimationDriver,
+                )
+            }
+            dismissedTaskView.isLargeTile -> {
+                val lastSpringAnimation =
+                    buildDismissReflowSpringAnimationChain(
+                        getTasksToReflow(
+                            recentsView.mUtils.getLargeTaskViews(),
+                            dismissedTaskView,
+                            towardsStart,
+                        ),
+                        dismissedTaskGap,
+                        previousSpring = springAnimationDriver,
+                    )
+                // Add all top and bottom grid tasks when animating towards the end of the grid.
+                if (!towardsStart) {
+                    buildDismissReflowSpringAnimationChain(
+                        recentsView.mUtils.getTopRowTaskViews(),
+                        dismissedTaskGap,
+                        previousSpring = lastSpringAnimation,
+                    )
+                    buildDismissReflowSpringAnimationChain(
+                        recentsView.mUtils.getBottomRowTaskViews(),
+                        dismissedTaskGap,
+                        previousSpring = lastSpringAnimation,
+                    )
+                }
+            }
+            recentsView.isOnGridBottomRow(dismissedTaskView) -> {
+                buildDismissReflowSpringAnimationChain(
+                    getTasksToReflow(
+                        recentsView.mUtils.getBottomRowTaskViews(),
+                        dismissedTaskView,
+                        towardsStart,
+                    ),
+                    dismissedTaskGap,
+                    previousSpring = springAnimationDriver,
+                )
+            }
+            else -> {
+                buildDismissReflowSpringAnimationChain(
+                    getTasksToReflow(
+                        recentsView.mUtils.getTopRowTaskViews(),
+                        dismissedTaskView,
+                        towardsStart,
+                    ),
+                    dismissedTaskGap,
+                    previousSpring = springAnimationDriver,
+                )
+            }
+        }
+
+        // Start animations and remove the dismissed task at the end, dismiss immediately if no
+        // neighboring tasks exist.
+        val runGridEndAnimationAndRelayout = {
+            recentsView.expressiveDismissTaskView(dismissedTaskView)
+        }
+        springAnimationDriver?.apply {
+            addEndListener { _, _, _, _ -> runGridEndAnimationAndRelayout() }
+            animateToFinalPosition(dismissedTaskGap)
+        } ?: runGridEndAnimationAndRelayout()
+    }
+
+    private fun getDismissedTaskGapForReflow(dismissedTaskView: TaskView): Float {
+        val screenStart = recentsView.pagedOrientationHandler.getPrimaryScroll(recentsView)
+        val screenEnd =
+            screenStart + recentsView.pagedOrientationHandler.getMeasuredSize(recentsView)
+        val taskStart =
+            recentsView.pagedOrientationHandler.getChildStart(dismissedTaskView) +
+                dismissedTaskView.getOffsetAdjustment(recentsView.showAsGrid())
+        val taskSize =
+            recentsView.pagedOrientationHandler.getMeasuredSize(dismissedTaskView) *
+                dismissedTaskView.getSizeAdjustment(recentsView.showAsFullscreen())
+        val taskEnd = taskStart + taskSize
+
+        val isDismissedTaskBeyondEndOfScreen =
+            if (recentsView.isRtl) taskEnd > screenEnd else taskStart < screenStart
+        if (
+            dismissedTaskView.isLargeTile &&
+                isDismissedTaskBeyondEndOfScreen &&
+                recentsView.mUtils.getLargeTileCount() == 1
+        ) {
+            return with(recentsView) {
+                    pagedOrientationHandler.getPrimaryScroll(this) -
+                        getScrollForPage(indexOfChild(mUtils.getFirstNonDesktopTaskView()))
+                }
+                .toFloat()
+        }
+
+        // If current page is beyond last TaskView's index, use last TaskView to calculate offset.
+        val lastTaskViewIndex = recentsView.indexOfChild(recentsView.mUtils.getLastTaskView())
+        val currentPage = recentsView.currentPage.coerceAtMost(lastTaskViewIndex)
+        val dismissHorizontalFactor =
+            when {
+                dismissedTaskView.isGridTask -> 1f
+                currentPage == lastTaskViewIndex -> -1f
+                recentsView.indexOfChild(dismissedTaskView) < currentPage -> -1f
+                else -> 1f
+            } * (if (recentsView.isRtl) 1f else -1f)
+
+        return (dismissedTaskView.layoutParams.width + recentsView.pageSpacing) *
+            dismissHorizontalFactor
+    }
+
+    private fun getTasksToReflow(
+        taskViews: List<TaskView>,
+        dismissedTaskView: TaskView,
+        towardsStart: Boolean,
+    ): List<TaskView> {
+        val dismissedTaskViewIndex = taskViews.indexOf(dismissedTaskView)
+        if (dismissedTaskViewIndex == -1) {
+            return emptyList()
+        }
+        return if (towardsStart) {
+            taskViews.take(dismissedTaskViewIndex).reversed()
+        } else {
+            taskViews.takeLast(taskViews.size - dismissedTaskViewIndex - 1)
+        }
+    }
+
+    private fun willTaskBeVisibleAfterDismiss(taskView: TaskView, taskTranslation: Int): Boolean {
+        val screenStart = recentsView.pagedOrientationHandler.getPrimaryScroll(recentsView)
+        val screenEnd =
+            screenStart + recentsView.pagedOrientationHandler.getMeasuredSize(recentsView)
+        return recentsView.isTaskViewWithinBounds(
+            taskView,
+            screenStart,
+            screenEnd,
+            /* taskViewTranslation = */ taskTranslation,
+        )
+    }
+
+    /** Builds a chain of spring animations for task reflow after dismissal */
+    private fun buildDismissReflowSpringAnimationChain(
+        taskViews: Iterable<TaskView>,
+        dismissedTaskGap: Float,
+        previousSpring: SpringAnimation,
+    ): SpringAnimation {
+        var lastTaskViewSpring = previousSpring
+        taskViews
+            .filter { taskView ->
+                willTaskBeVisibleAfterDismiss(taskView, dismissedTaskGap.roundToInt())
+            }
+            .forEach { taskView ->
+                val taskViewSpringAnimation =
+                    SpringAnimation(
+                            taskView,
+                            FloatPropertyCompat.createFloatPropertyCompat(
+                                taskView.primaryDismissTranslationProperty
+                            ),
+                        )
+                        .setSpring(createExpressiveGridReflowSpringForce(dismissedTaskGap))
+                // Update live tile on spring animation.
+                if (taskView.isRunningTask && recentsView.enableDrawingLiveTile) {
+                    taskViewSpringAnimation.addUpdateListener { _, _, _ ->
+                        recentsView.runActionOnRemoteHandles { remoteTargetHandle ->
+                            remoteTargetHandle.taskViewSimulator.taskPrimaryTranslation.value =
+                                taskView.primaryDismissTranslationProperty.get(taskView)
+                        }
+                        recentsView.redrawLiveTile()
+                    }
+                }
+                lastTaskViewSpring.addUpdateListener { _, value, _ ->
+                    taskViewSpringAnimation.animateToFinalPosition(value)
+                }
+                lastTaskViewSpring = taskViewSpringAnimation
+            }
+        return lastTaskViewSpring
+    }
+
+    private companion object {
+        // The additional damping to apply to tasks further from the dismissed task.
+        private const val ADDITIONAL_DISMISS_DAMPING_RATIO = 0.15f
+        private const val RECENTS_SCALE_SPRING_MULTIPLIER = 1000f
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 6752775..bb2aa75 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -25,21 +25,23 @@
 import static com.android.app.animation.Interpolators.ACCELERATE_0_75;
 import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
 import static com.android.app.animation.Interpolators.DECELERATE_2;
+import static com.android.app.animation.Interpolators.EMPHASIZED;
 import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
 import static com.android.app.animation.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.app.animation.Interpolators.FINAL_FRAME;
 import static com.android.app.animation.Interpolators.LINEAR;
-import static com.android.app.animation.Interpolators.EMPHASIZED;
 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.enableDesktopExplodedView;
 import static com.android.launcher3.Flags.enableDesktopTaskAlphaAnimation;
 import static com.android.launcher3.Flags.enableGridOnlyOverview;
 import static com.android.launcher3.Flags.enableLargeDesktopWindowingTile;
 import static com.android.launcher3.Flags.enableRefactorTaskThumbnail;
+import static com.android.launcher3.Flags.enableSeparateExternalDisplayTasks;
 import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
@@ -63,19 +65,15 @@
 import static com.android.quickstep.BaseContainerInterface.getTaskDimension;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
 import static com.android.quickstep.util.LogUtils.splitFailureMessage;
-import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_DOWN;
-import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_LEFT;
-import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_RIGHT;
-import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_TAB;
-import static com.android.quickstep.util.TaskGridNavHelper.DIRECTION_UP;
 import static com.android.quickstep.views.ClearAllButton.DISMISS_ALPHA;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_ACTIONS_IN_MENU;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_DESKTOP;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NON_ZERO_ROTATION;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_RECENTS;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_NO_TASKS;
-import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SCREEN;
 import static com.android.quickstep.views.OverviewActionsView.HIDDEN_SPLIT_SELECT_ACTIVE;
+import static com.android.quickstep.views.RecentsViewUtils.DESK_EXPLODE_PROGRESS;
+import static com.android.quickstep.views.TaskView.SPLIT_ALPHA;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -131,11 +129,13 @@
 import android.widget.Toast;
 import android.window.DesktopModeFlags;
 import android.window.PictureInPictureSurfaceTransaction;
+import android.window.TransitionInfo;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 import androidx.core.graphics.ColorUtils;
+import androidx.dynamicanimation.animation.SpringAnimation;
 
 import com.android.internal.jank.Cuj;
 import com.android.launcher3.AbstractFloatingView;
@@ -143,7 +143,6 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Flags;
 import com.android.launcher3.Insettable;
-import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -159,12 +158,14 @@
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.statehandlers.DepthController;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
 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;
+import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.util.CancellableTask;
 import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.IntArray;
@@ -179,8 +180,11 @@
 import com.android.launcher3.util.TranslateEdgeEffect;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.launcher3.util.ViewPool;
+import com.android.launcher3.util.coroutines.DispatcherProvider;
+import com.android.launcher3.util.window.WindowManagerProxy.DesktopVisibilityListener;
 import com.android.quickstep.BaseContainerInterface;
 import com.android.quickstep.GestureState;
+import com.android.quickstep.HighResLoadingState;
 import com.android.quickstep.OverviewCommandHelper;
 import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.RecentsAnimationTargets;
@@ -193,10 +197,10 @@
 import com.android.quickstep.SplitSelectionListener;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskOverlayFactory;
-import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskViewUtils;
 import com.android.quickstep.TopTaskTracker;
 import com.android.quickstep.ViewUtils;
+import com.android.quickstep.fallback.window.RecentsWindowFlags;
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
 import com.android.quickstep.recents.data.RecentTasksRepository;
 import com.android.quickstep.recents.data.RecentsDeviceProfileRepository;
@@ -208,15 +212,17 @@
 import com.android.quickstep.recents.viewmodel.RecentsViewModel;
 import com.android.quickstep.util.ActiveGestureProtoLogProxy;
 import com.android.quickstep.util.AnimUtils;
+import com.android.quickstep.util.DesksUtils;
 import com.android.quickstep.util.DesktopTask;
 import com.android.quickstep.util.GroupTask;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RecentsAtomicAnimationFactory;
 import com.android.quickstep.util.RecentsOrientedState;
-import com.android.quickstep.util.RecentsViewUtils;
+import com.android.quickstep.util.SingleTask;
 import com.android.quickstep.util.SplitAnimationController.Companion.SplitAnimInitProps;
 import com.android.quickstep.util.SplitAnimationTimings;
 import com.android.quickstep.util.SplitSelectStateController;
+import com.android.quickstep.util.SplitTask;
 import com.android.quickstep.util.SurfaceTransaction;
 import com.android.quickstep.util.SurfaceTransactionApplier;
 import com.android.quickstep.util.TaskGridNavHelper;
@@ -237,6 +243,10 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
 
 import kotlin.Unit;
+import kotlin.collections.CollectionsKt;
+import kotlin.jvm.functions.Function0;
+
+import kotlinx.coroutines.CoroutineScope;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -250,7 +260,6 @@
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
-
 /**
  * A list of recent tasks.
  *
@@ -260,14 +269,14 @@
 public abstract class RecentsView<
         CONTAINER_TYPE extends Context & RecentsViewContainer & StatefulContainer<STATE_TYPE>,
         STATE_TYPE extends BaseState<STATE_TYPE>> extends PagedView implements Insettable,
-        TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback,
-        TaskVisualsChangeListener {
+        HighResLoadingState.HighResLoadingStateChangedCallback,
+        TaskVisualsChangeListener, DesktopVisibilityListener {
 
     private static final String TAG = "RecentsView";
     private static final boolean DEBUG = false;
 
-    public static final FloatProperty<RecentsView> CONTENT_ALPHA =
-            new FloatProperty<RecentsView>("contentAlpha") {
+    public static final FloatProperty<RecentsView<?, ?>> CONTENT_ALPHA =
+            new FloatProperty<>("contentAlpha") {
                 @Override
                 public void setValue(RecentsView view, float v) {
                     view.setContentAlpha(v);
@@ -279,8 +288,8 @@
                 }
             };
 
-    public static final FloatProperty<RecentsView> FULLSCREEN_PROGRESS =
-            new FloatProperty<RecentsView>("fullscreenProgress") {
+    public static final FloatProperty<RecentsView<?, ?>> FULLSCREEN_PROGRESS =
+            new FloatProperty<>("fullscreenProgress") {
                 @Override
                 public void setValue(RecentsView recentsView, float v) {
                     recentsView.setFullscreenProgress(v);
@@ -292,8 +301,8 @@
                 }
             };
 
-    public static final FloatProperty<RecentsView> TASK_MODALNESS =
-            new FloatProperty<RecentsView>("taskModalness") {
+    public static final FloatProperty<RecentsView<?, ?>> TASK_MODALNESS =
+            new FloatProperty<>("taskModalness") {
                 @Override
                 public void setValue(RecentsView recentsView, float v) {
                     recentsView.setTaskModalness(v);
@@ -305,8 +314,8 @@
                 }
             };
 
-    public static final FloatProperty<RecentsView> ADJACENT_PAGE_HORIZONTAL_OFFSET =
-            new FloatProperty<RecentsView>("adjacentPageHorizontalOffset") {
+    public static final FloatProperty<RecentsView<?, ?>> ADJACENT_PAGE_HORIZONTAL_OFFSET =
+            new FloatProperty<>("adjacentPageHorizontalOffset") {
                 @Override
                 public void setValue(RecentsView recentsView, float v) {
                     if (recentsView.mAdjacentPageHorizontalOffset != v) {
@@ -321,8 +330,8 @@
                 }
             };
 
-    public static final FloatProperty<RecentsView> RUNNING_TASK_ATTACH_ALPHA =
-            new FloatProperty<RecentsView>("runningTaskAttachAlpha") {
+    public static final FloatProperty<RecentsView<?, ?>> RUNNING_TASK_ATTACH_ALPHA =
+            new FloatProperty<>("runningTaskAttachAlpha") {
                 @Override
                 public void setValue(RecentsView recentsView, float v) {
                     recentsView.mRunningTaskAttachAlpha = v;
@@ -346,8 +355,8 @@
      * 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.
      */
-    private static final FloatProperty<RecentsView> COLOR_TINT =
-            new FloatProperty<RecentsView>("colorTint") {
+    private static final FloatProperty<RecentsView<?, ?>> COLOR_TINT =
+            new FloatProperty<>("colorTint") {
                 @Override
                 public void setValue(RecentsView recentsView, float v) {
                     recentsView.setColorTint(v);
@@ -365,8 +374,8 @@
      * more specific, we'd want to create a similar FloatProperty just for a TaskView's
      * offsetX/Y property
      */
-    public static final FloatProperty<RecentsView> TASK_SECONDARY_TRANSLATION =
-            new FloatProperty<RecentsView>("taskSecondaryTranslation") {
+    public static final FloatProperty<RecentsView<?, ?>> TASK_SECONDARY_TRANSLATION =
+            new FloatProperty<>("taskSecondaryTranslation") {
                 @Override
                 public void setValue(RecentsView recentsView, float v) {
                     recentsView.setTaskViewsResistanceTranslation(v);
@@ -384,8 +393,8 @@
      * more specific, we'd want to create a similar FloatProperty just for a TaskView's
      * offsetX/Y property
      */
-    public static final FloatProperty<RecentsView> TASK_PRIMARY_SPLIT_TRANSLATION =
-            new FloatProperty<RecentsView>("taskPrimarySplitTranslation") {
+    public static final FloatProperty<RecentsView<?, ?>> TASK_PRIMARY_SPLIT_TRANSLATION =
+            new FloatProperty<>("taskPrimarySplitTranslation") {
                 @Override
                 public void setValue(RecentsView recentsView, float v) {
                     recentsView.setTaskViewsPrimarySplitTranslation(v);
@@ -397,8 +406,8 @@
                 }
             };
 
-    public static final FloatProperty<RecentsView> TASK_SECONDARY_SPLIT_TRANSLATION =
-            new FloatProperty<RecentsView>("taskSecondarySplitTranslation") {
+    public static final FloatProperty<RecentsView<?, ?>> TASK_SECONDARY_SPLIT_TRANSLATION =
+            new FloatProperty<>("taskSecondarySplitTranslation") {
                 @Override
                 public void setValue(RecentsView recentsView, float v) {
                     recentsView.setTaskViewsSecondarySplitTranslation(v);
@@ -411,8 +420,8 @@
             };
 
     /** Same as normal SCALE_PROPERTY, but also updates page offsets that depend on this scale. */
-    public static final FloatProperty<RecentsView> RECENTS_SCALE_PROPERTY =
-            new FloatProperty<RecentsView>("recentsScale") {
+    public static final FloatProperty<RecentsView<?, ?>> RECENTS_SCALE_PROPERTY =
+            new FloatProperty<>("recentsScale") {
                 @Override
                 public void setValue(RecentsView view, float scale) {
                     view.setScaleX(scale);
@@ -441,8 +450,8 @@
      * Progress of Recents view from carousel layout to grid layout. If Recents is not shown as a
      * grid, then the value remains 0.
      */
-    public static final FloatProperty<RecentsView> RECENTS_GRID_PROGRESS =
-            new FloatProperty<RecentsView>("recentsGrid") {
+    public static final FloatProperty<RecentsView<?, ?>> RECENTS_GRID_PROGRESS =
+            new FloatProperty<>("recentsGrid") {
                 @Override
                 public void setValue(RecentsView view, float gridProgress) {
                     view.setGridProgress(gridProgress);
@@ -454,7 +463,7 @@
                 }
             };
 
-    public static final FloatProperty<RecentsView> DESKTOP_CAROUSEL_DETACH_PROGRESS =
+    public static final FloatProperty<RecentsView<?, ?>> DESKTOP_CAROUSEL_DETACH_PROGRESS =
             new FloatProperty<>("desktopCarouselDetachProgress") {
                 @Override
                 public void setValue(RecentsView view, float offset) {
@@ -473,8 +482,8 @@
      * Alpha of the task thumbnail splash, where being in BackgroundAppState has a value of 1, and
      * being in any other state has a value of 0.
      */
-    public static final FloatProperty<RecentsView> TASK_THUMBNAIL_SPLASH_ALPHA =
-            new FloatProperty<RecentsView>("taskThumbnailSplashAlpha") {
+    public static final FloatProperty<RecentsView<?, ?>> TASK_THUMBNAIL_SPLASH_ALPHA =
+            new FloatProperty<>("taskThumbnailSplashAlpha") {
                 @Override
                 public void setValue(RecentsView view, float taskThumbnailSplashAlpha) {
                     view.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha);
@@ -503,7 +512,7 @@
     private static final float FOREGROUND_SCRIM_TINT = 0.32f;
 
     protected final RecentsOrientedState mOrientationState;
-    protected final BaseContainerInterface<STATE_TYPE, CONTAINER_TYPE> mSizeStrategy;
+    protected final BaseContainerInterface<STATE_TYPE, ?> mSizeStrategy;
     @Nullable
     protected RecentsAnimationController mRecentsAnimationController;
     @Nullable
@@ -519,7 +528,6 @@
 
     @Nullable
     protected RemoteTargetHandle[] mRemoteTargetHandles;
-    protected final Rect mLastComputedCarouselTaskSize = new Rect();
     protected final Rect mLastComputedTaskSize = new Rect();
     protected final Rect mLastComputedGridSize = new Rect();
     protected final Rect mLastComputedGridTaskSize = new Rect();
@@ -547,8 +555,16 @@
     private final int mSplitPlaceholderSize;
     private final int mSplitPlaceholderInset;
     private final ClearAllButton mClearAllButton;
+    @Nullable
+    private AddDesktopButton mAddDesktopButton = null;
     private final Rect mClearAllButtonDeadZoneRect = new Rect();
     private final Rect mTaskViewDeadZoneRect = new Rect();
+    private final Rect mTopRowDeadZoneRect = new Rect();
+    private final Rect mBottomRowDeadZoneRect = new Rect();
+
+    @Nullable
+    private DesktopVisibilityController mDesktopVisibilityController = null;
+
     /**
      * Reflects if Recents is currently in the middle of a gesture, and if so, which tasks are
      * running. If a gesture is not in progress, this will be null.
@@ -558,8 +574,6 @@
     // Keeps track of the previously known visible tasks for purposes of loading/unloading task data
     private final SparseBooleanArray mHasVisibleTaskData = new SparseBooleanArray();
 
-    private final InvariantDeviceProfile mIdp;
-
     /**
      * Getting views should be done via {@link #getTaskViewFromPool(int)}
      */
@@ -589,7 +603,7 @@
     private float mTaskThumbnailSplashAlpha = 0;
     private boolean mBorderEnabled = false;
     private boolean mShowAsGridLastOnLayout = false;
-    private final IntSet mTopRowIdSet = new IntSet();
+    protected final IntSet mTopRowIdSet = new IntSet();
     private int mClearAllShortTotalWidthTranslation = 0;
 
     // The GestureEndTarget that is still in progress.
@@ -607,7 +621,8 @@
     private int mKeyboardTaskFocusSnapAnimationDuration;
     private int mKeyboardTaskFocusIndex = INVALID_PAGE;
 
-    private int[] mDismissPrimaryTranslations;
+    private Map<TaskView, Integer> mTaskViewsDismissPrimaryTranslations =
+            new HashMap<TaskView, Integer>();
 
     /**
      * TODO: Call reloadIdNeeded in onTaskStackChanged.
@@ -647,24 +662,24 @@
                 return;
             }
 
-            TaskView taskView = getTaskViewByTaskId(taskId);
-            if (taskView == null) {
-                Log.d(TAG, "onTaskRemoved: " + taskId + ", no associated TaskView");
+            TaskContainer taskContainer = mUtils.getTaskContainerById(taskId);
+            if (taskContainer == null) {
+                Log.d(TAG, "onTaskRemoved: " + taskId + ", no associated Task");
                 return;
             }
             Log.d(TAG, "onTaskRemoved: " + taskId);
-            Task.TaskKey taskKey = taskView.getFirstTask().key;
+            Task.TaskKey taskKey = taskContainer.getTask().key;
             UI_HELPER_EXECUTOR.execute(new CancellableTask<>(
                     () -> PackageManagerWrapper.getInstance()
                             .getActivityInfo(taskKey.getComponent(), taskKey.userId) == null,
                     MAIN_EXECUTOR,
                     apkRemoved -> {
                         if (apkRemoved) {
-                            dismissTask(taskId);
+                            dismissTask(taskId, /*animate=*/true, /*removeTask=*/false);
                         } else {
                             mModel.isTaskRemoved(taskKey.id, taskRemoved -> {
                                 if (taskRemoved) {
-                                    dismissTask(taskId);
+                                    dismissTask(taskId, /*animate=*/true, /*removeTask=*/false);
                                 }
                             }, RecentsFilterState.getFilter(mFilterState.getPackageNameToFilter()));
                         }
@@ -842,17 +857,24 @@
 
     private final RecentsViewModel mRecentsViewModel;
     private final RecentsViewModelHelper mHelper;
-    private final RecentsViewUtils mUtils = new RecentsViewUtils();
+    protected final RecentsViewUtils mUtils = new RecentsViewUtils(this);
+    protected final RecentsDismissUtils mDismissUtils = new RecentsDismissUtils(this);
 
     private final Matrix mTmpMatrix = new Matrix();
 
-    public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
-            BaseContainerInterface sizeStrategy) {
+    private int mTaskViewCount = 0;
+    @Nullable
+    public TaskView getFirstTaskView() {
+        return mUtils.getFirstTaskView();
+    }
+
+    public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
         setEnableFreeScroll(true);
 
-        mSizeStrategy = sizeStrategy;
         mContainer = RecentsViewContainer.containerFromContext(context);
+        mSizeStrategy = getContainerInterface(mContainer.getDisplayId());
+
         mOrientationState = new RecentsOrientedState(
                 context, mSizeStrategy, this::animateRecentsRotationInPlace);
         final int rotation = mContainer.getDisplay().getRotation();
@@ -860,18 +882,23 @@
 
         // Start Recents Dependency graph
         if (enableRefactorTaskThumbnail()) {
-            RecentsDependencies recentsDependencies = RecentsDependencies.Companion.initialize(
-                    this);
+            RecentsDependencies recentsDependencies = RecentsDependencies.Companion.maybeInitialize(
+                    context);
+            String scopeId = recentsDependencies.createRecentsViewScope(context);
             mRecentsViewModel = new RecentsViewModel(
-                    recentsDependencies.inject(RecentTasksRepository.class),
-                    recentsDependencies.inject(RecentsViewData.class)
+                    recentsDependencies.inject(RecentTasksRepository.class, scopeId),
+                    recentsDependencies.inject(RecentsViewData.class, scopeId)
             );
-            mHelper = new RecentsViewModelHelper(mRecentsViewModel);
+            mHelper = new RecentsViewModelHelper(
+                    mRecentsViewModel,
+                    recentsDependencies.inject(CoroutineScope.class, scopeId),
+                    recentsDependencies.inject(DispatcherProvider.class, scopeId)
+            );
 
-            recentsDependencies.provide(RecentsRotationStateRepository.class,
+            recentsDependencies.provide(RecentsRotationStateRepository.class, scopeId,
                     () -> new RecentsRotationStateRepositoryImpl(mOrientationState));
 
-            recentsDependencies.provide(RecentsDeviceProfileRepository.class,
+            recentsDependencies.provide(RecentsDeviceProfileRepository.class, scopeId,
                     () -> new RecentsDeviceProfileRepositoryImpl(mContainer));
         } else {
             mRecentsViewModel = null;
@@ -883,17 +910,27 @@
         mFastFlingVelocity = getResources()
                 .getDimensionPixelSize(R.dimen.recents_fast_fling_velocity);
         mModel = RecentsModel.INSTANCE.get(context);
-        mIdp = InvariantDeviceProfile.INSTANCE.get(context);
 
         mClearAllButton = (ClearAllButton) LayoutInflater.from(context)
                 .inflate(R.layout.overview_clear_all_button, this, false);
         mClearAllButton.setOnClickListener(this::dismissAllTasks);
+
+        if (DesktopModeStatus.enableMultipleDesktops(mContext)) {
+            mAddDesktopButton = (AddDesktopButton) LayoutInflater.from(context).inflate(
+                    R.layout.overview_add_desktop_button, this, false);
+            mAddDesktopButton.setOnClickListener(this::createDesk);
+
+            mDesktopVisibilityController = DesktopVisibilityController.INSTANCE.get(mContext);
+        }
+
         mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
                 10 /* initial size */);
+        int groupedViewPoolInitialSize = enableRefactorTaskThumbnail() ? 2 : 10;
         mGroupedTaskViewPool = new ViewPool<>(context, this,
-                R.layout.task_grouped, 20 /* max size */, 10 /* initial size */);
+                R.layout.task_grouped, 20 /* max size */, groupedViewPoolInitialSize);
+        int desktopViewPoolInitialSize = DesktopModeStatus.canEnterDesktopMode(mContext) ? 1 : 0;
         mDesktopTaskViewPool = new ViewPool<>(context, this, R.layout.task_desktop,
-                5 /* max size */, 1 /* initial size */);
+                5 /* max size */, desktopViewPoolInitialSize);
 
         setOrientationHandler(mOrientationState.getOrientationHandler());
         mIsRtl = getPagedOrientationHandler().getRecentsRtlSetting(getResources());
@@ -913,8 +950,11 @@
         mEmptyMessagePaint.setColor(Themes.getAttrColor(context, android.R.attr.textColorPrimary));
         mEmptyMessagePaint.setTextSize(getResources()
                 .getDimension(R.dimen.recents_empty_message_text_size));
-        mEmptyMessagePaint.setTypeface(Typeface.create(Themes.getDefaultBodyFont(context),
-                Typeface.NORMAL));
+        Typeface typeface = Typeface.create(
+                Typeface.create(Themes.getDefaultBodyFont(context), Typeface.NORMAL),
+                getFontWeight(),
+                false);
+        mEmptyMessagePaint.setTypeface(typeface);
         mEmptyMessagePaint.setAntiAlias(true);
         mEmptyMessagePadding = getResources()
                 .getDimensionPixelSize(R.dimen.recents_empty_message_text_padding);
@@ -1091,7 +1131,7 @@
                 TaskView taskView = getTaskViewByTaskId(taskId);
                 if (taskView != null) {
                     for (TaskContainer container : taskView.getTaskContainers()) {
-                        if (container == null || taskId != container.getTask().key.id) {
+                        if (taskId != container.getTask().key.id) {
                             continue;
                         }
                         container.getThumbnailViewDeprecated().setThumbnail(container.getTask(),
@@ -1106,9 +1146,10 @@
     @Override
     public void onTaskIconChanged(@NonNull String pkg, @NonNull UserHandle user) {
         for (TaskView taskView : getTaskViews()) {
-            Task task = taskView.getFirstTask();
-            if (pkg.equals(task.key.getPackageName()) && task.key.userId == user.getIdentifier()) {
-                task.icon = null;
+            Task firstTask = taskView.getFirstTask();
+            if (firstTask != null && pkg.equals(firstTask.key.getPackageName())
+                    && firstTask.key.userId == user.getIdentifier()) {
+                firstTask.icon = null;
                 if (taskView.getTaskContainers().stream().anyMatch(
                         container -> container.getIconView().getDrawable() != null)) {
                     taskView.onTaskListVisibilityChanged(true /* visible */);
@@ -1160,7 +1201,7 @@
     public void init(OverviewActionsView actionsView, SplitSelectStateController splitController,
             @Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
         mActionsView = actionsView;
-        mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
+        mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, !hasTaskViews());
         // Update flags for 1p/3p launchers
         mActionsView.updateFor3pLauncher(!supportsAppPairs());
         mSplitSelectStateController = splitController;
@@ -1194,17 +1235,15 @@
         mSyncTransactionApplier = new SurfaceTransactionApplier(this);
         runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
                 .setSyncTransactionApplier(mSyncTransactionApplier));
-        RecentsModel.INSTANCE.get(getContext()).addThumbnailChangeListener(this);
+        RecentsModel.INSTANCE.get(mContext).addThumbnailChangeListener(this);
         mIPipAnimationListener.setActivityAndRecentsView(mContainer, this);
-        SystemUiProxy.INSTANCE.get(getContext()).setPipAnimationListener(
+        SystemUiProxy.INSTANCE.get(mContext).setPipAnimationListener(
                 mIPipAnimationListener);
         mOrientationState.initListeners();
         mTaskOverlayFactory.initListeners();
-        if (FeatureFlags.enableSplitContextually()) {
-            mSplitSelectStateController.registerSplitListener(mSplitSelectionListener);
-        }
-        if (enableRefactorTaskThumbnail()) {
-            mHelper.onAttachedToWindow();
+        mSplitSelectStateController.registerSplitListener(mSplitSelectionListener);
+        if (mDesktopVisibilityController != null) {
+            mDesktopVisibilityController.registerDesktopVisibilityListener(this);
         }
     }
 
@@ -1220,48 +1259,71 @@
         runActionOnRemoteHandles(remoteTargetHandle -> remoteTargetHandle.getTransformParams()
                 .setSyncTransactionApplier(null));
         executeSideTaskLaunchCallback();
-        RecentsModel.INSTANCE.get(getContext()).removeThumbnailChangeListener(this);
-        SystemUiProxy.INSTANCE.get(getContext()).setPipAnimationListener(null);
+        RecentsModel.INSTANCE.get(mContext).removeThumbnailChangeListener(this);
+        SystemUiProxy.INSTANCE.get(mContext).setPipAnimationListener(null);
         mIPipAnimationListener.setActivityAndRecentsView(null, null);
         mOrientationState.destroyListeners();
         mTaskOverlayFactory.removeListeners();
-        if (FeatureFlags.enableSplitContextually()) {
-            mSplitSelectStateController.unregisterSplitListener(mSplitSelectionListener);
+        mSplitSelectStateController.unregisterSplitListener(mSplitSelectionListener);
+        if (mDesktopVisibilityController != null) {
+            mDesktopVisibilityController.unregisterDesktopVisibilityListener(this);
         }
         reset();
+    }
+
+    /**
+     * Execute clean-up logic needed when the view is destroyed.
+     */
+    public void destroy() {
+        Log.d(TAG, "destroy");
         if (enableRefactorTaskThumbnail()) {
-            mHelper.onDetachedFromWindow();
+            try {
+                mTaskViewPool.killOngoingInitializations();
+                mGroupedTaskViewPool.killOngoingInitializations();
+                mDesktopTaskViewPool.killOngoingInitializations();
+            } catch (InterruptedException e) {
+                Log.e(TAG, "Ongoing initializations could not be killed", e);
+            }
+            mHelper.onDestroy();
+            RecentsDependencies.destroy(getContext());
         }
     }
 
     @Override
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
-
         // Clear the task data for the removed child if it was visible unless:
         // - It's the initial taskview for entering split screen, we only pretend to dismiss the
         // task
         // - It's the focused task to be moved to the front, we immediately re-add the task
-        if (child instanceof TaskView && child != mSplitHiddenTaskView
-                && child != mMovingTaskView) {
-            TaskView taskView = (TaskView) child;
-            for (int i : taskView.getTaskIds()) {
-                mHasVisibleTaskData.delete(i);
+        if (child instanceof TaskView) {
+            mTaskViewCount = Math.max(0, --mTaskViewCount);
+            if (child != mSplitHiddenTaskView && child != mMovingTaskView) {
+                clearAndRecycleTaskView((TaskView) child);
             }
-            if (child instanceof GroupedTaskView) {
-                mGroupedTaskViewPool.recycle((GroupedTaskView) taskView);
-            } else if (child instanceof DesktopTaskView) {
-                mDesktopTaskViewPool.recycle((DesktopTaskView) taskView);
-            } else {
-                mTaskViewPool.recycle(taskView);
-            }
-            mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
         }
     }
 
+    private void clearAndRecycleTaskView(TaskView taskView) {
+        for (int i : taskView.getTaskIds()) {
+            mHasVisibleTaskData.delete(i);
+        }
+        if (taskView instanceof GroupedTaskView) {
+            mGroupedTaskViewPool.recycle((GroupedTaskView) taskView);
+        } else if (taskView instanceof DesktopTaskView) {
+            mDesktopTaskViewPool.recycle((DesktopTaskView) taskView);
+        } else {
+            mTaskViewPool.recycle(taskView);
+        }
+        mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, !hasTaskViews());
+    }
+
     @Override
     public void onViewAdded(View child) {
         super.onViewAdded(child);
+        if (child instanceof TaskView) {
+            mTaskViewCount++;
+        }
         child.setAlpha(mContentAlpha);
         // RecentsView is set to RTL in the constructor when system is using LTR. Here we set the
         // child direction back to match system settings.
@@ -1346,12 +1408,13 @@
         RemoteAnimationTargets targets = params.getTargetSet();
         if (targets != null && targets.findTask(taskId) != null) {
             launchSideTaskInLiveTileMode(taskId, targets.apps, targets.wallpapers,
-                    targets.nonApps);
+                    targets.nonApps, /* transitionInfo= */ null);
         }
     }
 
     public void launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTarget[] apps,
-            RemoteAnimationTarget[] wallpaper, RemoteAnimationTarget[] nonApps) {
+            RemoteAnimationTarget[] wallpaper, RemoteAnimationTarget[] nonApps,
+            @Nullable TransitionInfo transitionInfo) {
         AnimatorSet anim = new AnimatorSet();
         TaskView taskView = getTaskViewByTaskId(taskId);
         if (taskView == null || !isTaskViewVisible(taskView)) {
@@ -1397,17 +1460,30 @@
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    finishRecentsAnimation(false /* toRecents */, null);
+                    finishRecentsAnimation(false /* toRecents */, true /*shouldPip*/,
+                            allAppsAreTranslucent(apps), null);
                 }
             });
         } else {
             TaskViewUtils.composeRecentsLaunchAnimator(anim, taskView, apps, wallpaper, nonApps,
                     true /* launcherClosing */, getStateManager(), this,
-                    getDepthController());
+                    getDepthController(), transitionInfo);
         }
         anim.start();
     }
 
+    private boolean allAppsAreTranslucent(RemoteAnimationTarget[] apps) {
+        if (apps == null) {
+            return false;
+        }
+        for (int i = apps.length - 1; i >= 0; --i) {
+            if (!apps[i].isTranslucent) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     public boolean isTaskViewVisible(TaskView tv) {
         if (showAsGrid()) {
             int screenStart = getPagedOrientationHandler().getPrimaryScroll(this);
@@ -1432,7 +1508,7 @@
 
     @Nullable
     private TaskView getLastGridTaskView() {
-        return getLastGridTaskView(getTopRowIdArray(), getBottomRowIdArray());
+        return getLastGridTaskView(mUtils.getTopRowIdArray(), mUtils.getBottomRowIdArray());
     }
 
     @Nullable
@@ -1459,13 +1535,26 @@
         return clearAllScroll + (mIsRtl ? distance : -distance);
     }
 
+    /**
+     * Launch running task view if it is instance of DesktopTaskView.
+     * @return provides runnable list to attach runnable at end of Desktop Mode launch
+     */
+    @Nullable
+    public RunnableList launchRunningDesktopTaskView() {
+        TaskView taskView = getRunningTaskView();
+        if (taskView instanceof DesktopTaskView) {
+            return taskView.launchWithAnimation();
+        }
+        return null;
+    }
+
     /*
      * Returns if TaskView is within screen bounds defined in [screenStart, screenEnd].
      *
      * @param taskViewTranslation taskView is considered within bounds if either translated or
      * original position of taskView is within screen bounds.
      */
-    private boolean isTaskViewWithinBounds(TaskView taskView, int screenStart, int screenEnd,
+    protected boolean isTaskViewWithinBounds(TaskView taskView, int screenStart, int screenEnd,
             int taskViewTranslation) {
         int taskStart = getPagedOrientationHandler().getChildStart(taskView)
                 + (int) taskView.getOffsetAdjustment(showAsGrid());
@@ -1493,30 +1582,19 @@
     }
 
     /**
-     * Returns true if the task is in expected scroll position.
-     *
-     * @param taskIndex the index of the task
+     * Returns true if the given TaskView is in expected scroll position.
      */
-    public boolean isTaskInExpectedScrollPosition(int taskIndex) {
-        return getScrollForPage(taskIndex) == getPagedOrientationHandler().getPrimaryScroll(this);
-    }
-
-    private boolean isFocusedTaskInExpectedScrollPosition() {
-        TaskView focusedTask = getFocusedTaskView();
-        return focusedTask != null && isTaskInExpectedScrollPosition(indexOfChild(focusedTask));
+    public boolean isTaskInExpectedScrollPosition(TaskView taskView) {
+        return getScrollForPage(indexOfChild(taskView))
+                == getPagedOrientationHandler().getPrimaryScroll(this);
     }
 
     /**
-     * Launch DesktopTaskView if found.
-     * @return provides runnable list to attach runnable at end of Desktop Mode launch
+     * Returns true if the focused TaskView is in expected scroll position.
      */
-    public RunnableList launchDesktopTaskView() {
-        for (TaskView taskView : getTaskViews()) {
-            if (taskView instanceof DesktopTaskView) {
-                return taskView.launchWithAnimation();
-            }
-        }
-        return null;
+    public boolean isFocusedTaskInExpectedScrollPosition() {
+        TaskView focusedTask = getFocusedTaskView();
+        return focusedTask != null && isTaskInExpectedScrollPosition(focusedTask);
     }
 
     /**
@@ -1586,6 +1664,9 @@
             taskView.setBorderEnabled(enabled);
         }
         mClearAllButton.setBorderEnabled(enabled);
+        if (mAddDesktopButton != null) {
+            mAddDesktopButton.setBorderEnabled(enabled);
+        }
     }
 
     /**
@@ -1694,8 +1775,11 @@
                                 mClearAllButton.getAlpha() == 1
                                         && mClearAllButtonDeadZoneRect.contains(x, y);
                         final boolean cameFromNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
+                        int adjustedX = x + getScrollX();
                         if (!clearAllButtonDeadZoneConsumed && !cameFromNavBar
-                                && !mTaskViewDeadZoneRect.contains(x + getScrollX(), y)) {
+                                && !mTaskViewDeadZoneRect.contains(adjustedX, y)
+                                && !mTopRowDeadZoneRect.contains(adjustedX, y)
+                                && !mBottomRowDeadZoneRect.contains(adjustedX, y)) {
                             mTouchDownToStartHome = true;
                         }
                     }
@@ -1731,8 +1815,7 @@
                 }
                 TaskView taskView = getTaskViewAt(mNextPage);
                 boolean shouldSnapToLargeTask = taskView != null && taskView.isLargeTile()
-                        && !mUtils.isAnySmallTaskFullyVisible(getTaskViews(),
-                        this::isTaskViewFullyVisible);
+                        && !mUtils.isAnySmallTaskFullyVisible();
                 boolean shouldSnapToClearAll = mNextPage == indexOfChild(mClearAllButton);
                 // Snap to large tile when grid tasks aren't fully visible or the clear all button.
                 if (!shouldSnapToLargeTask && !shouldSnapToClearAll) {
@@ -1787,7 +1870,7 @@
             return;
         }
 
-        int runningTaskExpectedIndex = getRunningTaskExpectedIndex(runningTaskView);
+        int runningTaskExpectedIndex = mUtils.getRunningTaskExpectedIndex(runningTaskView);
         if (mCurrentPage == runningTaskExpectedIndex) {
             return;
         }
@@ -1807,25 +1890,6 @@
         updateTaskSize();
     }
 
-    private int getRunningTaskExpectedIndex(TaskView runningTaskView) {
-        if (mContainer.getDeviceProfile().isTablet) {
-            if (runningTaskView instanceof DesktopTaskView) {
-                return 0; // Desktop running task is always in front.
-            } else if (enableLargeDesktopWindowingTile()) {
-                return getDesktopTaskViewCount(); // Other running task is behind desktop tasks.
-            } else {
-                return 0;
-            }
-        } else {
-            int currentIndex = indexOfChild(runningTaskView);
-            if (currentIndex != -1) {
-                return currentIndex; // Keep the position if running task already in layout.
-            } else {
-                return 0; // New running task are added to the front to begin with.
-            }
-        }
-    }
-
     @Override
     protected void onScrollerAnimationAborted() {
         ActiveGestureProtoLogProxy.logOnScrollerAnimationAborted();
@@ -1851,7 +1915,7 @@
         }
         mLoadPlanEverApplied = true;
         if (taskGroups == null || taskGroups.isEmpty()) {
-            removeTasksViewsAndClearAllButton();
+            removeAllTaskViews();
             onTaskStackUpdated();
             // With all tasks removed, touch handling in PagedView is disabled and we need to reset
             // touch state or otherwise values will be obsolete.
@@ -1862,6 +1926,8 @@
             return;
         }
 
+        // TODO: b/400532675 - The use of `currentTaskIds`, `runningTaskIds`, and `focusedTaskIds`
+        // needs to be audited so that they can work with empty desks that have no tasks.
         int[] currentTaskIds;
         TaskView currentTaskView = getTaskViewAt(mCurrentPage);
         if (currentTaskView != null) {
@@ -1913,6 +1979,14 @@
         if (enableLargeDesktopWindowingTile()) {
             taskGroups = mUtils.sortDesktopTasksToFront(taskGroups);
         }
+        if (enableSeparateExternalDisplayTasks()) {
+            taskGroups = mUtils.sortExternalDisplayTasksToFront(taskGroups);
+        }
+
+        if (mAddDesktopButton != null) {
+            // Add `mAddDesktopButton` as the first child.
+            addView(mAddDesktopButton);
+        }
 
         // Add views as children based on whether it's grouped or single task. Looping through
         // taskGroups backwards populates the thumbnail grid from least recent to most recent.
@@ -1920,7 +1994,7 @@
             GroupTask groupTask = taskGroups.get(i);
             boolean containsStagedTask = stagedTaskIdToBeRemoved != INVALID_TASK_ID
                     && groupTask.containsTask(stagedTaskIdToBeRemoved);
-            boolean shouldSkipGroupTask = containsStagedTask && !groupTask.hasMultipleTasks();
+            boolean shouldSkipGroupTask = containsStagedTask && groupTask instanceof SingleTask;
 
             if ((isSplitSelectionActive() && groupTask.taskViewType == TaskViewType.DESKTOP)
                     || shouldSkipGroupTask) {
@@ -1934,25 +2008,22 @@
             // to be a temporary container for the remaining task.
             TaskView taskView = getTaskViewFromPool(
                     containsStagedTask ? TaskViewType.SINGLE : groupTask.taskViewType);
-            if (taskView instanceof GroupedTaskView) {
-                boolean firstTaskIsLeftTopTask =
-                        groupTask.mSplitBounds.leftTopTaskId == groupTask.task1.key.id;
-                Task leftTopTask = firstTaskIsLeftTopTask ? groupTask.task1 : groupTask.task2;
-                Task rightBottomTask = firstTaskIsLeftTopTask ? groupTask.task2 : groupTask.task1;
-                ((GroupedTaskView) taskView).bind(leftTopTask, rightBottomTask, mOrientationState,
-                        mTaskOverlayFactory, groupTask.mSplitBounds);
-            } else if (taskView instanceof DesktopTaskView) {
-                // Minimized tasks should not be shown in Overview
-                List<Task> nonMinimizedTasks =
-                        ((DesktopTask) groupTask).tasks.stream()
-                                .filter(task -> !task.isMinimized)
-                                .toList();
-                ((DesktopTaskView) taskView).bind(nonMinimizedTasks, mOrientationState,
+            if (taskView instanceof GroupedTaskView groupedTaskView) {
+                var splitTask = (SplitTask) groupTask;
+                groupedTaskView.bind(splitTask.getTopLeftTask(),
+                        splitTask.getBottomRightTask(), mOrientationState,
+                        mTaskOverlayFactory, splitTask.getSplitBounds());
+            } else if (taskView instanceof DesktopTaskView desktopTaskView) {
+                desktopTaskView.bind((DesktopTask) groupTask, mOrientationState,
                         mTaskOverlayFactory);
-            } else {
-                Task task = groupTask.task1.key.id == stagedTaskIdToBeRemoved ? groupTask.task2
-                        : groupTask.task1;
+            } else if (groupTask instanceof SplitTask splitTask) {
+                Task task = splitTask.getTopLeftTask().key.id == stagedTaskIdToBeRemoved
+                        ? splitTask.getBottomRightTask()
+                        : splitTask.getTopLeftTask();
                 taskView.bind(task, mOrientationState, mTaskOverlayFactory);
+            } else {
+                taskView.bind(((SingleTask) groupTask).getTask(), mOrientationState,
+                        mTaskOverlayFactory);
             }
             addView(taskView);
 
@@ -1962,9 +2033,7 @@
             }
         }
 
-        if (!taskGroups.isEmpty()) {
-            addView(mClearAllButton);
-        }
+        addView(mClearAllButton);
 
         // Keep same previous focused task
         TaskView newFocusedTaskView = null;
@@ -1976,7 +2045,7 @@
             }
             // If the list changed, maybe the focused task doesn't exist anymore.
             if (newFocusedTaskView == null) {
-                newFocusedTaskView = mUtils.getExpectedFocusedTask(getTaskViews());
+                newFocusedTaskView = mUtils.getFirstNonDesktopTaskView();
             }
         }
         setFocusedTaskViewId(
@@ -2020,8 +2089,7 @@
             targetPage = previousFocusedPage;
         } else {
             targetPage = indexOfChild(
-                    mUtils.getExpectedCurrentTask(newRunningTaskView, newFocusedTaskView,
-                            getTaskViews()));
+                    mUtils.getExpectedCurrentTask(newRunningTaskView, newFocusedTaskView));
         }
         if (targetPage != -1 && mCurrentPage != targetPage) {
             int finalTargetPage = targetPage;
@@ -2062,35 +2130,30 @@
         return mModel.isLoadingTasksInBackground();
     }
 
-    private void removeTasksViewsAndClearAllButton() {
-        for (TaskView taskView : getTaskViews()) {
-            if (isGestureActive() && taskView.isRunningTask()) {
-                // This handles an edge case where applyLoadPlan happens during a gesture when the
-                // only Task is one with excludeFromRecents, in which case we should not remove it.
-                continue;
-            }
-            removeView(taskView);
-        }
-        if (getTaskViewCount() == 0 && indexOfChild(mClearAllButton) != -1) {
+    private void removeAllTaskViews() {
+        // This handles an edge case where applyLoadPlan happens during a gesture when the only
+        // Task is one with excludeFromRecents, in which case we should not remove it.
+        CollectionsKt
+                .filter(getTaskViews(), taskView -> !isGestureActive() || !taskView.isRunningTask())
+                .forEach(this::removeView);
+        if (!hasTaskViews()) {
+            removeView(mAddDesktopButton);
             removeView(mClearAllButton);
         }
     }
 
-    public int getTaskViewCount() {
-        int taskViewCount = getChildCount();
-        if (indexOfChild(mClearAllButton) != -1) {
-            taskViewCount--;
-        }
-        return taskViewCount;
+    /** Returns true if there are at least one TaskView has been added to the RecentsView. */
+    public boolean hasTaskViews() {
+        return mUtils.hasTaskViews();
     }
 
-    /**
-     * Transverse RecentsView children to calculate the amount of DesktopTaskViews.
-     *
-     * @return Number of children that are instances of DesktopTaskView
-     */
-    private int getDesktopTaskViewCount() {
-        return mUtils.getDesktopTaskViewCount(getTaskViews());
+    public int getTaskViewCount() {
+        return mTaskViewCount;
+    }
+
+    /** Counts {@link TaskView}s that are not {@link DesktopTaskView} instances. */
+    public int getNonDesktopTaskViewCount() {
+        return mUtils.getNonDesktopTaskViewCount();
     }
 
     /**
@@ -2149,9 +2212,6 @@
 
     public void setFullscreenProgress(float fullscreenProgress) {
         mFullscreenProgress = fullscreenProgress;
-        if (enableRefactorTaskThumbnail()) {
-            mRecentsViewModel.updateFullscreenProgress(mFullscreenProgress);
-        }
         for (TaskView taskView : getTaskViews()) {
             taskView.setFullscreenProgress(mFullscreenProgress);
         }
@@ -2268,11 +2328,6 @@
         mSizeStrategy.calculateGridTaskSize(mContainer, dp, mLastComputedGridTaskSize,
                 getPagedOrientationHandler());
 
-        if (enableGridOnlyOverview()) {
-            mSizeStrategy.calculateCarouselTaskSize(mContainer, dp, mLastComputedCarouselTaskSize,
-                    getPagedOrientationHandler());
-        }
-
         mTaskGridVerticalDiff = mLastComputedGridTaskSize.top - mLastComputedTaskSize.top;
         mTopBottomRowHeightDiff =
                 mLastComputedGridTaskSize.height() + dp.overviewTaskThumbnailTopMarginPx
@@ -2287,23 +2342,14 @@
      * Updates TaskView scaling and translation required to support variable width.
      */
     private void updateTaskSize() {
-        final int taskCount = getTaskViewCount();
-        if (taskCount == 0) {
+        if (!hasTaskViews()) {
             return;
         }
 
         float accumulatedTranslationX = 0;
-        float translateXToMiddle = 0;
-        if (enableGridOnlyOverview() && mContainer.getDeviceProfile().isTablet) {
-            translateXToMiddle = mIsRtl
-                    ? mLastComputedCarouselTaskSize.right - mLastComputedTaskSize.right
-                    : mLastComputedCarouselTaskSize.left - mLastComputedTaskSize.left;
-        }
         for (TaskView taskView : getTaskViews()) {
-            taskView.updateTaskSize(mLastComputedTaskSize, mLastComputedGridTaskSize,
-                    mLastComputedCarouselTaskSize);
+            taskView.updateTaskSize(mLastComputedTaskSize, mLastComputedGridTaskSize);
             taskView.setNonGridTranslationX(accumulatedTranslationX);
-            taskView.setNonGridPivotTranslationX(translateXToMiddle);
             // Compensate space caused by TaskView scaling.
             float widthDiff =
                     taskView.getLayoutParams().width * (1 - taskView.getNonGridScale());
@@ -2312,6 +2358,12 @@
 
         mClearAllButton.setFullscreenTranslationPrimary(accumulatedTranslationX);
 
+        float taskAlignmentTranslationY = getTaskAlignmentTranslationY();
+        mClearAllButton.setTaskAlignmentTranslationY(taskAlignmentTranslationY);
+        if (mAddDesktopButton != null) {
+            mAddDesktopButton.setTranslationY(taskAlignmentTranslationY);
+        }
+
         updateGridProperties();
     }
 
@@ -2332,17 +2384,32 @@
      */
     public Rect getSelectedTaskBounds() {
         if (mSelectedTask == null) {
-            return mLastComputedTaskSize;
+            return enableGridOnlyOverview() && mContainer.getDeviceProfile().isTablet
+                    ? mLastComputedGridTaskSize : mLastComputedTaskSize;
         }
         return getTaskBounds(mSelectedTask);
     }
 
+    /**
+     * Get the Y translation that should be applied to the non-TaskView item inside the RecentsView
+     * (ClearAllButton and AddDesktopButton) in the original layout position, before scrolling. This
+     * is done to make sure the button is aligned to the middle of Task thumbnail in y coordinate.
+     */
+    private float getTaskAlignmentTranslationY() {
+        DeviceProfile deviceProfile = mContainer.getDeviceProfile();
+        if (deviceProfile.isTablet) {
+            return deviceProfile.overviewRowSpacing;
+        }
+        return deviceProfile.overviewTaskThumbnailTopMarginPx / 2.0f;
+    }
+
     private Rect getTaskBounds(TaskView taskView) {
         int selectedPage = indexOfChild(taskView);
         int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this);
         int selectedPageScroll = getScrollForPage(selectedPage);
-        boolean isTopRow = taskView != null && mTopRowIdSet.contains(taskView.getTaskViewId());
-        Rect outRect = new Rect(mLastComputedTaskSize);
+        boolean isTopRow = mTopRowIdSet.contains(taskView.getTaskViewId());
+        Rect outRect = new Rect(
+                taskView.isGridTask() ? mLastComputedGridTaskSize : mLastComputedTaskSize);
         outRect.offset(
                 -(primaryScroll - (selectedPageScroll + getOffsetFromScrollPosition(selectedPage))),
                 (int) (showAsGrid() && enableGridOnlyOverview() && !isTopRow
@@ -2359,10 +2426,6 @@
         return mLastComputedGridTaskSize;
     }
 
-    public Rect getLastComputedCarouselTaskSize() {
-        return mLastComputedCarouselTaskSize;
-    }
-
     /** Gets the task size for modal state. */
     public void getModalTaskSize(Rect outRect) {
         mSizeStrategy.calculateModalTaskSize(mContainer, mContainer.getDeviceProfile(), outRect,
@@ -2446,6 +2509,11 @@
         int minDistanceFromScreenStart = Integer.MAX_VALUE;
         int minDistanceFromScreenStartIndex = INVALID_PAGE;
         for (int i = 0; i < getChildCount(); ++i) {
+            // Do not set the destination page to the AddDesktopButton, which has the same page
+            // scrolls as the first [TaskView] and shouldn't be scrolled to.
+            if (getChildAt(i) instanceof AddDesktopButton) {
+                continue;
+            }
             int distanceFromScreenStart = Math.abs(mPageScrolls[i] - scaledScroll);
             if (distanceFromScreenStart < minDistanceFromScreenStart) {
                 minDistanceFromScreenStart = distanceFromScreenStart;
@@ -2467,41 +2535,39 @@
             return;
         }
 
-        int lower = 0;
-        int upper = 0;
-        int visibleStart = 0;
-        int visibleEnd = 0;
+        int lowerIndex, upperIndex, visibleStart, visibleEnd;
         if (showAsGrid()) {
             int screenStart = getPagedOrientationHandler().getPrimaryScroll(this);
             int pageOrientedSize = getPagedOrientationHandler().getMeasuredSize(this);
             // For GRID_ONLY_OVERVIEW, use +/- 1 task column as visible area for preloading
             // adjacent thumbnails, otherwise use +/-50% screen width
-            int extraWidth = enableGridOnlyOverview() ? getLastComputedTaskSize().width()
-                    + getPageSpacing() : pageOrientedSize / 2;
+            int extraWidth =
+                    enableGridOnlyOverview() ? getLastComputedTaskSize().width() + getPageSpacing()
+                            : pageOrientedSize / 2;
+            lowerIndex = upperIndex = 0;
             visibleStart = screenStart - extraWidth;
             visibleEnd = screenStart + pageOrientedSize + extraWidth;
         } else {
             int centerPageIndex = getPageNearestToCenterOfScreen();
             int numChildren = getChildCount();
-            lower = Math.max(0, centerPageIndex - 2);
-            upper = Math.min(centerPageIndex + 2, numChildren - 1);
+            lowerIndex = Math.max(0, centerPageIndex - 2);
+            upperIndex = Math.min(centerPageIndex + 2, numChildren - 1);
+            visibleStart = visibleEnd = 0;
         }
 
         List<Integer> visibleTaskIds = new ArrayList<>();
-
         // Update the task data for the in/visible children
-        for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView taskView = requireTaskViewAt(i);
+        getTaskViews().forEachWithIndexInParent((index, taskView) -> {
             List<TaskContainer> containers = taskView.getTaskContainers();
             if (containers.isEmpty()) {
-                continue;
+                return;
             }
             boolean visible;
             if (showAsGrid()) {
                 visible = isTaskViewWithinBounds(taskView, visibleStart, visibleEnd,
-                        mDismissPrimaryTranslations != null ? mDismissPrimaryTranslations[i] : 0);
+                        mTaskViewsDismissPrimaryTranslations.getOrDefault(taskView, 0));
             } else {
-                visible = lower <= i && i <= upper;
+                visible = index >= lowerIndex && index <= upperIndex;
             }
             if (visible) {
                 // Default update all non-null tasks, then remove running ones
@@ -2513,7 +2579,7 @@
                             tasksToUpdate.stream().map((task) -> task.key.id).toList());
                 }
                 if (tasksToUpdate.isEmpty()) {
-                    continue;
+                    return;
                 }
                 int visibilityChanges = 0;
                 for (Task task : tasksToUpdate) {
@@ -2547,7 +2613,7 @@
                     taskView.onTaskListVisibilityChanged(false /* visible */, visibilityChanges);
                 }
             }
-        }
+        });
         if (enableRefactorTaskThumbnail()) {
             mRecentsViewModel.updateVisibleTasks(visibleTaskIds);
         }
@@ -2620,7 +2686,6 @@
         setFocusedTaskViewId(INVALID_TASK_ID);
         mAnyTaskHasBeenDismissed = false;
 
-
         if (enableRefactorTaskThumbnail()) {
             // TODO(b/353917593): RecentsView is never destroyed, so its dependencies need to
             //  be cleaned up during the reset, but re-created when RecentsView is "resumed".
@@ -2642,9 +2707,6 @@
         }
         runActionOnRemoteHandles(remoteTargetHandle ->
                 remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false));
-        if (!FeatureFlags.enableSplitContextually()) {
-            resetFromSplitSelectionState();
-        }
 
         // These are relatively expensive and don't need to be done this frame (RecentsView isn't
         // visible anyway), so defer by a frame to get off the critical path, e.g. app to home.
@@ -2652,15 +2714,15 @@
     }
 
     private void onReset() {
-        if (enableRefactorTaskThumbnail()) {
-            mRecentsViewModel.onReset();
-        }
         unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
         setCurrentPage(0);
         LayoutUtils.setViewEnabled(mActionsView, true);
         if (mOrientationState.setGestureActive(false)) {
             updateOrientationHandler(/* forceRecreateDragLayerControllers = */ false);
         }
+        if (enableRefactorTaskThumbnail()) {
+            mRecentsViewModel.onReset();
+        }
     }
 
     public int getRunningTaskViewId() {
@@ -2690,7 +2752,7 @@
     }
 
     @Nullable
-    private TaskView getTaskViewFromTaskViewId(int taskViewId) {
+    TaskView getTaskViewFromTaskViewId(int taskViewId) {
         if (taskViewId == -1) {
             return null;
         }
@@ -2768,14 +2830,16 @@
     /**
      * Called when a gesture from an app is starting.
      */
-    public void onGestureAnimationStart(
-            Task[] runningTasks, RotationTouchHelper rotationTouchHelper) {
+    // TODO: b/401582344 - Implement a way to exclude the `DesktopWallpaperActivity` from being
+    //  considered in Overview.
+    public void onGestureAnimationStart(Task[] runningTasks) {
         Log.d(TAG, "onGestureAnimationStart - runningTasks: " + Arrays.toString(runningTasks));
         mActiveGestureRunningTasks = runningTasks;
+
+
         // This needs to be called before the other states are set since it can create the task view
         if (mOrientationState.setGestureActive(true)) {
-            setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
-                    rotationTouchHelper.getDisplayRotation());
+            reapplyActiveRotation();
             // Force update to ensure the initial task size is computed even if the orientation has
             // not changed.
             updateSizeAndPadding();
@@ -2852,10 +2916,10 @@
         if (!shouldRotateMenuForFakeRotation) {
             return;
         }
-        TaskMenuView tv = (TaskMenuView) getTopOpenViewWithType(mContainer, TYPE_TASK_MENU);
-        if (tv != null) {
+        AbstractFloatingView floatingView = getTopOpenViewWithType(mContainer, TYPE_TASK_MENU);
+        if (floatingView instanceof TaskMenuView taskMenuView) {
             // Rotation is supported on phone (details at b/254198019#comment4)
-            tv.onRotationChanged();
+            taskMenuView.onRotationChanged();
         }
     }
 
@@ -2864,7 +2928,7 @@
      */
     public void onPrepareGestureEndAnimation(
             @Nullable AnimatorSet animatorSet, GestureState.GestureEndTarget endTarget,
-            TaskViewSimulator[] taskViewSimulators) {
+            RemoteTargetHandle[] remoteTargetHandles) {
         Log.d(TAG, "onPrepareGestureEndAnimation - endTarget: " + endTarget);
         mCurrentGestureEndTarget = endTarget;
         boolean isOverviewEndTarget = endTarget == GestureState.GestureEndTarget.RECENTS;
@@ -2873,31 +2937,59 @@
         }
 
         BaseState<?> endState = mSizeStrategy.stateFromGestureEndTarget(endTarget);
+        // Starting the desk exploded animation when the gesture from an app is released.
+        if (enableDesktopExplodedView()) {
+            if (animatorSet == null) {
+                mUtils.setDeskExplodeProgress(endState.showExplodedDesktopView() ? 1f : 0f);
+            } else {
+                animatorSet.play(
+                        ObjectAnimator.ofFloat(this, DESK_EXPLODE_PROGRESS,
+                                endState.showExplodedDesktopView() ? 1f : 0f));
+            }
+
+            for (TaskView taskView : getTaskViews()) {
+                if (taskView instanceof DesktopTaskView desktopTaskView) {
+                    desktopTaskView.setRemoteTargetHandles(remoteTargetHandles);
+                }
+            }
+        }
+
         if (endState.displayOverviewTasksAsGrid(mContainer.getDeviceProfile())) {
             TaskView runningTaskView = getRunningTaskView();
-            float runningTaskPrimaryGridTranslation = 0;
-            float runningTaskSecondaryGridTranslation = 0;
+            float runningTaskGridTranslationX = 0;
+            float runningTaskGridTranslationY = 0;
             if (runningTaskView != null) {
                 // Apply the grid translation to running task unless it's being snapped to
                 // and removes the current translation applied to the running task.
-                runningTaskPrimaryGridTranslation = runningTaskView.getGridTranslationX()
+                runningTaskGridTranslationX = runningTaskView.getGridTranslationX()
                         - runningTaskView.getNonGridTranslationX();
-                runningTaskSecondaryGridTranslation = runningTaskView.getGridTranslationY();
+                runningTaskGridTranslationY = runningTaskView.getGridTranslationY();
             }
-            for (TaskViewSimulator tvs : taskViewSimulators) {
+            for (RemoteTargetHandle remoteTargetHandle : remoteTargetHandles) {
+                TaskViewSimulator tvs = remoteTargetHandle.getTaskViewSimulator();
                 if (animatorSet == null) {
                     setGridProgress(1);
-                    tvs.taskPrimaryTranslation.value = runningTaskPrimaryGridTranslation;
-                    tvs.taskSecondaryTranslation.value = runningTaskSecondaryGridTranslation;
+                    if (enableGridOnlyOverview()) {
+                        tvs.taskGridTranslationX.value = runningTaskGridTranslationX;
+                        tvs.taskGridTranslationY.value = runningTaskGridTranslationY;
+                    } else {
+                        tvs.taskPrimaryTranslation.value = runningTaskGridTranslationX;
+                        tvs.taskSecondaryTranslation.value = runningTaskGridTranslationY;
+                    }
                 } else {
                     animatorSet.play(ObjectAnimator.ofFloat(this, RECENTS_GRID_PROGRESS, 1));
-                    animatorSet.play(tvs.carouselScale.animateToValue(1));
-                    animatorSet.play(tvs.carouselPrimaryTranslation.animateToValue(0));
-                    animatorSet.play(tvs.carouselSecondaryTranslation.animateToValue(0));
-                    animatorSet.play(tvs.taskPrimaryTranslation.animateToValue(
-                            runningTaskPrimaryGridTranslation));
-                    animatorSet.play(tvs.taskSecondaryTranslation.animateToValue(
-                            runningTaskSecondaryGridTranslation));
+                    if (enableGridOnlyOverview()) {
+                        animatorSet.play(tvs.carouselScale.animateToValue(1));
+                        animatorSet.play(tvs.taskGridTranslationX.animateToValue(
+                                runningTaskGridTranslationX));
+                        animatorSet.play(tvs.taskGridTranslationY.animateToValue(
+                                runningTaskGridTranslationY));
+                    } else {
+                        animatorSet.play(tvs.taskPrimaryTranslation.animateToValue(
+                                runningTaskGridTranslationX));
+                        animatorSet.play(tvs.taskSecondaryTranslation.animateToValue(
+                                runningTaskGridTranslationY));
+                    }
                 }
             }
         }
@@ -2934,6 +3026,27 @@
         startIconFadeInOnGestureComplete();
         animateActionsViewIn();
 
+        if (mEnableDrawingLiveTile) {
+            if (enableDesktopExplodedView()) {
+                for (TaskView taskView : getTaskViews()) {
+                    if (taskView instanceof DesktopTaskView desktopTaskView) {
+                        desktopTaskView.setRemoteTargetHandles(mRemoteTargetHandles);
+                    }
+                }
+            }
+            TaskView runningTaskView = getRunningTaskView();
+            if (showAsGrid() && enableGridOnlyOverview() && runningTaskView != null) {
+                runActionOnRemoteHandles(remoteTargetHandle -> {
+                    TaskViewSimulator taskViewSimulator = remoteTargetHandle.getTaskViewSimulator();
+                    // After settling in Overview, recentsScroll will be used to adjust horizontally
+                    // location and taskGridTranslationX doesn't needs to be applied.
+                    taskViewSimulator.taskGridTranslationX.value = 0;
+                    taskViewSimulator.taskGridTranslationY.value =
+                            runningTaskView.getGridTranslationY();
+                });
+            }
+        }
+
         mCurrentGestureEndTarget = null;
     }
 
@@ -2957,6 +3070,21 @@
     }
 
     /**
+     * Creates a `DesktopTaskView` for the currently active desk on this display, which contains the
+     * gievn `runningTasks`.
+     */
+    private DesktopTaskView createDesktopTaskViewForActiveDesk(Task[] runningTasks) {
+        final int activeDeskId = mUtils.getActiveDeskIdOnThisDisplay();
+        final var desktopTaskView = (DesktopTaskView) getTaskViewFromPool(TaskViewType.DESKTOP);
+
+        // TODO: b/401582344 - Implement a way to exclude the `DesktopWallpaperActivity`.
+        desktopTaskView.bind(
+                new DesktopTask(activeDeskId, Arrays.asList(runningTasks)),
+                mOrientationState, mTaskOverlayFactory);
+        return desktopTaskView;
+    }
+
+    /**
      * Creates a task view (if necessary) to represent the task with the {@param runningTaskId}.
      *
      * All subsequent calls to reload will keep the task as the first item until {@link #reset()}
@@ -2970,16 +3098,14 @@
         }
 
         int runningTaskViewId = -1;
-        boolean needGroupTaskView = runningTasks.length > 1;
-        boolean needDesktopTask = hasDesktopTask(runningTasks);
         if (shouldAddStubTaskView(runningTasks)) {
             boolean wasEmpty = getChildCount() == 0;
             // Add an empty view for now until the task plan is loaded and applied
             final TaskView taskView;
+            final boolean needGroupTaskView = runningTasks.length > 1;
+            final boolean needDesktopTask = hasDesktopTask(runningTasks);
             if (needDesktopTask) {
-                taskView = getTaskViewFromPool(TaskViewType.DESKTOP);
-                ((DesktopTaskView) taskView).bind(Arrays.asList(runningTasks),
-                        mOrientationState, mTaskOverlayFactory);
+                taskView = createDesktopTaskViewForActiveDesk(runningTasks);
             } else if (needGroupTaskView) {
                 taskView = getTaskViewFromPool(TaskViewType.GROUPED);
                 // When we create a placeholder task view mSplitBoundsConfig will be null, but with
@@ -2991,7 +3117,10 @@
                 taskView = getTaskViewFromPool(TaskViewType.SINGLE);
                 taskView.bind(runningTasks[0], mOrientationState, mTaskOverlayFactory);
             }
-            addView(taskView, getRunningTaskExpectedIndex(taskView));
+            if (mAddDesktopButton != null && wasEmpty) {
+                addView(mAddDesktopButton);
+            }
+            addView(taskView, mUtils.getRunningTaskExpectedIndex(taskView));
             runningTaskViewId = taskView.getTaskViewId();
             if (wasEmpty) {
                 addView(mClearAllButton);
@@ -3002,8 +3131,11 @@
             measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
                     makeMeasureSpec(getMeasuredHeight(), EXACTLY));
             layout(getLeft(), getTop(), getRight(), getBottom());
-        } else if (getTaskViewByTaskId(runningTasks[0].key.id) != null) {
-            runningTaskViewId = getTaskViewByTaskId(runningTasks[0].key.id).getTaskViewId();
+        } else {
+            var runningTaskView = getTaskViewByTaskId(runningTasks[0].key.id);
+            if (runningTaskView != null) {
+                runningTaskViewId = runningTaskView.getTaskViewId();
+            }
         }
 
         boolean runningTaskTileHidden = mRunningTaskTileHidden;
@@ -3014,7 +3146,7 @@
             focusedTaskViewId = INVALID_TASK_ID;
         } else if (enableLargeDesktopWindowingTile()
                 && getRunningTaskView() instanceof DesktopTaskView) {
-            TaskView focusedTaskView = getTaskViewAt(getDesktopTaskViewCount());
+            TaskView focusedTaskView = mUtils.getFirstNonDesktopTaskView();
             focusedTaskViewId =
                     focusedTaskView != null ? focusedTaskView.getTaskViewId() : INVALID_TASK_ID;
         } else {
@@ -3042,6 +3174,10 @@
                 return true;
             }
         }
+
+        // A running empty desk will have a single running app for the `DesktopWallpaperActivity`.
+        // TODO: b/401582344 - Implement a way to exclude the `DesktopWallpaperActivity`.
+
         return false;
     }
 
@@ -3104,7 +3240,7 @@
     private void applyAttachAlpha() {
         // Only hide non running task carousel when it's fully off screen, otherwise it needs to
         // be visible to move to on screen.
-        mUtils.applyAttachAlpha(getTaskViews(), getRunningTaskView(), mRunningTaskAttachAlpha,
+        mUtils.applyAttachAlpha(
                 /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress == 1f);
     }
 
@@ -3159,7 +3295,7 @@
      * Skips rebalance.
      */
     private void updateGridProperties() {
-        updateGridProperties(Integer.MAX_VALUE);
+        updateGridProperties(null);
     }
 
     /**
@@ -3169,12 +3305,11 @@
      * This method only calculates the potential position and depends on {@link #setGridProgress} to
      * apply the actual scaling and translation.
      *
-     * @param startRebalanceAfter which view index to start rebalancing from. Use Integer.MAX_VALUE
-     *                            to skip rebalance
+     * @param lastVisibleTaskViewDuringDismiss which TaskView to start rebalancing from. Use
+     *                                         `null` to skip rebalance.
      */
-    private void updateGridProperties(int startRebalanceAfter) {
-        int taskCount = getTaskViewCount();
-        if (taskCount == 0) {
+    private void updateGridProperties(TaskView lastVisibleTaskViewDuringDismiss) {
+        if (!hasTaskViews()) {
             return;
         }
 
@@ -3183,78 +3318,97 @@
 
         int topRowWidth = 0;
         int bottomRowWidth = 0;
+        int largeTileRowWidth = 0;
         float topAccumulatedTranslationX = 0;
         float bottomAccumulatedTranslationX = 0;
 
-        // Contains whether the child index is in top or bottom of grid (for non-focused task)
-        // Different from mTopRowIdSet, which contains the taskViewId of what task is in top row
-        IntSet topSet = new IntSet();
-        IntSet bottomSet = new IntSet();
+        // Horizontal grid translation for each task.
+        Map<TaskView, Float> gridTranslations = new HashMap<>();
 
-        // Horizontal grid translation for each task
-        float[] gridTranslations = new float[taskCount];
-
-        TaskView lastLargeTaskView = mUtils.getLastLargeTaskView(getTaskViews());
-        int lastLargeTaskIndex =
-                (lastLargeTaskView == null) ? Integer.MAX_VALUE : indexOfChild(lastLargeTaskView);
-        Set<Integer> largeTasksIndices = new HashSet<>();
-        int focusedTaskShift = 0;
+        TaskView lastLargeTaskView = mUtils.getLastLargeTaskView();
+        int focusedTaskViewShift = 0;
         int largeTaskWidthAndSpacing = 0;
         int snappedTaskRowWidth = 0;
+        int expectedCurrentTaskRowWidth = 0;
         int snappedPage = isKeyboardTaskFocusPending() ? mKeyboardTaskFocusIndex : getNextPage();
         TaskView snappedTaskView = getTaskViewAt(snappedPage);
         TaskView homeTaskView = getHomeTaskView();
+        TaskView expectedCurrentTaskView = mUtils.getExpectedCurrentTask(getFocusedTaskView(),
+                getRunningTaskView());
         TaskView nextFocusedTaskView = null;
 
         // Don't clear the top row, if the user has dismissed a task, to maintain the task order.
         if (!mAnyTaskHasBeenDismissed) {
             mTopRowIdSet.clear();
         }
-        for (int i = 0; i < taskCount; i++) {
-            TaskView taskView = requireTaskViewAt(i);
+
+        // Consecutive task views in the top row or bottom row, which means another one set will
+        // be cleared up while starting to add TaskViews to one of them. Also means only one of
+        // them can be non-empty at most.
+        Set<TaskView> lastTopTaskViews = new HashSet<>();
+        Set<TaskView> lastBottomTaskViews = new HashSet<>();
+
+        int largeTasksCount = 0;
+        // True if the last large TaskView has been visited during the TaskViews iteration.
+        boolean encounteredLastLargeTaskView = false;
+        // True if the highest index visible TaskView has been visited during the TaskViews
+        // iteration.
+        boolean encounteredLastVisibleTaskView = false;
+        for (TaskView taskView : getTaskViews()) {
+            if (taskView == lastLargeTaskView) {
+                encounteredLastLargeTaskView = true;
+            }
+            if (taskView == lastVisibleTaskViewDuringDismiss) {
+                encounteredLastVisibleTaskView = true;
+            }
+            float gridTranslation = 0f;
             int taskWidthAndSpacing = taskView.getLayoutParams().width + mPageSpacing;
             // Evenly distribute tasks between rows unless rearranging due to task dismissal, in
             // which case keep tasks in their respective rows. For the running task, don't join
             // the grid.
-            boolean isLargeTile = taskView.isLargeTile();
-
-            if (isLargeTile) {
+            if (taskView.isLargeTile()) {
+                largeTasksCount++;
                 // DesktopTaskView`s are hidden during split select state, so we shouldn't count
                 // them when calculating row width.
                 if (!(taskView instanceof DesktopTaskView && isSplitSelectionActive())) {
                     topRowWidth += taskWidthAndSpacing;
                     bottomRowWidth += taskWidthAndSpacing;
+                    largeTileRowWidth += taskWidthAndSpacing;
                 }
-                gridTranslations[i] += focusedTaskShift;
-                gridTranslations[i] += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
+                gridTranslation += focusedTaskViewShift;
+                gridTranslation += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
 
                 // Center view vertically in case it's from different orientation.
                 taskView.setGridTranslationY((mLastComputedTaskSize.height() + taskTopMargin
                         - taskView.getLayoutParams().height) / 2f);
 
-                largeTasksIndices.add(i);
                 largeTaskWidthAndSpacing = taskWidthAndSpacing;
 
                 if (taskView == snappedTaskView) {
-                    // If focused task is snapped, the row width is just task width and spacing.
-                    snappedTaskRowWidth = taskWidthAndSpacing;
+                    snappedTaskRowWidth = largeTileRowWidth;
+                }
+                if (taskView == expectedCurrentTaskView) {
+                    expectedCurrentTaskRowWidth = largeTileRowWidth;
                 }
             } else {
-                if (i > lastLargeTaskIndex) {
+                if (encounteredLastLargeTaskView) {
                     // For tasks after the last large task, shift by large task's width and spacing.
-                    gridTranslations[i] +=
+                    gridTranslation +=
                             mIsRtl ? largeTaskWidthAndSpacing : -largeTaskWidthAndSpacing;
                 } else {
-                    // For task before the focused task, accumulate the width and spacing to
-                    // calculate the distance focused task need to shift.
-                    focusedTaskShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
+                    // For TaskViews before the new focused TaskView, accumulate the width and
+                    // spacing to calculate the distance the new focused TaskView needs to shift.
+                    // This could happen for example after multiple times of dismissing the
+                    // focused TaskView, the triggered rebalance might set a non-first TaskView
+                    // inside `mChildren` as the new focused TaskView.
+                    focusedTaskViewShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
                 }
                 int taskViewId = taskView.getTaskViewId();
 
-                // Rebalance the grid starting after a certain index
                 boolean isTopRow;
                 if (mAnyTaskHasBeenDismissed) {
-                    if (i > startRebalanceAfter) {
+                    // Rebalance the grid starting after a certain index.
+                    if (encounteredLastVisibleTaskView) {
                         mTopRowIdSet.remove(taskViewId);
                         isTopRow = topRowWidth <= bottomRowWidth;
                     } else {
@@ -3271,47 +3425,47 @@
                     } else {
                         topRowWidth += taskWidthAndSpacing;
                     }
-                    topSet.add(i);
                     mTopRowIdSet.add(taskViewId);
-
                     taskView.setGridTranslationY(mTaskGridVerticalDiff);
 
                     // Move horizontally into empty space.
                     float widthOffset = 0;
-                    for (int j = i - 1; !topSet.contains(j) && j >= 0; j--) {
-                        if (largeTasksIndices.contains(j)) {
-                            continue;
-                        }
-                        widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing;
+                    for (TaskView bottomTaskView : lastBottomTaskViews) {
+                        widthOffset += bottomTaskView.getLayoutParams().width + mPageSpacing;
                     }
 
                     float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset;
-                    gridTranslations[i] += topAccumulatedTranslationX + currentTaskTranslationX;
+                    gridTranslation += topAccumulatedTranslationX + currentTaskTranslationX;
                     topAccumulatedTranslationX += currentTaskTranslationX;
+                    lastTopTaskViews.add(taskView);
+                    lastBottomTaskViews.clear();
                 } else {
                     bottomRowWidth += taskWidthAndSpacing;
-                    bottomSet.add(i);
 
                     // Move into bottom row.
                     taskView.setGridTranslationY(mTopBottomRowHeightDiff + mTaskGridVerticalDiff);
 
                     // Move horizontally into empty space.
                     float widthOffset = 0;
-                    for (int j = i - 1; !bottomSet.contains(j) && j >= 0; j--) {
-                        if (largeTasksIndices.contains(j)) {
-                            continue;
-                        }
-                        widthOffset += requireTaskViewAt(j).getLayoutParams().width + mPageSpacing;
+                    for (TaskView topTaskView : lastTopTaskViews) {
+                        widthOffset += topTaskView.getLayoutParams().width + mPageSpacing;
                     }
 
                     float currentTaskTranslationX = mIsRtl ? widthOffset : -widthOffset;
-                    gridTranslations[i] += bottomAccumulatedTranslationX + currentTaskTranslationX;
+                    gridTranslation += bottomAccumulatedTranslationX + currentTaskTranslationX;
                     bottomAccumulatedTranslationX += currentTaskTranslationX;
+                    lastBottomTaskViews.add(taskView);
+                    lastTopTaskViews.clear();
                 }
+                int taskViewRowWidth = isTopRow ? topRowWidth : bottomRowWidth;
                 if (taskView == snappedTaskView) {
-                    snappedTaskRowWidth = isTopRow ? topRowWidth : bottomRowWidth;
+                    snappedTaskRowWidth = taskViewRowWidth;
+                }
+                if (taskView == expectedCurrentTaskView) {
+                    expectedCurrentTaskRowWidth = taskViewRowWidth;
                 }
             }
+            gridTranslations.put(taskView, gridTranslation);
         }
 
         // We need to maintain snapped task's page scroll invariant between quick switch and
@@ -3322,22 +3476,22 @@
         if (snappedTaskView != null) {
             snappedTaskNonGridScrollAdjustment = snappedTaskView.getScrollAdjustment(
                     /*gridEnabled=*/false);
-            snappedTaskGridTranslationX = gridTranslations[snappedPage];
+            snappedTaskGridTranslationX = gridTranslations.getOrDefault(snappedTaskView, 0f);
         }
 
         // Use the accumulated translation of the row containing the last task.
-        float clearAllAccumulatedTranslation = topSet.contains(taskCount - 1)
+        float clearAllAccumulatedTranslation = !lastTopTaskViews.isEmpty()
                 ? topAccumulatedTranslationX : bottomAccumulatedTranslationX;
 
         // If the last task is on the shorter row, ClearAllButton will embed into the shorter row
         // which is not what we want. Compensate the width difference of the 2 rows in that case.
         float shorterRowCompensation = 0;
         if (topRowWidth <= bottomRowWidth) {
-            if (topSet.contains(taskCount - 1)) {
+            if (!lastTopTaskViews.isEmpty()) {
                 shorterRowCompensation = bottomRowWidth - topRowWidth;
             }
         } else {
-            if (bottomSet.contains(taskCount - 1)) {
+            if (!lastBottomTaskViews.isEmpty()) {
                 shorterRowCompensation = topRowWidth - bottomRowWidth;
             }
         }
@@ -3349,18 +3503,16 @@
         float clearAllShortTotalWidthTranslation = 0;
         int longRowWidth = Math.max(topRowWidth, bottomRowWidth);
 
-        // If Recents contains only large task sizes, it should only consider 1 large size
-        // for ClearAllButton translation. The space at the left side of the large task will be
-        // empty and it should be move ClearAllButton further away as well.
-        // TODO(b/359573248): Validate the translation for ClearAllButton for grid only.
-        boolean hasOnlyLargeTasks = taskCount == largeTasksIndices.size();
-        if (enableLargeDesktopWindowingTile() && hasOnlyLargeTasks) {
-            longRowWidth = largeTaskWidthAndSpacing;
-        }
-
         // If first task is not in the expected position (mLastComputedTaskSize) and being too close
         // to ClearAllButton, then apply extra translation to ClearAllButton.
-        int firstTaskStart = mLastComputedGridSize.left + longRowWidth;
+        int rowWidthAfterExpectedCurrentTask = longRowWidth - expectedCurrentTaskRowWidth;
+        int expectedCurrentTaskWidthAndSpacing =
+                (expectedCurrentTaskView != null
+                        ? expectedCurrentTaskView.getLayoutParams().width
+                        : 0
+                ) + mPageSpacing;
+        int firstTaskStart = mLastComputedGridSize.left + rowWidthAfterExpectedCurrentTask
+                + expectedCurrentTaskWidthAndSpacing;
         int expectedFirstTaskStart = mLastComputedTaskSize.right;
         if (firstTaskStart < expectedFirstTaskStart) {
             mClearAllShortTotalWidthTranslation = expectedFirstTaskStart - firstTaskStart;
@@ -3378,7 +3530,7 @@
         float clearAllTotalTranslationX =
                 clearAllAccumulatedTranslation + clearAllShorterRowCompensation
                         + clearAllShortTotalWidthTranslation + snappedTaskNonGridScrollAdjustment;
-        if (!largeTasksIndices.isEmpty()) {
+        if (largeTasksCount > 0) {
             // Shift by focused task's width and spacing if a task is focused.
             clearAllTotalTranslationX +=
                     mIsRtl ? largeTaskWidthAndSpacing : -largeTaskWidthAndSpacing;
@@ -3402,18 +3554,24 @@
             }
         }
 
-        for (int i = 0; i < taskCount; i++) {
-            TaskView taskView = requireTaskViewAt(i);
-            taskView.setGridTranslationX(gridTranslations[i] - snappedTaskGridTranslationX
-                    + snappedTaskNonGridScrollAdjustment);
+        for (TaskView taskView : getTaskViews()) {
+            taskView.setGridTranslationX(
+                    gridTranslations.getOrDefault(taskView, 0f) - snappedTaskGridTranslationX
+                            + snappedTaskNonGridScrollAdjustment);
         }
 
-        final TaskView runningTask = getRunningTaskView();
-        if (showAsGrid() && enableGridOnlyOverview() && runningTask != null) {
-            runActionOnRemoteHandles(
-                    remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
-                            .taskSecondaryTranslation.value = runningTask.getGridTranslationY()
-            );
+        if (mAddDesktopButton != null) {
+            TaskView firstTaskView = getFirstTaskView();
+            float translationX = 0f;
+            if (firstTaskView != null) {
+                translationX += firstTaskView.getGridTranslationX();
+            }
+            if (focusedTaskViewShift != 0) {
+                // If the focused task is inserted between `firstTaskView` and
+                // `mAddDesktopButton`, shift `mAddDesktopButton` to accommodate.
+                translationX += largeTaskWidthAndSpacing;
+            }
+            mAddDesktopButton.setGridTranslationX(translationX);
         }
 
         mClearAllButton.setGridTranslationPrimary(
@@ -3424,7 +3582,7 @@
         setGridProgress(mGridProgress);
     }
 
-    private boolean isSameGridRow(TaskView taskView1, TaskView taskView2) {
+    protected boolean isSameGridRow(TaskView taskView1, TaskView taskView2) {
         if (taskView1 == null || taskView2 == null) {
             return false;
         }
@@ -3452,11 +3610,6 @@
     }
 
     private void setTaskThumbnailSplashAlpha(float taskThumbnailSplashAlpha) {
-        if (enableRefactorTaskThumbnail()) {
-            mRecentsViewModel.updateThumbnailSplashProgress(taskThumbnailSplashAlpha);
-            return;
-        }
-
         mTaskThumbnailSplashAlpha = taskThumbnailSplashAlpha;
         for (TaskView taskView : getTaskViews()) {
             taskView.setTaskThumbnailSplashAlpha(taskThumbnailSplashAlpha);
@@ -3531,12 +3684,9 @@
         if (taskView.isRunningTask()) {
             anim.addOnFrameCallback(() -> {
                 if (!mEnableDrawingLiveTile) return;
-                runActionOnRemoteHandles(
-                        remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
-                                .taskSecondaryTranslation.value = getPagedOrientationHandler()
-                                .getSecondaryValue(taskView.getTranslationX(),
-                                        taskView.getTranslationY()
-                                ));
+                runActionOnRemoteHandles(remoteTargetHandle ->
+                        remoteTargetHandle.getTaskViewSimulator().taskSecondaryTranslation.value =
+                                taskView.getSecondaryDismissTranslationProperty().get(taskView));
                 redrawLiveTile();
             });
         }
@@ -3617,11 +3767,7 @@
                 InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER);
             } else {
                 // If transition to split select was interrupted, clean up to prevent glitches
-                if (FeatureFlags.enableSplitContextually()) {
-                    mSplitSelectStateController.resetState();
-                } else {
-                    resetFromSplitSelectionState();
-                }
+                mSplitSelectStateController.resetState();
                 InteractionJankMonitorWrapper.cancel(Cuj.CUJ_SPLIT_SCREEN_ENTER);
             }
 
@@ -3640,11 +3786,13 @@
      * @param duration                    duration of the animation
      * @param dismissingForSplitSelection task dismiss animation is used for entering split
      *                                    selection state from app icon
+     * @param isExpressiveDismiss         runs expressive animations controlled via
+     *                                    {@link RecentsDismissUtils}
      */
     public void createTaskDismissAnimation(PendingAnimation anim,
             @Nullable TaskView dismissedTaskView,
             boolean animateTaskView, boolean shouldRemoveTask, long duration,
-            boolean dismissingForSplitSelection) {
+            boolean dismissingForSplitSelection, boolean isExpressiveDismiss) {
         if (mPendingAnimation != null) {
             mPendingAnimation.createPlaybackController().dispatchOnCancel().dispatchOnEnd();
         }
@@ -3732,7 +3880,7 @@
         boolean currentPageSnapsToEndOfGrid = currentPageScroll == lastGridTaskScroll;
 
         int topGridRowSize = mTopRowIdSet.size();
-        int numLargeTiles = mUtils.getLargeTileCount(getTaskViews());
+        int numLargeTiles = mUtils.getLargeTileCount();
         int bottomGridRowSize = taskCount - mTopRowIdSet.size() - numLargeTiles;
         boolean topRowLonger = topGridRowSize > bottomGridRowSize;
         boolean bottomRowLonger = bottomGridRowSize > topGridRowSize;
@@ -3761,7 +3909,8 @@
                 newClearAllShortTotalWidthTranslation = expectedFirstTaskStart - firstTaskStart;
             }
         }
-        if (lastGridTaskView != null && lastGridTaskView.isVisibleToUser()) {
+        if (lastGridTaskView != null && (lastGridTaskView.isVisibleToUser() || (
+                isExpressiveDismiss && lastGridTaskView == dismissedTaskView))) {
             // After dismissal, animate translation of the remaining tasks to fill any gap left
             // between the end of the grid and the clear all button. Only animate if the clear
             // all button is visible or would become visible after dismissal.
@@ -3806,6 +3955,22 @@
                 // the only invariant point in landscape split screen.
                 snapToLastTask = true;
             }
+            if (mUtils.getGridTaskCount() == 1 && dismissedTaskView.isGridTask()) {
+                TaskView lastLargeTile = mUtils.getLastLargeTaskView();
+                if (lastLargeTile != null) {
+                    // Calculate the distance to put last large tile back to middle of the screen.
+                    int primaryScroll = getPagedOrientationHandler().getPrimaryScroll(this);
+                    int lastLargeTileScroll = getScrollForPage(indexOfChild(lastLargeTile));
+                    longGridRowWidthDiff = primaryScroll - lastLargeTileScroll;
+
+                    if (!isClearAllHidden) {
+                        // If ClearAllButton is visible, reduce the distance by scroll difference
+                        // between ClearAllButton and the last task.
+                        longGridRowWidthDiff += getLastTaskScroll(/*clearAllScroll=*/0,
+                                getPagedOrientationHandler().getPrimarySize(mClearAllButton));
+                    }
+                }
+            }
 
             // If we need to animate the grid to compensate the clear all gap, we split the second
             // half of the dismiss pending animation (in which the non-dismissed tasks slide into
@@ -3862,15 +4027,16 @@
         int distanceFromDismissedTask = 1;
         int slidingTranslation = 0;
         if (isSlidingTasks) {
-            int nextSnappedPage = isStagingFocusedTask
-                    ? indexOfChild(mUtils.getFirstSmallTaskView(getTaskViews()))
-                    : mUtils.getDesktopTaskViewCount(getTaskViews());
+            int nextSnappedPage = indexOfChild(isStagingFocusedTask
+                    ? mUtils.getFirstSmallTaskView()
+                    : mUtils.getFirstNonDesktopTaskView());
             slidingTranslation = getPagedOrientationHandler().getPrimaryScroll(this)
                     - getScrollForPage(nextSnappedPage);
             slidingTranslation += mIsRtl ? newClearAllShortTotalWidthTranslation
                     : -newClearAllShortTotalWidthTranslation;
         }
-        mDismissPrimaryTranslations = new int[taskCount];
+        mTaskViewsDismissPrimaryTranslations.clear();
+        int lastTaskViewIndex = indexOfChild(mUtils.getLastTaskView());
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
             if (child == dismissedTaskView) {
@@ -3880,15 +4046,21 @@
             } else if (!showAsGrid || (enableLargeDesktopWindowingTile()
                     && dismissedTaskView != null && dismissedTaskView.isLargeTile()
                     && nextFocusedTaskView == null && !dismissingForSplitSelection)) {
-                int offset = getOffsetToDismissedTask(scrollDiffPerPage, dismissedIndex, taskCount);
+                int offset = getOffsetToDismissedTask(scrollDiffPerPage, dismissedIndex,
+                        lastTaskViewIndex);
                 int scrollDiff = newScroll[i] - oldScroll[i] + offset;
                 if (scrollDiff != 0) {
-                    translateTaskWhenDismissed(
-                            child,
-                            Math.abs(i - dismissedIndex),
-                            scrollDiff,
-                            anim,
-                            splitTimings, i);
+                    if (!isExpressiveDismiss) {
+                        translateTaskWhenDismissed(
+                                child,
+                                Math.abs(i - dismissedIndex),
+                                scrollDiff,
+                                anim,
+                                splitTimings);
+                    }
+                    if (child instanceof TaskView taskView) {
+                        mTaskViewsDismissPrimaryTranslations.put(taskView, scrollDiffPerPage);
+                    }
                     needsCurveUpdates = true;
                 }
             } else if (child instanceof TaskView taskView) {
@@ -3973,14 +4145,17 @@
                                 : finalTranslation + (mIsRtl ? -mLastComputedTaskSize.right
                                         : mLastComputedTaskSize.right);
                     }
-                    Animator dismissAnimator = ObjectAnimator.ofFloat(taskView,
-                            taskView.getPrimaryDismissTranslationProperty(),
-                            startTranslation, finalTranslation);
-                    dismissAnimator.setInterpolator(
-                            clampToProgress(dismissInterpolator, animationStartProgress,
-                                    animationEndProgress));
-                    anim.add(dismissAnimator);
-                    mDismissPrimaryTranslations[i] = (int) finalTranslation;
+                    // Expressive dismiss will animate the translations of taskViews itself.
+                    if (!isExpressiveDismiss) {
+                        Animator dismissAnimator = ObjectAnimator.ofFloat(taskView,
+                                taskView.getPrimaryDismissTranslationProperty(),
+                                startTranslation, finalTranslation);
+                        dismissAnimator.setInterpolator(
+                                clampToProgress(dismissInterpolator, animationStartProgress,
+                                        animationEndProgress));
+                        anim.add(dismissAnimator);
+                    }
+                    mTaskViewsDismissPrimaryTranslations.put(taskView, (int) finalTranslation);
                     distanceFromDismissedTask++;
                 }
             }
@@ -4000,6 +4175,10 @@
             dismissedTaskView.setTranslationZ(0.1f);
         }
         loadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
+        if (!dismissingForSplitSelection) {
+            anim.addStartListener(() -> InteractionJankMonitorWrapper.begin(this,
+                    Cuj.CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS));
+        }
         mPendingAnimation = anim;
         final TaskView finalNextFocusedTaskView = nextFocusedTaskView;
         final boolean finalCloseGapBetweenClearAll = closeGapBetweenClearAll;
@@ -4032,10 +4211,8 @@
                         } else {
                             removeTaskInternal(dismissedTaskView);
                         }
-                        announceForAccessibility(
-                                getResources().getString(R.string.task_view_closed));
                         mContainer.getStatsLogManager().logger()
-                                .withItemInfo(dismissedTaskView.getFirstItemInfo())
+                                .withItemInfo(dismissedTaskView.getItemInfo())
                                 .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
                     }
 
@@ -4051,7 +4228,7 @@
                                 pageToSnapTo = indexOfChild(mClearAllButton);
                             } else if (isClearAllHidden) {
                                 // Snap to focused task if clear all is hidden.
-                                pageToSnapTo = 0;
+                                pageToSnapTo = indexOfChild(getFirstTaskView());
                             }
                         } else {
                             // Get the id of the task view we will snap to based on the current
@@ -4069,7 +4246,7 @@
                                     } else {
                                         // Won't focus next task in split select, so snap to the
                                         // first task.
-                                        pageToSnapTo = 0;
+                                        pageToSnapTo = indexOfChild(getFirstTaskView());
                                         calculateScrollDiff = false;
                                     }
                                 } else {
@@ -4077,8 +4254,8 @@
                                     boolean isSnappedTaskInTopRow = mTopRowIdSet.contains(
                                             snappedTaskViewId);
                                     IntArray taskViewIdArray =
-                                            isSnappedTaskInTopRow ? getTopRowIdArray()
-                                                    : getBottomRowIdArray();
+                                            isSnappedTaskInTopRow ? mUtils.getTopRowIdArray()
+                                                    : mUtils.getBottomRowIdArray();
                                     int snappedIndex = taskViewIdArray.indexOf(snappedTaskViewId);
                                     taskViewIdArray.removeValue(dismissedTaskViewId);
                                     if (finalNextFocusedTaskView != null) {
@@ -4093,8 +4270,8 @@
                                         // dismissed row,
                                         // snap to the same column in the other grid row
                                         IntArray inverseRowTaskViewIdArray =
-                                                isSnappedTaskInTopRow ? getBottomRowIdArray()
-                                                        : getTopRowIdArray();
+                                                isSnappedTaskInTopRow ? mUtils.getBottomRowIdArray()
+                                                        : mUtils.getTopRowIdArray();
                                         if (snappedIndex < inverseRowTaskViewIdArray.size()) {
                                             taskViewIdToSnapTo = inverseRowTaskViewIdArray.get(
                                                     snappedIndex);
@@ -4110,7 +4287,7 @@
                                 mCurrentPageScrollDiff = primaryScroll - currentPageScroll;
                             }
                         }
-                    } else if (dismissedIndex < pageToSnapTo || pageToSnapTo == taskCount - 1) {
+                    } else if (dismissedIndex < pageToSnapTo || pageToSnapTo == lastTaskViewIndex) {
                         pageToSnapTo--;
                     }
                     boolean isHomeTaskDismissed = dismissedTaskView == getHomeTaskView();
@@ -4119,6 +4296,7 @@
 
                     if (taskCount == 1) {
                         removeViewInLayout(mClearAllButton);
+                        removeViewInLayout(mAddDesktopButton);
                         if (isHomeTaskDismissed) {
                             updateEmptyMessage();
                         } else if (!mSplitSelectStateController.isSplitSelectActive()) {
@@ -4140,15 +4318,15 @@
 
                         if (showAsGrid) {
                             // Rebalance tasks in the grid
-                            int highestVisibleTaskIndex = getHighestVisibleTaskIndex();
-                            if (highestVisibleTaskIndex < Integer.MAX_VALUE) {
-                                TaskView taskView = requireTaskViewAt(highestVisibleTaskIndex);
-
+                            TaskView highestVisibleTaskView = getHighestVisibleTaskView();
+                            if (highestVisibleTaskView != null) {
                                 boolean shouldRebalance;
                                 int screenStart = getPagedOrientationHandler().getPrimaryScroll(
                                         RecentsView.this);
-                                int taskStart = getPagedOrientationHandler().getChildStart(taskView)
-                                        + (int) taskView.getOffsetAdjustment(/*gridEnabled=*/ true);
+                                int taskStart = getPagedOrientationHandler().getChildStart(
+                                        highestVisibleTaskView)
+                                        + (int) highestVisibleTaskView.getOffsetAdjustment(
+                                                /*gridEnabled=*/true);
 
                                 // Rebalance only if there is a maximum gap between the task and the
                                 // screen's edge; this ensures that rebalanced tasks are outside the
@@ -4161,7 +4339,7 @@
                                             RecentsView.this);
                                     int taskSize = (int) (
                                             getPagedOrientationHandler().getMeasuredSize(
-                                                    taskView) * taskView
+                                                    highestVisibleTaskView) * highestVisibleTaskView
                                                     .getSizeAdjustment(/*fullscreenEnabled=*/
                                                             false));
                                     int taskEnd = taskStart + taskSize;
@@ -4170,13 +4348,13 @@
                                 }
 
                                 if (shouldRebalance) {
-                                    updateGridProperties(highestVisibleTaskIndex);
+                                    updateGridProperties(highestVisibleTaskView);
                                     updateScrollSynchronously();
                                 }
                             }
 
-                            IntArray topRowIdArray = getTopRowIdArray();
-                            IntArray bottomRowIdArray = getBottomRowIdArray();
+                            IntArray topRowIdArray = mUtils.getTopRowIdArray();
+                            IntArray bottomRowIdArray = mUtils.getBottomRowIdArray();
                             if (finalSnapToLastTask) {
                                 // If snapping to last task, find the last task after dismissal.
                                 pageToSnapTo = indexOfChild(
@@ -4186,8 +4364,7 @@
                                     // Snap to latest large tile page after dismissing the
                                     // last grid task. This will prevent snapping to page 0 when
                                     // desktop task is visible as large tile.
-                                    pageToSnapTo = indexOfChild(
-                                            mUtils.getLastLargeTaskView(getTaskViews()));
+                                    pageToSnapTo = indexOfChild(mUtils.getLastLargeTaskView());
                                 }
                             } else if (taskViewIdToSnapTo != -1) {
                                 // If snapping to another page due to indices rearranging, find
@@ -4217,7 +4394,13 @@
                 updateCurrentTaskActionsVisibility();
                 onDismissAnimationEnds();
                 mPendingAnimation = null;
-                mDismissPrimaryTranslations = null;
+                mTaskViewsDismissPrimaryTranslations.clear();
+
+                if (!dismissingForSplitSelection && success) {
+                    InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS);
+                } else if (!dismissingForSplitSelection) {
+                    InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS);
+                }
             }
         });
     }
@@ -4231,14 +4414,14 @@
      * - Current page is rightmost page (leftmost for RTL)
      * - Dragging an adjacent page on the left side (right side for RTL)
      */
-    private int getOffsetToDismissedTask(int scrollDiffPerPage, int dismissedIndex, int taskCount) {
-        // When mCurrentPage is ClearAllButton, use the last TaskView instead to calculate
-        // offset.
-        int currentPage = mCurrentPage == taskCount ? taskCount - 1 : mCurrentPage;
+    private int getOffsetToDismissedTask(int scrollDiffPerPage, int dismissedIndex,
+            int lastTaskViewIndex) {
+        // If `mCurrentPage` is beyond `lastTaskViewIndex`, use the last TaskView instead to
+        // calculate offset.
+        int currentPage = Math.min(mCurrentPage, lastTaskViewIndex);
         int offset = mIsRtl ? scrollDiffPerPage : 0;
         if (currentPage == dismissedIndex) {
-            int lastPage = taskCount - 1;
-            if (currentPage == lastPage) {
+            if (currentPage == lastTaskViewIndex) {
                 offset += mIsRtl ? -scrollDiffPerPage : scrollDiffPerPage;
             }
         } else {
@@ -4256,8 +4439,12 @@
             int indexDiff,
             int scrollDiffPerPage,
             PendingAnimation pendingAnimation,
-            SplitAnimationTimings splitTimings,
-            int index) {
+            SplitAnimationTimings splitTimings) {
+        // No need to translate the AddDesktopButton on dismissing a TaskView, which should be
+        // always at the right most position, even when dismissing the last TaskView.
+        if (view instanceof AddDesktopButton) {
+            return;
+        }
         FloatProperty translationProperty = view instanceof TaskView
                 ? ((TaskView) view).getPrimaryDismissTranslationProperty()
                 : getPagedOrientationHandler().getPrimaryViewTranslate();
@@ -4290,10 +4477,6 @@
                         animationEndProgress
                 )
         );
-
-        if (view instanceof TaskView) {
-            mDismissPrimaryTranslations[index] = scrollDiffPerPage;
-        }
         if (mEnableDrawingLiveTile && view instanceof TaskView
                 && ((TaskView) view).isRunningTask()) {
             pendingAnimation.addOnFrameCallback(() -> {
@@ -4321,9 +4504,6 @@
         boolean isCurrentSplit = taskView instanceof GroupedTaskView;
         GroupedTaskView groupedTaskView = isCurrentSplit ? (GroupedTaskView) taskView : null;
         // Update flags to see if entire actions bar should be hidden.
-        if (!FeatureFlags.enableAppPairs()) {
-            mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SCREEN, isCurrentSplit);
-        }
         mActionsView.updateHiddenFlags(HIDDEN_SPLIT_SELECT_ACTIVE, isSplitSelectionActive());
         // Update flags to see if actions bar should show buttons for a single task or a pair of
         // tasks.
@@ -4341,52 +4521,17 @@
     }
 
     /**
-     * Returns all the tasks in the top row, without the focused task
-     */
-    private IntArray getTopRowIdArray() {
-        if (mTopRowIdSet.isEmpty()) {
-            return new IntArray(0);
-        }
-        IntArray topArray = new IntArray(mTopRowIdSet.size());
-        for (TaskView taskView : getTaskViews()) {
-            int taskViewId = taskView.getTaskViewId();
-            if (mTopRowIdSet.contains(taskViewId)) {
-                topArray.add(taskViewId);
-            }
-        }
-        return topArray;
-    }
-
-    /**
-     * Returns all the tasks in the bottom row, without the focused task
-     */
-    private IntArray getBottomRowIdArray() {
-        int bottomRowIdArraySize = getBottomRowTaskCountForTablet();
-        if (bottomRowIdArraySize <= 0) {
-            return new IntArray(0);
-        }
-        IntArray bottomArray = new IntArray(bottomRowIdArraySize);
-        for (TaskView taskView : getTaskViews()) {
-            int taskViewId = taskView.getTaskViewId();
-            if (!mTopRowIdSet.contains(taskViewId) && !taskView.isLargeTile()) {
-                bottomArray.add(taskViewId);
-            }
-        }
-        return bottomArray;
-    }
-
-    /**
      * Iterate the grid by columns instead of by TaskView index, starting after the focused task and
      * up to the last balanced column.
      *
-     * @return the highest visible TaskView index between both rows
+     * @return the highest visible TaskView between both rows
      */
-    private int getHighestVisibleTaskIndex() {
-        if (mTopRowIdSet.isEmpty()) return Integer.MAX_VALUE; // return earlier
+    private TaskView getHighestVisibleTaskView() {
+        if (mTopRowIdSet.isEmpty()) return null; // return earlier
 
-        int lastVisibleIndex = Integer.MAX_VALUE;
-        IntArray topRowIdArray = getTopRowIdArray();
-        IntArray bottomRowIdArray = getBottomRowIdArray();
+        TaskView lastVisibleTaskView = null;
+        IntArray topRowIdArray = mUtils.getTopRowIdArray();
+        IntArray bottomRowIdArray = mUtils.getBottomRowIdArray();
         int balancedColumns = Math.min(bottomRowIdArray.size(), topRowIdArray.size());
 
         for (int i = 0; i < balancedColumns; i++) {
@@ -4394,33 +4539,43 @@
 
             if (isTaskViewVisible(topTask)) {
                 TaskView bottomTask = getTaskViewFromTaskViewId(bottomRowIdArray.get(i));
-                lastVisibleIndex = Math.max(indexOfChild(topTask), indexOfChild(bottomTask));
-            } else if (lastVisibleIndex < Integer.MAX_VALUE) {
+                lastVisibleTaskView =
+                        indexOfChild(topTask) > indexOfChild(bottomTask) ? topTask : bottomTask;
+            } else if (lastVisibleTaskView != null) {
                 break;
             }
         }
 
-        return lastVisibleIndex;
+        return lastVisibleTaskView;
     }
 
-  private void removeTaskInternal(@NonNull TaskView dismissedTaskView) {
-    UI_HELPER_EXECUTOR
-        .getHandler()
-        .post(
-            () -> {
-              if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()
-                  && dismissedTaskView instanceof DesktopTaskView) {
-                // TODO: b/362720497 - Use the api with desktop id instead.
-                SystemUiProxy.INSTANCE
+    private void removeTaskInternal(@NonNull TaskView dismissedTaskView) {
+        UI_HELPER_EXECUTOR
+                .getHandler()
+                .post(
+                        () -> {
+                            if (dismissedTaskView instanceof DesktopTaskView desktopTaskView) {
+                                removeDesktopTaskView(desktopTaskView);
+                            } else {
+                                for (int taskId : dismissedTaskView.getTaskIds()) {
+                                    ActivityManagerWrapper.getInstance().removeTask(taskId);
+                                }
+                            }
+                        });
+    }
+
+    private void removeDesktopTaskView(DesktopTaskView desktopTaskView) {
+        if (DesksUtils.areMultiDesksFlagsEnabled()) {
+            SystemUiProxy.INSTANCE
                     .get(getContext())
-                    .removeDesktop(mContainer.getDisplay().getDisplayId());
-              } else {
-                for (int taskId : dismissedTaskView.getTaskIds()) {
-                    ActivityManagerWrapper.getInstance().removeTask(taskId);
-                }
-              }
-            });
-  }
+                    .removeDesk(desktopTaskView.getDeskId());
+        } else if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) {
+            SystemUiProxy.INSTANCE
+                    .get(getContext())
+                    .removeDefaultDeskInDisplay(
+                            mContainer.getDisplay().getDisplayId());
+        }
+    }
 
     protected void onDismissAnimationEnds() {
         AccessibilityManagerCompat.sendTestProtocolEventToTest(getContext(),
@@ -4440,11 +4595,17 @@
         mPendingAnimation = anim;
         mPendingAnimation.addEndListener(isSuccess -> {
             if (isSuccess) {
+                // Remove desktops first, since desks can be empty (so they have no recent tasks),
+                // and closing all tasks on a desk doesn't always necessarily mean that the desk
+                // will be removed. So, there are no guarantees that the below call to
+                // `ActivityManagerWrapper::removeAllRecentTasks()` will be enough.
+                SystemUiProxy.INSTANCE.get(getContext()).removeAllDesks();
+
                 // Remove all the task views now
                 finishRecentsAnimation(true /* toRecents */, false /* shouldPip */, () -> {
                     UI_HELPER_EXECUTOR.getHandler().post(
                             ActivityManagerWrapper.getInstance()::removeAllRecentTasks);
-                    removeTasksViewsAndClearAllButton();
+                    removeAllTaskViews();
                     startHome();
                 });
             }
@@ -4454,7 +4615,7 @@
     }
 
     private boolean snapToPageRelative(int delta, boolean cycle,
-            @TaskGridNavHelper.TASK_NAV_DIRECTION int direction) {
+            TaskGridNavHelper.TaskNavDirection direction) {
         // Set next page if scroll animation is still running, otherwise cannot snap to the
         // next page on successive key presses. Setting the current page aborts the scroll.
         if (!mScroller.isFinished()) {
@@ -4473,32 +4634,41 @@
         return true;
     }
 
-    private int getNextPageInternal(int delta, @TaskGridNavHelper.TASK_NAV_DIRECTION int direction,
+    private int getNextPageInternal(int delta, TaskGridNavHelper.TaskNavDirection direction,
             boolean cycle) {
         if (!showAsGrid()) {
             return getNextPage() + delta;
         }
 
         // Init task grid nav helper with top/bottom id arrays.
-        TaskGridNavHelper taskGridNavHelper = new TaskGridNavHelper(getTopRowIdArray(),
-                getBottomRowIdArray(), mUtils.getLargeTaskViewIds(getTaskViews()));
+        TaskGridNavHelper taskGridNavHelper = new TaskGridNavHelper(mUtils.getTopRowIdArray(),
+                mUtils.getBottomRowIdArray(), mUtils.getLargeTaskViewIds(),
+                mAddDesktopButton != null);
 
         // Get current page's task view ID.
         TaskView currentPageTaskView = getCurrentPageTaskView();
         int currentPageTaskViewId;
+        final int clearAllButtonIndex = indexOfChild(mClearAllButton);
+        final int addDesktopButtonIndex = indexOfChild(mAddDesktopButton);
         if (currentPageTaskView != null) {
             currentPageTaskViewId = currentPageTaskView.getTaskViewId();
-        } else if (mCurrentPage == indexOfChild(mClearAllButton)) {
+        } else if (mCurrentPage == clearAllButtonIndex) {
             currentPageTaskViewId = TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID;
+        } else if (mCurrentPage == addDesktopButtonIndex) {
+            currentPageTaskViewId = TaskGridNavHelper.ADD_DESK_PLACEHOLDER_ID;
         } else {
             return INVALID_PAGE;
         }
 
-        int nextGridPage =
+        final int nextGridPage =
                 taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle);
-        return nextGridPage == TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID
-                ? indexOfChild(mClearAllButton)
-                : indexOfChild(getTaskViewFromTaskViewId(nextGridPage));
+        if (nextGridPage == TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID) {
+            return clearAllButtonIndex;
+        }
+        if (nextGridPage == TaskGridNavHelper.ADD_DESK_PLACEHOLDER_ID) {
+            return addDesktopButtonIndex;
+        }
+        return indexOfChild(getTaskViewFromTaskViewId(nextGridPage));
     }
 
     private void runDismissAnimation(PendingAnimation pendingAnim) {
@@ -4509,20 +4679,38 @@
     }
 
     @UiThread
-    private void dismissTask(int taskId) {
+    public void dismissTask(int taskId, boolean animate, boolean removeTask) {
         TaskView taskView = getTaskViewByTaskId(taskId);
         if (taskView == null) {
             Log.d(TAG, "dismissTask: " + taskId + ",  no associated TaskView");
             return;
         }
         Log.d(TAG, "dismissTask: " + taskId);
-        dismissTask(taskView, true /* animate */, false /* removeTask */);
+
+        if (enableDesktopExplodedView() && taskView instanceof  DesktopTaskView desktopTaskView) {
+            desktopTaskView.removeTaskFromExplodedView(taskId, animate);
+
+            if (removeTask) {
+                ActivityManagerWrapper.getInstance().removeTask(taskId);
+            }
+        } else {
+            dismissTaskView(taskView, animate, removeTask);
+        }
     }
 
-    public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) {
+    /** Dismisses the entire [taskView]. */
+    public void dismissTaskView(TaskView taskView, boolean animateTaskView, boolean removeTask) {
         PendingAnimation pa = new PendingAnimation(DISMISS_TASK_DURATION);
         createTaskDismissAnimation(pa, taskView, animateTaskView, removeTask, DISMISS_TASK_DURATION,
-                false /* dismissingForSplitSelection*/);
+                false /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */);
+        runDismissAnimation(pa);
+    }
+
+    protected void expressiveDismissTaskView(TaskView taskView) {
+        PendingAnimation pa = new PendingAnimation(DISMISS_TASK_DURATION);
+        createTaskDismissAnimation(pa, taskView, false /* animateTaskView */, true /* removeTask */,
+                DISMISS_TASK_DURATION, false /* dismissingForSplitSelection*/,
+                true /* isExpressiveDismiss */);
         runDismissAnimation(pa);
     }
 
@@ -4535,10 +4723,16 @@
     private void dismissCurrentTask() {
         TaskView taskView = getNextPageTaskView();
         if (taskView != null) {
-            dismissTask(taskView, true /*animateTaskView*/, true /*removeTask*/);
+            dismissTaskView(taskView, true /*animateTaskView*/, true /*removeTask*/);
         }
     }
 
+    private void createDesk(View view) {
+        SystemUiProxy.INSTANCE
+                .get(getContext())
+                .createDesk(mContainer.getDisplay().getDisplayId());
+    }
+
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
         if (isHandlingTouch() || event.getAction() != KeyEvent.ACTION_DOWN) {
@@ -4547,15 +4741,19 @@
         switch (event.getKeyCode()) {
             case KeyEvent.KEYCODE_TAB:
                 return snapToPageRelative(event.isShiftPressed() ? -1 : 1, true /* cycle */,
-                        DIRECTION_TAB);
+                        TaskGridNavHelper.TaskNavDirection.TAB);
             case KeyEvent.KEYCODE_DPAD_RIGHT:
-                return snapToPageRelative(mIsRtl ? -1 : 1, true /* cycle */, DIRECTION_RIGHT);
+                return snapToPageRelative(mIsRtl ? -1 : 1, true /* cycle */,
+                        TaskGridNavHelper.TaskNavDirection.RIGHT);
             case KeyEvent.KEYCODE_DPAD_LEFT:
-                return snapToPageRelative(mIsRtl ? 1 : -1, true /* cycle */, DIRECTION_LEFT);
+                return snapToPageRelative(mIsRtl ? 1 : -1, true /* cycle */,
+                        TaskGridNavHelper.TaskNavDirection.LEFT);
             case KeyEvent.KEYCODE_DPAD_UP:
-                return snapToPageRelative(1, false /* cycle */, DIRECTION_UP);
+                return snapToPageRelative(1, false /* cycle */,
+                        TaskGridNavHelper.TaskNavDirection.UP);
             case KeyEvent.KEYCODE_DPAD_DOWN:
-                return snapToPageRelative(1, false /* cycle */, DIRECTION_DOWN);
+                return snapToPageRelative(1, false /* cycle */,
+                        TaskGridNavHelper.TaskNavDirection.DOWN);
             case KeyEvent.KEYCODE_DEL:
             case KeyEvent.KEYCODE_FORWARD_DEL:
                 dismissCurrentTask();
@@ -4603,6 +4801,10 @@
             taskView.setStableAlpha(alpha);
         }
         mClearAllButton.setContentAlpha(mContentAlpha);
+
+        if (mAddDesktopButton != null) {
+            mAddDesktopButton.setContentAlpha(mContentAlpha);
+        }
         int alphaInt = Math.round(alpha * 255);
         mEmptyMessagePaint.setAlpha(alphaInt);
         mEmptyIcon.setAlpha(alphaInt);
@@ -4658,6 +4860,12 @@
         }
     }
 
+    public void reapplyActiveRotation() {
+        RotationTouchHelper rotationTouchHelper = RotationTouchHelper.INSTANCE.get(getContext());
+        setLayoutRotation(rotationTouchHelper.getCurrentActiveRotation(),
+                rotationTouchHelper.getDisplayRotation());
+    }
+
     public void setLayoutRotation(int touchRotation, int displayRotation) {
         if (mOrientationState.update(touchRotation, displayRotation)) {
             updateOrientationHandler();
@@ -4684,11 +4892,11 @@
 
     @Nullable
     public TaskView getLastLargeTaskView() {
-        return mUtils.getLastLargeTaskView(getTaskViews());
+        return mUtils.getLastLargeTaskView();
     }
 
     public int getLargeTilesCount() {
-        return mUtils.getLargeTileCount(getTaskViews());
+        return mUtils.getLargeTileCount();
     }
 
     @Nullable
@@ -4716,18 +4924,10 @@
     }
 
     /**
-     * A version of {@link #getTaskViewAt} when the caller is sure about the input index.
+     * Returns iterable [TaskView] children.
      */
-    @NonNull
-    private TaskView requireTaskViewAt(int index) {
-        return Objects.requireNonNull(getTaskViewAt(index));
-    }
-
-    /**
-     * Returns the current list of [TaskView] children.
-     */
-    private Iterable<TaskView> getTaskViews() {
-        return mUtils.getTaskViews(getTaskViewCount(), this::requireTaskViewAt);
+    public RecentsViewUtils.TaskViewsIterable getTaskViews() {
+        return mUtils.getTaskViews();
     }
 
     public void setOnEmptyMessageUpdatedListener(OnEmptyMessageUpdatedListener listener) {
@@ -4735,7 +4935,7 @@
     }
 
     public void updateEmptyMessage() {
-        boolean isEmpty = getTaskViewCount() == 0;
+        boolean isEmpty = !hasTaskViews();
         boolean hasSizeChanged = mLastMeasureSize.x != getWidth()
                 || mLastMeasureSize.y != getHeight();
         if (isEmpty == mShowEmptyMessage && !hasSizeChanged) {
@@ -4788,19 +4988,16 @@
                 mTempPointF.set(mLastComputedTaskSize.centerX(), mLastComputedTaskSize.bottom);
             }
         } else {
-            // Only update pivot when it is tablet and not in grid yet, so the pivot is correct
-            // for non-current tasks when swiping up to overview
-            if (enableGridOnlyOverview() && mContainer.getDeviceProfile().isTablet
-                    && !mOverviewGridEnabled) {
-                mTempRect.set(mLastComputedCarouselTaskSize);
-            } else {
-                mTempRect.set(mLastComputedTaskSize);
-            }
+            mTempRect.set(mLastComputedTaskSize);
             getPagedViewOrientedState().getFullScreenScaleAndPivot(mTempRect,
                     mContainer.getDeviceProfile(), mTempPointF);
         }
         setPivotX(mTempPointF.x);
         setPivotY(mTempPointF.y);
+        if (enableGridOnlyOverview()) {
+            runActionOnRemoteHandles(remoteTargetHandle ->
+                    remoteTargetHandle.getTaskViewSimulator().setPivotOverride(mTempPointF));
+        }
     }
 
     /**
@@ -4829,7 +5026,7 @@
         int modalMidpoint = getCurrentPage();
         TaskView carouselHiddenMidpointTask = runningTask != null ? runningTask
                 : mUtils.getFirstTaskViewInCarousel(/*nonRunningTaskCarouselHidden=*/true,
-                        getTaskViews(), null);
+                        /*runningTaskView=*/null);
         int carouselHiddenMidpoint = indexOfChild(carouselHiddenMidpointTask);
         boolean shouldCalculateOffsetForAllTasks = showAsGrid
                 && (enableGridOnlyOverview() || enableLargeDesktopWindowingTile())
@@ -4906,10 +5103,14 @@
                     && child instanceof DesktopTaskView;
             float totalTranslationX = (skipTranslationOffset ? 0f : translation) + modalTranslation
                     + carouselHiddenOffsetSize;
-            FloatProperty translationPropertyX = child instanceof TaskView
-                    ? ((TaskView) child).getPrimaryTaskOffsetTranslationProperty()
-                    : getPagedOrientationHandler().getPrimaryViewTranslate();
-            translationPropertyX.set(child, totalTranslationX);
+            if (child instanceof TaskView taskView) {
+                taskView.getPrimaryTaskOffsetTranslationProperty().set(taskView, totalTranslationX);
+            } else if (child instanceof ClearAllButton) {
+                getPagedOrientationHandler().getPrimaryViewTranslate().set(child,
+                        totalTranslationX);
+            } else if (child instanceof AddDesktopButton addDesktopButton) {
+                addDesktopButton.setOffsetTranslationX(totalTranslationX);
+            }
             if (mEnableDrawingLiveTile && i == getRunningTaskIndex()) {
                 runActionOnRemoteHandles(
                         remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
@@ -4917,11 +5118,11 @@
                 redrawLiveTile();
             }
 
-            if (showAsGrid && enableGridOnlyOverview() && child instanceof TaskView) {
-                float totalTranslationY = getVerticalOffsetSize(i, modalOffset);
-                FloatProperty translationPropertyY =
-                        ((TaskView) child).getSecondaryTaskOffsetTranslationProperty();
-                translationPropertyY.set(child, totalTranslationY);
+            if (showAsGrid && enableGridOnlyOverview() && child instanceof TaskView taskView) {
+                float totalTranslationY = getVerticalOffsetSize(taskView, modalOffset);
+                FloatProperty<TaskView> translationPropertyY =
+                        taskView.getSecondaryTaskOffsetTranslationProperty();
+                translationPropertyY.set(taskView, totalTranslationY);
             }
         }
         updateCurveProperties();
@@ -5037,7 +5238,7 @@
      *
      * @param offsetProgress From 0 to 1 where 0 means no offset and 1 means offset offscreen.
      */
-    private float getVerticalOffsetSize(int childIndex, float offsetProgress) {
+    private float getVerticalOffsetSize(TaskView taskView, float offsetProgress) {
         if (offsetProgress == 0 || !(showAsGrid() && enableGridOnlyOverview())
                 || mSelectedTask == null) {
             // Don't bother calculating everything below if we won't offset vertically.
@@ -5045,11 +5246,10 @@
         }
 
         // First, get the position of the task relative to the top row.
-        TaskView child = getTaskViewAt(childIndex);
-        Rect taskPosition = getTaskBounds(child);
+        Rect taskPosition = getTaskBounds(taskView);
 
         boolean isSelectedTaskTopRow = mTopRowIdSet.contains(mSelectedTask.getTaskViewId());
-        boolean isChildTopRow = mTopRowIdSet.contains(child.getTaskViewId());
+        boolean isChildTopRow = mTopRowIdSet.contains(taskView.getTaskViewId());
         // Whether the task should be shifted to the top.
         boolean isTopShift = !isSelectedTaskTopRow && isChildTopRow;
         boolean isBottomShift = isSelectedTaskTopRow && !isChildTopRow;
@@ -5112,18 +5312,20 @@
      * Primarily used by overview actions to initiate split from focused task, logs the source
      * of split invocation as such.
      */
-    public void initiateSplitSelect(TaskView taskView) {
+    public void initiateSplitSelect(TaskContainer taskContainer) {
         int defaultSplitPosition = getPagedOrientationHandler()
                 .getDefaultSplitPosition(mContainer.getDeviceProfile());
-        initiateSplitSelect(taskView, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT);
+        initiateSplitSelect(taskContainer, defaultSplitPosition, LAUNCHER_OVERVIEW_ACTIONS_SPLIT);
     }
 
     /** TODO(b/266477929): Consolidate this call w/ the one below */
-    public void initiateSplitSelect(TaskView taskView, @StagePosition int stagePosition,
+    public void initiateSplitSelect(TaskContainer taskContainer,
+            @StagePosition int stagePosition,
             StatsLogManager.EventEnum splitEvent) {
+        TaskView taskView = taskContainer.getTaskView();
         mSplitHiddenTaskView = taskView;
         mSplitSelectStateController.setInitialTaskSelect(null /*intent*/, stagePosition,
-                taskView.getFirstItemInfo(), splitEvent, taskView.getFirstTask().key.id);
+                taskContainer.getItemInfo(), splitEvent, taskContainer.getTask().key.id);
         mSplitSelectStateController.setAnimateCurrentTaskDismissal(
                 true /*animateCurrentTaskDismissal*/);
         mSplitHiddenTaskViewIndex = indexOfChild(taskView);
@@ -5160,17 +5362,16 @@
         SplitAnimationTimings timings = AnimUtils.getDeviceOverviewToSplitTimings(
                 mContainer.getDeviceProfile().isTablet);
         if (enableLargeDesktopWindowingTile()) {
-            for (int i = 0; i < getTaskViewCount(); i++) {
-                TaskView taskView = requireTaskViewAt(i);
+            getTaskViews().forEachWithIndexInParent((index, taskView) -> {
                 if (taskView instanceof DesktopTaskView) {
                     // Setting pivot to scale down from screen centre.
-                    if (i >= mCurrentPage - 1 && i <= mCurrentPage + 1) {
-                        float pivotX;
-                        if (i == mCurrentPage - 1) {
+                    if (isTaskViewVisible(taskView)) {
+                        float pivotX = 0f;
+                        if (index < mCurrentPage) {
                             pivotX = mIsRtl ? taskView.getWidth() / 2f - mPageSpacing
                                     - taskView.getWidth()
                                     : taskView.getWidth() / 2f + mPageSpacing + taskView.getWidth();
-                        } else if (i == mCurrentPage) {
+                        } else if (index == mCurrentPage) {
                             pivotX = taskView.getWidth() / 2f;
                         } else {
                             pivotX = mIsRtl ? taskView.getWidth() + mPageSpacing
@@ -5184,12 +5385,11 @@
                                 clampToProgress(timings.getDesktopTaskScaleInterpolator(), 0f,
                                         timings.getDesktopFadeSplitAnimationEndOffset()));
                     }
-                    builder.addFloat(taskView.getSplitAlphaProperty(),
-                            MULTI_PROPERTY_VALUE, 1f, 0f,
+                    builder.addFloat(taskView, SPLIT_ALPHA, 1f, 0f,
                             clampToProgress(deskTopFadeInterPolator, 0f,
                                     timings.getDesktopFadeSplitAnimationEndOffset()));
                 }
-            }
+            });
         }
     }
 
@@ -5215,15 +5415,16 @@
         boolean isInitiatingTaskViewSplitPair =
                 mSplitSelectStateController.isDismissingFromSplitPair();
         if (isInitiatingSplitFromTaskView && isInitiatingTaskViewSplitPair
-                && mSplitHiddenTaskView instanceof GroupedTaskView) {
+                && mSplitHiddenTaskView instanceof GroupedTaskView groupedTaskView) {
             // Splitting from Overview for split pair task
             createInitialSplitSelectAnimation(builder);
 
             // Animate pair thumbnail into full thumbnail
-            boolean primaryTaskSelected = mSplitHiddenTaskView.getTaskIds()[0]
+            boolean primaryTaskSelected = groupedTaskView.getLeftTopTaskContainer().getTask().key.id
                     == mSplitSelectStateController.getInitialTaskId();
-            TaskContainer taskContainer = mSplitHiddenTaskView
-                    .getTaskContainers().get(primaryTaskSelected ? 1 : 0);
+            TaskContainer taskContainer =
+                    primaryTaskSelected ? groupedTaskView.getRightBottomTaskContainer()
+                            : groupedTaskView.getLeftTopTaskContainer();
             mSplitSelectStateController.getSplitAnimationController()
                     .addInitialSplitFromPair(taskContainer, builder,
                             mContainer.getDeviceProfile(),
@@ -5242,7 +5443,7 @@
             }
             // Splitting from Overview for fullscreen task
             createTaskDismissAnimation(builder, mSplitHiddenTaskView, true, false, duration,
-                    true /* dismissingForSplitSelection*/);
+                    true /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */);
         } else {
             // Splitting from Home
             TaskView currentPageTaskView = getTaskViewAt(mCurrentPage);
@@ -5250,7 +5451,7 @@
             // display correct animation in split mode
             if (currentPageTaskView instanceof DesktopTaskView) {
                 createTaskDismissAnimation(builder, null, true, false, duration,
-                        true /* dismissingForSplitSelection*/);
+                        true /* dismissingForSplitSelection*/, false /* isExpressiveDismiss */);
             } else {
                 createInitialSplitSelectAnimation(builder);
             }
@@ -5348,11 +5549,7 @@
             mSplitSelectStateController.launchSplitTasks(
                     aBoolean1 -> {
                         InteractionJankMonitorWrapper.end(Cuj.CUJ_SPLIT_SCREEN_ENTER);
-                        if (FeatureFlags.enableSplitContextually()) {
-                            mSplitSelectStateController.resetState();
-                        } else {
-                            resetFromSplitSelectionState();
-                        }
+                        mSplitSelectStateController.resetState();
                     });
         });
 
@@ -5374,17 +5571,14 @@
 
     @SuppressLint("WrongCall")
     protected void resetFromSplitSelectionState() {
-        if (mSplitSelectSource != null || mSplitHiddenTaskViewIndex != -1 ||
-                FeatureFlags.enableSplitContextually()) {
-            safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView());
-            safeRemoveDragLayerView(mSecondFloatingTaskView);
-            safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView());
-            safeRemoveDragLayerView(mSplitScrim);
-            mSecondFloatingTaskView = null;
-            mSplitSelectSource = null;
-            mSplitSelectStateController.getSplitAnimationController()
-                    .removeSplitInstructionsView(mContainer);
-        }
+        safeRemoveDragLayerView(mSplitSelectStateController.getFirstFloatingTaskView());
+        safeRemoveDragLayerView(mSecondFloatingTaskView);
+        safeRemoveDragLayerView(mSplitSelectStateController.getSplitInstructionsView());
+        safeRemoveDragLayerView(mSplitScrim);
+        mSecondFloatingTaskView = null;
+        mSplitSelectSource = null;
+        mSplitSelectStateController.getSplitAnimationController()
+                .removeSplitInstructionsView(mContainer);
 
         if (mSecondSplitHiddenView != null) {
             mSecondSplitHiddenView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
@@ -5396,11 +5590,6 @@
         setTaskViewsPrimarySplitTranslation(0);
         setTaskViewsSecondarySplitTranslation(0);
 
-        if (!FeatureFlags.enableSplitContextually()) {
-            // When flag is on, this method gets called from resetState() call below, let's avoid
-            // infinite recursion today
-            mSplitSelectStateController.resetState();
-        }
         if (mSplitHiddenTaskViewIndex == -1) {
             return;
         }
@@ -5419,6 +5608,13 @@
         mSplitHiddenTaskViewIndex = -1;
         if (mSplitHiddenTaskView != null) {
             mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
+            // mSplitHiddenTaskView is set when split select animation starts. The TaskView is only
+            // removed when when the animation finishes. So in the case of overview being dismissed
+            // during the animation, we should not call clearAndRecycleTaskView() because it has
+            // not been removed yet.
+            if (mSplitHiddenTaskView.getParent() == null) {
+                clearAndRecycleTaskView(mSplitHiddenTaskView);
+            }
             mSplitHiddenTaskView = null;
         }
     }
@@ -5470,7 +5666,7 @@
         firstFloatingTaskView.update(mTempRectF, /*progress=*/1f);
 
         RecentsPagedOrientationHandler orientationHandler = getPagedOrientationHandler();
-        Pair<FloatProperty<RecentsView>, FloatProperty<RecentsView>> taskViewsFloat =
+        Pair<FloatProperty<RecentsView<?, ?>>, FloatProperty<RecentsView<?, ?>>> taskViewsFloat =
                 orientationHandler.getSplitSelectTaskOffset(
                         TASK_PRIMARY_SPLIT_TRANSLATION, TASK_SECONDARY_SPLIT_TRANSLATION,
                         mContainer.getDeviceProfile());
@@ -5492,15 +5688,8 @@
             mClearAllButtonDeadZoneRect.inset(-getPaddingRight() / 2, -verticalMargin);
         }
 
-        // Get the deadzone rect between the task views
-        mTaskViewDeadZoneRect.setEmpty();
-        int count = getTaskViewCount();
-        if (count > 0) {
-            final View taskView = requireTaskViewAt(0);
-            requireTaskViewAt(count - 1).getHitRect(mTaskViewDeadZoneRect);
-            mTaskViewDeadZoneRect.union(taskView.getLeft(), taskView.getTop(), taskView.getRight(),
-                    taskView.getBottom());
-        }
+        mUtils.updateTaskViewDeadZoneRect(mTaskViewDeadZoneRect, mTopRowDeadZoneRect,
+                mBottomRowDeadZoneRect);
     }
 
     private void updateEmptyStateUi(boolean sizeChanged) {
@@ -5569,7 +5758,7 @@
             anim.play(ObjectAnimator.ofFloat(this, FULLSCREEN_PROGRESS, 1));
             anim.addListener(new AnimatorListenerAdapter() {
                 @Override
-                public void onAnimationStart(@NonNull Animator animation, boolean isReverse) {
+                public void onAnimationStart(@NonNull Animator animation) {
                     taskView.getThumbnailBounds(mTempRect, /*relativeToDragLayer=*/true);
                     getTaskDimension(mContext, mContainer.getDeviceProfile(), mTempPointF);
                     Rect fullscreenBounds = new Rect(0, 0, (int) mTempPointF.x,
@@ -5591,6 +5780,18 @@
                                 });
                     }
                 }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    // If live tile is not launching, reset the pivot applied above.
+                    if (!taskView.isRunningTask()) {
+                        runActionOnRemoteHandles(
+                                remoteTargetHandle -> {
+                                    remoteTargetHandle.getTaskViewSimulator().setPivotOverride(
+                                            null);
+                                });
+                    }
+                }
             });
         } else if (!showAsGrid) {
             // We are launching an adjacent task, so parallax the center and other adjacent task.
@@ -5625,6 +5826,9 @@
         if (taskView instanceof DesktopTaskView) {
             anim.play(ObjectAnimator.ofArgb(mContainer.getScrimView(), VIEW_BACKGROUND_COLOR,
                     Color.TRANSPARENT));
+            if (enableDesktopExplodedView()) {
+                anim.play(ObjectAnimator.ofFloat(this, DESK_EXPLODE_PROGRESS, 1f, 0f));
+            }
         }
         DepthController depthController = getDepthController();
         if (depthController != null) {
@@ -5640,31 +5844,28 @@
      * Returns the scale up required on the view, so that it coves the screen completely
      */
     public float getMaxScaleForFullScreen() {
-        if (enableGridOnlyOverview() && mContainer.getDeviceProfile().isTablet
-                && !mOverviewGridEnabled) {
-            if (mLastComputedCarouselTaskSize.isEmpty()) {
-                mSizeStrategy.calculateCarouselTaskSize(mContainer, mContainer.getDeviceProfile(),
-                        mLastComputedCarouselTaskSize, getPagedOrientationHandler());
-            }
-            mTempRect.set(mLastComputedCarouselTaskSize);
-        } else {
-            if (mLastComputedTaskSize.isEmpty()) {
-                getTaskSize(mLastComputedTaskSize);
-            }
-            mTempRect.set(mLastComputedTaskSize);
+        if (mLastComputedTaskSize.isEmpty()) {
+            getTaskSize(mLastComputedTaskSize);
         }
+        mTempRect.set(mLastComputedTaskSize);
         return getPagedViewOrientedState().getFullScreenScaleAndPivot(
                 mTempRect, mContainer.getDeviceProfile(), mTempPointF);
     }
 
+    /**
+     * Clears the existing PendingAnimation.
+     */
+    public void clearPendingAnimation() {
+        mPendingAnimation = null;
+    }
+
     public PendingAnimation createTaskLaunchAnimation(
             TaskView taskView, long duration, Interpolator interpolator) {
         if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
             throw new IllegalStateException("Another pending animation is still running");
         }
 
-        int count = getTaskViewCount();
-        if (count == 0) {
+        if (!hasTaskViews()) {
             return new PendingAnimation(duration);
         }
 
@@ -5673,7 +5874,7 @@
         updateGridProperties();
         updateScrollSynchronously();
 
-        int targetSysUiFlags = taskView.getTaskContainers().getFirst().getSysUiStatusNavFlags();
+        int targetSysUiFlags = taskView.getSysUiStatusNavFlags();
         final boolean[] passedOverviewThreshold = new boolean[]{false};
         AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(taskView);
         anim.play(new AnimatedFloat(v -> {
@@ -5702,10 +5903,12 @@
 
         mPendingAnimation = new PendingAnimation(duration);
         mPendingAnimation.add(anim);
-        runActionOnRemoteHandles(
-                remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
-                        .addOverviewToAppAnim(mPendingAnimation, interpolator));
-        mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
+        if (taskView.isRunningTask()) {
+            runActionOnRemoteHandles(
+                    remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
+                            .addOverviewToAppAnim(mPendingAnimation, interpolator));
+            mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
+        }
         mPendingAnimation.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
@@ -5731,7 +5934,7 @@
                 } else {
                     taskView.launchWithoutAnimation(this::onTaskLaunchAnimationEnd);
                 }
-                mContainer.getStatsLogManager().logger().withItemInfo(taskView.getFirstItemInfo())
+                mContainer.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo())
                         .log(LAUNCHER_TASK_LAUNCH_SWIPE_DOWN);
             } else {
                 onTaskLaunchAnimationEnd(false);
@@ -5770,34 +5973,33 @@
 
     @Override
     public void addChildrenForAccessibility(ArrayList<View> outChildren) {
-        // Add children in reverse order
-        for (int i = getChildCount() - 1; i >= 0; --i) {
-            outChildren.add(getChildAt(i));
-        }
+        outChildren.addAll(getAccessibilityChildren());
+    }
+
+    public List<View> getAccessibilityChildren() {
+        return mUtils.getAccessibilityChildren();
     }
 
     @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         final AccessibilityNodeInfo.CollectionInfo
-                collectionInfo = AccessibilityNodeInfo.CollectionInfo.obtain(
-                1, getTaskViewCount(), false,
-                AccessibilityNodeInfo.CollectionInfo.SELECTION_MODE_NONE);
+                collectionInfo = new AccessibilityNodeInfo.CollectionInfo(
+                1, getAccessibilityChildren().size(), false);
         info.setCollectionInfo(collectionInfo);
     }
 
     @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
-
-        final int taskViewCount = getTaskViewCount();
-        event.setScrollable(taskViewCount > 0);
+        event.setScrollable(hasTaskViews());
 
         if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
+            final List<View> accessibilityChildren = getAccessibilityChildren();
             final int[] visibleTasks = getVisibleChildrenRange();
-            event.setFromIndex(taskViewCount - visibleTasks[1]);
-            event.setToIndex(taskViewCount - visibleTasks[0]);
-            event.setItemCount(taskViewCount);
+            event.setFromIndex(accessibilityChildren.indexOf(getChildAt(visibleTasks[1])));
+            event.setToIndex(accessibilityChildren.indexOf(getChildAt(visibleTasks[0])));
+            event.setItemCount(accessibilityChildren.size());
         }
     }
 
@@ -5816,6 +6018,10 @@
         mEnableDrawingLiveTile = enableDrawingLiveTile;
     }
 
+    public boolean getEnableDrawingLiveTile() {
+        return mEnableDrawingLiveTile;
+    }
+
     public void redrawLiveTile() {
         runActionOnRemoteHandles(remoteTargetHandle -> {
             TransformParams params = remoteTargetHandle.getTransformParams();
@@ -5845,7 +6051,8 @@
         if (recentsAnimationTargets.hasDesktopTasks(mContext)) {
             gluer = new RemoteTargetGluer(getContext(), getSizeStrategy(), recentsAnimationTargets,
                     true /* forDesktop */);
-            mRemoteTargetHandles = gluer.assignTargetsForDesktop(recentsAnimationTargets);
+            mRemoteTargetHandles = gluer.assignTargetsForDesktop(
+                    recentsAnimationTargets, /* transitionInfo= */ null);
         } else {
             gluer = new RemoteTargetGluer(getContext(), getSizeStrategy(), recentsAnimationTargets,
                     false);
@@ -5858,7 +6065,7 @@
         // mSyncTransactionApplier doesn't get transferred over
         runActionOnRemoteHandles(remoteTargetHandle -> {
             final TransformParams params = remoteTargetHandle.getTransformParams();
-            if (Flags.enableFallbackOverviewInWindow() || Flags.enableLauncherOverviewInWindow()) {
+            if (RecentsWindowFlags.Companion.getEnableOverviewInWindow()) {
                 params.setHomeBuilderProxy((builder, app, transformParams) -> {
                     mTmpMatrix.setScale(
                             1f, 1f, app.localBounds.exactCenterX(), app.localBounds.exactCenterY());
@@ -6023,7 +6230,7 @@
 
     @Override
     protected int computeMinScroll() {
-        if (getTaskViewCount() <= 0) {
+        if (!hasTaskViews()) {
             return super.computeMinScroll();
         }
 
@@ -6032,7 +6239,7 @@
 
     @Override
     protected int computeMaxScroll() {
-        if (getTaskViewCount() <= 0) {
+        if (!hasTaskViews()) {
             return super.computeMaxScroll();
         }
 
@@ -6040,21 +6247,19 @@
     }
 
     private int getFirstViewIndex() {
-        final TaskView firstView;
+        final View firstView;
         if (mShowAsGridLastOnLayout) {
             // For grid Overview, it always start if a large tile (focused task or desktop task) if
             // they exist, otherwise it start with the first task.
-            TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView(getTaskViews(),
-                    isSplitSelectionActive());
+            TaskView firstLargeTaskView = mUtils.getFirstLargeTaskView();
             if (firstLargeTaskView != null) {
                 firstView = firstLargeTaskView;
             } else {
-                firstView = mUtils.getFirstSmallTaskView(getTaskViews());
+                firstView = mUtils.getFirstSmallTaskView();
             }
         } else {
             firstView = mUtils.getFirstTaskViewInCarousel(
-                    /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0,
-                    getTaskViews(), getRunningTaskView());
+                    /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0);
         }
         return indexOfChild(firstView);
     }
@@ -6071,12 +6276,11 @@
             if (lastGridTaskView != null) {
                 lastView = lastGridTaskView;
             } else {
-                lastView = mUtils.getLastLargeTaskView(getTaskViews());
+                lastView = mUtils.getLastLargeTaskView();
             }
         } else {
             lastView = mUtils.getLastTaskViewInCarousel(
-                    /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0,
-                    getTaskViews(), getRunningTaskView());
+                    /*nonRunningTaskCarouselHidden=*/mDesktopCarouselDetachProgress > 0);
         }
         return indexOfChild(lastView);
     }
@@ -6104,42 +6308,42 @@
             mClearAllButton.setScrollOffsetPrimary(mIsRtl ? clearAllWidthDiff : -clearAllWidthDiff);
         }
 
-        boolean pageScrollChanged = false;
-
+        int[] oldPageScrolls = Arrays.copyOf(outPageScrolls, outPageScrolls.length);
         int clearAllIndex = indexOfChild(mClearAllButton);
         int clearAllScroll = 0;
         int clearAllWidth = getPagedOrientationHandler().getPrimarySize(mClearAllButton);
         if (clearAllIndex != -1 && clearAllIndex < outPageScrolls.length) {
             float scrollDiff = mClearAllButton.getScrollAdjustment(showAsFullscreen, showAsGrid);
-            clearAllScroll = newPageScrolls[clearAllIndex] + (int) scrollDiff;
-            if (outPageScrolls[clearAllIndex] != clearAllScroll) {
-                pageScrollChanged = true;
-                outPageScrolls[clearAllIndex] = clearAllScroll;
-            }
+            clearAllScroll = newPageScrolls[clearAllIndex] + Math.round(scrollDiff);
+            outPageScrolls[clearAllIndex] = clearAllScroll;
         }
 
-        final int taskCount = getTaskViewCount();
         int lastTaskScroll = getLastTaskScroll(clearAllScroll, clearAllWidth);
-        for (int i = 0; i < taskCount; i++) {
-            TaskView taskView = requireTaskViewAt(i);
+        getTaskViews().forEachWithIndexInParent((index, taskView) -> {
             float scrollDiff = taskView.getScrollAdjustment(showAsGrid);
-            int pageScroll = newPageScrolls[i] + Math.round(scrollDiff);
+            int pageScroll = newPageScrolls[index] + Math.round(scrollDiff);
             if ((mIsRtl && pageScroll < lastTaskScroll)
                     || (!mIsRtl && pageScroll > lastTaskScroll)) {
                 pageScroll = lastTaskScroll;
             }
-            if (outPageScrolls[i] != pageScroll) {
-                pageScrollChanged = true;
-                outPageScrolls[i] = pageScroll;
-            }
+            outPageScrolls[index] = pageScroll;
             if (DEBUG) {
-                Log.d(TAG, "getPageScrolls - outPageScrolls[" + i + "]: " + outPageScrolls[i]);
+                Log.d(TAG,
+                        "getPageScrolls - outPageScrolls[" + index + "]: " + outPageScrolls[index]);
+            }
+        });
+
+        int addDesktopButtonIndex = indexOfChild(mAddDesktopButton);
+        if (addDesktopButtonIndex >= 0 && addDesktopButtonIndex < outPageScrolls.length) {
+            int firstViewIndex = getFirstViewIndex();
+            if (firstViewIndex >= 0 && firstViewIndex < outPageScrolls.length) {
+                outPageScrolls[addDesktopButtonIndex] = outPageScrolls[firstViewIndex];
             }
         }
         if (DEBUG) {
             Log.d(TAG, "getPageScrolls - clearAllScroll: " + clearAllScroll);
         }
-        return pageScrollChanged;
+        return !Arrays.equals(oldPageScrolls, outPageScrolls);
     }
 
     @Override
@@ -6156,12 +6360,12 @@
     }
 
     @Override
-    protected int getChildVisibleSize(int index) {
-        final TaskView taskView = getTaskViewAt(index);
+    protected int getChildVisibleSize(int childIndex) {
+        final TaskView taskView = getTaskViewAt(childIndex);
         if (taskView == null) {
-            return super.getChildVisibleSize(index);
+            return super.getChildVisibleSize(childIndex);
         }
-        return (int) (super.getChildVisibleSize(index) * taskView.getSizeAdjustment(
+        return (int) (super.getChildVisibleSize(childIndex) * taskView.getSizeAdjustment(
                 showAsFullscreen()));
     }
 
@@ -6169,6 +6373,11 @@
         return mClearAllButton;
     }
 
+    @Nullable
+    public AddDesktopButton getAddDeskButton() {
+        return mAddDesktopButton;
+    }
+
     /**
      * @return How many pixels the running task is offset on the currently laid out dominant axis.
      */
@@ -6220,7 +6429,7 @@
      * Returns how many pixels the page is offset on the currently laid out dominant axis.
      */
     private int getUnclampedScrollOffset(int pageIndex) {
-        if (pageIndex == -1) {
+        if (pageIndex == INVALID_PAGE) {
             return 0;
         }
         // Don't dampen the scroll (due to overscroll) if the adjacent tasks are offscreen, so that
@@ -6239,7 +6448,8 @@
      * Returns how many pixels the page is offset from its scroll position.
      */
     private int getOffsetFromScrollPosition(int pageIndex) {
-        return getOffsetFromScrollPosition(pageIndex, getTopRowIdArray(), getBottomRowIdArray());
+        return getOffsetFromScrollPosition(pageIndex, mUtils.getTopRowIdArray(),
+                mUtils.getBottomRowIdArray());
     }
 
     private int getOffsetFromScrollPosition(
@@ -6289,7 +6499,7 @@
     }
 
     /**
-     * @return true if the task in on the top of the grid
+     * @return true if the task in on the bottom of the grid
      */
     public boolean isOnGridBottomRow(TaskView taskView) {
         return showAsGrid()
@@ -6412,8 +6622,7 @@
             return;
         }
 
-        Map<Integer, ThumbnailData> updatedThumbnails = mUtils.screenshotTasks(taskView,
-                mRecentsAnimationController);
+        Map<Integer, ThumbnailData> updatedThumbnails = mUtils.screenshotTasks(taskView);
         if (enableRefactorTaskThumbnail()) {
             mHelper.switchToScreenshot(taskView, updatedThumbnails, onFinishRunnable);
         } else {
@@ -6483,6 +6692,12 @@
         return mSizeStrategy;
     }
 
+
+    /**
+     * Returns the container interface
+     */
+    protected abstract BaseContainerInterface<STATE_TYPE, ?> getContainerInterface(int displayId);
+
     /**
      * Set all the task views to color tint scrim mode, dimming or tinting them all. Allows the
      * tasks to be dimmed while other elements in the recents view are left alone.
@@ -6506,10 +6721,6 @@
     private void setColorTint(float tintAmount) {
         mColorTint = tintAmount;
 
-        if (enableRefactorTaskThumbnail()) {
-            mRecentsViewModel.setTintAmount(tintAmount);
-        }
-
         for (TaskView taskView : getTaskViews()) {
             taskView.setColorTint(mColorTint, mTintingColor);
         }
@@ -6537,7 +6748,7 @@
                 .displayOverviewTasksAsGrid(mContainer.getDeviceProfile()));
     }
 
-    private boolean showAsFullscreen() {
+    protected boolean showAsFullscreen() {
         return mOverviewFullscreenEnabled
                 && mCurrentGestureEndTarget != GestureState.GestureEndTarget.RECENTS;
     }
@@ -6747,6 +6958,63 @@
         }
     }
 
+    @Override
+    public void onCanCreateDesksChanged(boolean canCreateDesks) {
+        // TODO: b/389209338 - update the AddDesktopButton's visibility on this.
+    }
+
+    @Override
+    public void onDeskAdded(int displayId, int deskId) {
+        // Ignore desk changes that don't belong to this display.
+        if (displayId != mContainer.getDisplay().getDisplayId()) {
+            return;
+        }
+
+        if (mUtils.getDesktopTaskViewForDeskId(deskId) != null) {
+            Log.e(TAG, "A task view for this desk has already been added.");
+            return;
+        }
+
+        // We assume that a newly added desk is always empty and gets added to the left of the
+        // `AddNewDesktopButton`.
+        DesktopTaskView desktopTaskView =
+                (DesktopTaskView) getTaskViewFromPool(TaskViewType.DESKTOP);
+        desktopTaskView.bind(new DesktopTask(deskId, new ArrayList<>()),
+                mOrientationState, mTaskOverlayFactory);
+
+        Objects.requireNonNull(mAddDesktopButton);
+        final int insertionIndex = 1 + indexOfChild(mAddDesktopButton);
+        addView(desktopTaskView, insertionIndex);
+
+        updateTaskSize();
+        updateChildTaskOrientations();
+
+        // TODO: b/401002178 - Recalculate the new current page such that the addition of the new
+        //  desk does not result in a change in the current scroll page.
+    }
+
+    @Override
+    public void onDeskRemoved(int displayId, int deskId) {
+        // Ignore desk changes that don't belong to this display.
+        if (displayId != mContainer.getDisplay().getDisplayId()) {
+            return;
+        }
+
+        // We need to distinguish between desk removals that are triggered from outside of overview
+        // vs. the ones that were initiated from overview by dismissing the corresponding desktop
+        // task view.
+        var taskView = mUtils.getDesktopTaskViewForDeskId(deskId);
+        if (taskView != null) {
+            dismissTaskView(taskView, true, true);
+        }
+    }
+
+    @Override
+    public void onActiveDeskChanged(int displayId, int newActiveDesk, int oldActiveDesk) {
+        // TODO: b/400870600 - We may need to add code here to special case when an empty desk gets
+        // activated, since `RemoteDesktopLaunchTransitionRunner` doesn't always get triggered.
+    }
+
     /** Get the color used for foreground scrimming the RecentsView for sharing. */
     public static int getForegroundScrimDimColor(Context context) {
         return context.getColor(R.color.overview_foreground_scrim_color);
@@ -6797,8 +7065,8 @@
             return;
         }
 
-        mDesktopRecentsTransitionController.moveToDesktop(taskContainer, transitionSource);
-        successCallback.run();
+        mDesktopRecentsTransitionController.moveToDesktop(taskContainer, transitionSource,
+                successCallback);
     }
 
     /**
@@ -6839,6 +7107,35 @@
         }
     }
 
+    private int getFontWeight() {
+        int fontWeightAdjustment = getResources().getConfiguration().fontWeightAdjustment;
+        if (fontWeightAdjustment != Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED) {
+            return Typeface.Builder.NORMAL_WEIGHT + fontWeightAdjustment;
+        }
+        return Typeface.Builder.NORMAL_WEIGHT;
+    }
+
+    /**
+     * Creates the spring animations which run as a task settles back into its place in overview.
+     *
+     * <p>When a task dismiss is cancelled, the task will return to its original position via a
+     * spring animation. As it passes the threshold of its settling state, its neighbors will
+     * spring in response to the perceived impact of the settling task.
+     */
+    public SpringAnimation createTaskDismissSettlingSpringAnimation(TaskView draggedTaskView,
+            float velocity, boolean isDismissing, SingleAxisSwipeDetector detector,
+            int dismissLength, Function0<Unit> onEndRunnable) {
+        return mDismissUtils.createTaskDismissSettlingSpringAnimation(draggedTaskView, velocity,
+                isDismissing, detector, dismissLength, onEndRunnable);
+    }
+
+    /**
+     * Animates RecentsView's scale to the provided value, using spring animations.
+     */
+    public SpringAnimation animateRecentsScale(float scale) {
+        return mDismissUtils.animateRecentsScale(scale);
+    }
+
     public interface TaskLaunchListener {
         void onTaskLaunched();
     }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
index a1d22fe..e61d402 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewContainer.java
@@ -29,7 +29,6 @@
 
 import com.android.launcher3.BaseActivity;
 import com.android.launcher3.logger.LauncherAtom;
-import com.android.launcher3.statehandlers.DesktopVisibilityController;
 import com.android.launcher3.taskbar.TaskbarUIController;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.ScrimView;
@@ -64,19 +63,6 @@
     <T extends View> T getOverviewPanel();
 
     /**
-     * Returns the RootView
-     */
-    View getRootView();
-
-    /**
-     * Dispatches a generic motion event to the view hierarchy.
-     * Returns the current RecentsViewContainer as context
-     */
-    default Context asContext() {
-        return (Context) this;
-    }
-
-    /**
      * @see Window.Callback#dispatchGenericMotionEvent(MotionEvent)
      */
     boolean dispatchGenericMotionEvent(MotionEvent ev);
@@ -209,9 +195,6 @@
                         .build());
     }
 
-    @Nullable
-    DesktopVisibilityController getDesktopVisibilityController();
-
     void setTaskbarUIController(@Nullable TaskbarUIController taskbarUIController);
 
     @Nullable TaskbarUIController getTaskbarUIController();
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt b/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
index 3616fbb..ff711da 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewModelHelper.kt
@@ -16,28 +16,24 @@
 
 package com.android.quickstep.views
 
+import com.android.launcher3.util.coroutines.DispatcherProvider
 import com.android.quickstep.ViewUtils
 import com.android.quickstep.recents.viewmodel.RecentsViewModel
 import com.android.systemui.shared.recents.model.ThumbnailData
-import kotlinx.coroutines.CoroutineName
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
 /** Helper for [RecentsView] to interact with the [RecentsViewModel]. */
-class RecentsViewModelHelper(private val recentsViewModel: RecentsViewModel) {
-    private lateinit var viewAttachedScope: CoroutineScope
-
-    fun onAttachedToWindow() {
-        viewAttachedScope =
-            CoroutineScope(SupervisorJob() + Dispatchers.Default + CoroutineName("RecentsView"))
-    }
-
-    fun onDetachedFromWindow() {
-        viewAttachedScope.cancel("RecentsView detaching from window")
+class RecentsViewModelHelper(
+    private val recentsViewModel: RecentsViewModel,
+    private val recentsCoroutineScope: CoroutineScope,
+    private val dispatcherProvider: DispatcherProvider,
+) {
+    fun onDestroy() {
+        recentsCoroutineScope.cancel("RecentsView is being destroyed")
     }
 
     fun switchToScreenshot(
@@ -48,10 +44,12 @@
         // Update recentsViewModel and apply the thumbnailOverride ASAP, before waiting inside
         // viewAttachedScope.
         recentsViewModel.setRunningTaskShowScreenshot(true)
-        viewAttachedScope.launch {
+        recentsCoroutineScope.launch(dispatcherProvider.background) {
             recentsViewModel.waitForRunningTaskShowScreenshotToUpdate()
             recentsViewModel.waitForThumbnailsToUpdate(updatedThumbnails)
-            withContext(Dispatchers.Main) { ViewUtils.postFrameDrawn(taskView, onFinishRunnable) }
+            withContext(Dispatchers.Main.immediate) {
+                ViewUtils.postFrameDrawn(taskView, onFinishRunnable)
+            }
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
new file mode 100644
index 0000000..037bef6
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/RecentsViewUtils.kt
@@ -0,0 +1,369 @@
+/*
+ * 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.views
+
+import android.graphics.Rect
+import android.util.FloatProperty
+import android.view.View
+import androidx.core.view.children
+import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
+import com.android.launcher3.Flags.enableSeparateExternalDisplayTasks
+import com.android.launcher3.statehandlers.DesktopVisibilityController
+import com.android.launcher3.statehandlers.DesktopVisibilityController.Companion.INACTIVE_DESK_ID
+import com.android.launcher3.util.IntArray
+import com.android.quickstep.util.DesksUtils
+import com.android.quickstep.util.DesktopTask
+import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.isExternalDisplay
+import com.android.quickstep.views.RecentsView.RUNNING_TASK_ATTACH_ALPHA
+import com.android.systemui.shared.recents.model.ThumbnailData
+import java.util.function.BiConsumer
+import kotlin.reflect.KMutableProperty1
+
+/**
+ * Helper class for [RecentsView]. This util class contains refactored and extracted functions from
+ * RecentsView to facilitate the implementation of unit tests.
+ */
+class RecentsViewUtils(private val recentsView: RecentsView<*, *>) {
+    val taskViews = TaskViewsIterable(recentsView)
+
+    /** Takes a screenshot of all [taskView] and return map of taskId to the screenshot */
+    fun screenshotTasks(taskView: TaskView): Map<Int, ThumbnailData> {
+        val recentsAnimationController = recentsView.recentsAnimationController ?: return emptyMap()
+        return taskView.taskContainers.associate {
+            it.task.key.id to recentsAnimationController.screenshotTask(it.task.key.id)
+        }
+    }
+
+    /**
+     * Sorts task groups to move desktop tasks to the end of the list.
+     *
+     * @param tasks List of group tasks to be sorted.
+     * @return Sorted list of GroupTasks to be used in the RecentsView.
+     */
+    fun sortDesktopTasksToFront(tasks: List<GroupTask>): List<GroupTask> {
+        var (desktopTasks, otherTasks) = tasks.partition { it.taskViewType == TaskViewType.DESKTOP }
+        if (DesksUtils.areMultiDesksFlagsEnabled()) {
+            // Desk IDs of newer desks are larger than those of older desks, hence we can use them
+            // to sort desks from old to new.
+            desktopTasks = desktopTasks.sortedBy { (it as DesktopTask).deskId }
+        }
+        return otherTasks + desktopTasks
+    }
+
+    fun sortExternalDisplayTasksToFront(tasks: List<GroupTask>): List<GroupTask> {
+        val (externalDisplayTasks, otherTasks) =
+            tasks.partition { it.tasks.firstOrNull().isExternalDisplay }
+        return otherTasks + externalDisplayTasks
+    }
+
+    class TaskViewsIterable(val recentsView: RecentsView<*, *>) : Iterable<TaskView> {
+        /** Iterates TaskViews when its index inside the RecentsView is needed. */
+        fun forEachWithIndexInParent(consumer: BiConsumer<Int, TaskView>) {
+            recentsView.children.forEachIndexed { index, child ->
+                (child as? TaskView)?.let { consumer.accept(index, it) }
+            }
+        }
+
+        override fun iterator(): Iterator<TaskView> =
+            recentsView.children.mapNotNull { it as? TaskView }.iterator()
+    }
+
+    /** Counts [TaskView]s that are [DesktopTaskView] instances. */
+    private fun getDesktopTaskViewCount(): Int = taskViews.count { it is DesktopTaskView }
+
+    /** Counts [TaskView]s that are not [DesktopTaskView] instances. */
+    fun getNonDesktopTaskViewCount(): Int = taskViews.count { it !is DesktopTaskView }
+
+    /** Returns a list of all large TaskView Ids from [TaskView]s */
+    fun getLargeTaskViewIds(): List<Int> = taskViews.filter { it.isLargeTile }.map { it.taskViewId }
+
+    /** Returns a list of all large TaskViews [TaskView]s */
+    fun getLargeTaskViews(): List<TaskView> = taskViews.filter { it.isLargeTile }
+
+    /** Returns all the TaskViews in the top row, without the focused task */
+    fun getTopRowTaskViews(): List<TaskView> =
+        taskViews.filter { recentsView.mTopRowIdSet.contains(it.taskViewId) }
+
+    /** Returns all the task Ids in the top row, without the focused task */
+    fun getTopRowIdArray(): IntArray = getTopRowTaskViews().map { it.taskViewId }.toIntArray()
+
+    /** Returns all the TaskViews in the bottom row, without the focused task */
+    fun getBottomRowTaskViews(): List<TaskView> =
+        taskViews.filter { !recentsView.mTopRowIdSet.contains(it.taskViewId) && !it.isLargeTile }
+
+    /** Returns all the task Ids in the bottom row, without the focused task */
+    fun getBottomRowIdArray(): IntArray = getBottomRowTaskViews().map { it.taskViewId }.toIntArray()
+
+    private fun List<Int>.toIntArray() = IntArray(size).apply { this@toIntArray.forEach(::add) }
+
+    /** Counts [TaskView]s that are large tiles. */
+    fun getLargeTileCount(): Int = taskViews.count { it.isLargeTile }
+
+    /** Counts [TaskView]s that are grid tasks. */
+    fun getGridTaskCount(): Int = taskViews.count { it.isGridTask }
+
+    /** Returns the first TaskView that should be displayed as a large tile. */
+    fun getFirstLargeTaskView(): TaskView? =
+        taskViews.firstOrNull {
+            it.isLargeTile && !(recentsView.isSplitSelectionActive && it is DesktopTaskView)
+        }
+
+    /**
+     * Returns the [DesktopTaskView] that matches the given [deskId], or null if it doesn't exist.
+     */
+    fun getDesktopTaskViewForDeskId(deskId: Int): DesktopTaskView? {
+        if (deskId == INACTIVE_DESK_ID) {
+            return null
+        }
+        return taskViews.firstOrNull { it is DesktopTaskView && it.deskId == deskId }
+            as? DesktopTaskView
+    }
+
+    /** Returns the active desk ID of the display that contains the [recentsView] instance. */
+    fun getActiveDeskIdOnThisDisplay(): Int =
+        DesktopVisibilityController.INSTANCE.get(recentsView.context)
+            .getActiveDeskId(recentsView.mContainer.display.displayId)
+
+    /** Returns the expected focus task. */
+    fun getFirstNonDesktopTaskView(): TaskView? =
+        if (enableLargeDesktopWindowingTile()) taskViews.firstOrNull { it !is DesktopTaskView }
+        else taskViews.firstOrNull()
+
+    /**
+     * Returns the [TaskView] that should be the current page during task binding, in the following
+     * priorities:
+     * 1. Running task
+     * 2. Focused task
+     * 3. First non-desktop task
+     * 4. Last desktop task
+     * 5. null otherwise
+     */
+    fun getExpectedCurrentTask(runningTaskView: TaskView?, focusedTaskView: TaskView?): TaskView? =
+        runningTaskView
+            ?: focusedTaskView
+            ?: taskViews.firstOrNull {
+                it !is DesktopTaskView &&
+                    !(enableSeparateExternalDisplayTasks() && it.isExternalDisplay)
+            }
+            ?: taskViews.lastOrNull()
+
+    private fun getDeviceProfile() = (recentsView.mContainer as RecentsViewContainer).deviceProfile
+
+    fun getRunningTaskExpectedIndex(runningTaskView: TaskView): Int {
+        val firstTaskViewIndex = recentsView.indexOfChild(getFirstTaskView())
+        return if (getDeviceProfile().isTablet) {
+            var index = firstTaskViewIndex
+            if (enableLargeDesktopWindowingTile() && runningTaskView !is DesktopTaskView) {
+                // For fullsreen tasks, skip over Desktop tasks in its section
+                index +=
+                    if (enableSeparateExternalDisplayTasks()) {
+                        if (runningTaskView.isExternalDisplay) {
+                            taskViews.count { it is DesktopTaskView && it.isExternalDisplay }
+                        } else {
+                            taskViews.count { it is DesktopTaskView && !it.isExternalDisplay }
+                        }
+                    } else {
+                        getDesktopTaskViewCount()
+                    }
+            }
+            if (enableSeparateExternalDisplayTasks() && !runningTaskView.isExternalDisplay) {
+                // For main display section, skip over external display tasks
+                index += taskViews.count { it.isExternalDisplay }
+            }
+            index
+        } else {
+            val currentIndex: Int = recentsView.indexOfChild(runningTaskView)
+            return if (currentIndex != -1) {
+                currentIndex // Keep the position if running task already in layout.
+            } else {
+                // New running task are added to the front to begin with.
+                firstTaskViewIndex
+            }
+        }
+    }
+
+    /** Returns the first TaskView if it exists, or null otherwise. */
+    fun getFirstTaskView(): TaskView? = taskViews.firstOrNull()
+
+    /** Returns the last TaskView if it exists, or null otherwise. */
+    fun getLastTaskView(): TaskView? = taskViews.lastOrNull()
+
+    /** Returns the first TaskView that is not large */
+    fun getFirstSmallTaskView(): TaskView? = taskViews.firstOrNull { !it.isLargeTile }
+
+    /** Returns the last TaskView that should be displayed as a large tile. */
+    fun getLastLargeTaskView(): TaskView? = taskViews.lastOrNull { it.isLargeTile }
+
+    /**
+     * Gets the list of accessibility children. Currently all the children of RecentsViews are
+     * added, and in the reverse order to the list.
+     */
+    fun getAccessibilityChildren(): List<View> = recentsView.children.toList().reversed()
+
+    @JvmOverloads
+    /** Returns the first [TaskView], with some tasks possibly hidden in the carousel. */
+    fun getFirstTaskViewInCarousel(
+        nonRunningTaskCarouselHidden: Boolean,
+        runningTaskView: TaskView? = recentsView.runningTaskView,
+    ): TaskView? =
+        taskViews.firstOrNull {
+            it.isVisibleInCarousel(runningTaskView, nonRunningTaskCarouselHidden)
+        }
+
+    /** Returns the last [TaskView], with some tasks possibly hidden in the carousel. */
+    fun getLastTaskViewInCarousel(nonRunningTaskCarouselHidden: Boolean): TaskView? =
+        taskViews.lastOrNull {
+            it.isVisibleInCarousel(recentsView.runningTaskView, nonRunningTaskCarouselHidden)
+        }
+
+    /** Returns if any small tasks are fully visible */
+    fun isAnySmallTaskFullyVisible(): Boolean =
+        taskViews.any { !it.isLargeTile && recentsView.isTaskViewFullyVisible(it) }
+
+    /** Apply attachAlpha to all [TaskView] accordingly to different conditions. */
+    fun applyAttachAlpha(nonRunningTaskCarouselHidden: Boolean) {
+        taskViews.forEach { taskView ->
+            taskView.attachAlpha =
+                if (taskView == recentsView.runningTaskView) {
+                    RUNNING_TASK_ATTACH_ALPHA.get(recentsView)
+                } else {
+                    if (
+                        taskView.isVisibleInCarousel(
+                            recentsView.runningTaskView,
+                            nonRunningTaskCarouselHidden,
+                        )
+                    )
+                        1f
+                    else 0f
+                }
+        }
+    }
+
+    fun TaskView.isVisibleInCarousel(
+        runningTaskView: TaskView?,
+        nonRunningTaskCarouselHidden: Boolean,
+    ): Boolean =
+        if (!nonRunningTaskCarouselHidden) true
+        else getCarouselType() == runningTaskView.getCarouselType()
+
+    /** Returns the carousel type of the TaskView, and default to fullscreen if it's null. */
+    private fun TaskView?.getCarouselType(): TaskViewCarousel =
+        if (this is DesktopTaskView) TaskViewCarousel.DESKTOP else TaskViewCarousel.FULL_SCREEN
+
+    private enum class TaskViewCarousel {
+        FULL_SCREEN,
+        DESKTOP,
+    }
+
+    /** Returns true if there are at least one TaskView has been added to the RecentsView. */
+    fun hasTaskViews() = taskViews.any()
+
+    fun getTaskContainerById(taskId: Int) =
+        taskViews.firstNotNullOfOrNull { it.getTaskContainerById(taskId) }
+
+    private fun getRowRect(firstView: View?, lastView: View?, outRowRect: Rect) {
+        outRowRect.setEmpty()
+        firstView?.let {
+            it.getHitRect(TEMP_RECT)
+            outRowRect.union(TEMP_RECT)
+        }
+        lastView?.let {
+            it.getHitRect(TEMP_RECT)
+            outRowRect.union(TEMP_RECT)
+        }
+    }
+
+    private fun getRowRect(rowTaskViewIds: IntArray, outRowRect: Rect) {
+        if (rowTaskViewIds.isEmpty) {
+            outRowRect.setEmpty()
+            return
+        }
+        getRowRect(
+            recentsView.getTaskViewFromTaskViewId(rowTaskViewIds.get(0)),
+            recentsView.getTaskViewFromTaskViewId(rowTaskViewIds.get(rowTaskViewIds.size() - 1)),
+            outRowRect,
+        )
+    }
+
+    fun updateTaskViewDeadZoneRect(
+        outTaskViewRowRect: Rect,
+        outTopRowRect: Rect,
+        outBottomRowRect: Rect,
+    ) {
+        if (!getDeviceProfile().isTablet) {
+            getRowRect(getFirstTaskView(), getLastTaskView(), outTaskViewRowRect)
+            return
+        }
+        getRowRect(getFirstLargeTaskView(), getLastLargeTaskView(), outTaskViewRowRect)
+        getRowRect(getTopRowIdArray(), outTopRowRect)
+        getRowRect(getBottomRowIdArray(), outBottomRowRect)
+
+        // Expand large tile Rect to include space between top/bottom row.
+        val nonEmptyRowRect =
+            when {
+                !outTopRowRect.isEmpty -> outTopRowRect
+                !outBottomRowRect.isEmpty -> outBottomRowRect
+                else -> return
+            }
+        if (recentsView.isRtl) {
+            if (outTaskViewRowRect.left > nonEmptyRowRect.right) {
+                outTaskViewRowRect.left = nonEmptyRowRect.right
+            }
+        } else {
+            if (outTaskViewRowRect.right < nonEmptyRowRect.left) {
+                outTaskViewRowRect.right = nonEmptyRowRect.left
+            }
+        }
+
+        // Expand the shorter row Rect to include the space between the 2 rows.
+        if (outTopRowRect.isEmpty || outBottomRowRect.isEmpty) return
+        if (outTopRowRect.width() <= outBottomRowRect.width()) {
+            if (outTopRowRect.bottom < outBottomRowRect.top) {
+                outTopRowRect.bottom = outBottomRowRect.top
+            }
+        } else {
+            if (outBottomRowRect.top > outTopRowRect.bottom) {
+                outBottomRowRect.top = outTopRowRect.bottom
+            }
+        }
+    }
+
+    var deskExplodeProgress: Float = 0f
+        set(value) {
+            field = value
+            taskViews.filterIsInstance<DesktopTaskView>().forEach { it.explodeProgress = field }
+        }
+
+    companion object {
+        class RecentsViewFloatProperty(
+            private val utilsProperty: KMutableProperty1<RecentsViewUtils, Float>
+        ) : FloatProperty<RecentsView<*, *>>(utilsProperty.name) {
+            override fun get(recentsView: RecentsView<*, *>): Float =
+                utilsProperty.get(recentsView.mUtils)
+
+            override fun setValue(recentsView: RecentsView<*, *>, value: Float) {
+                utilsProperty.set(recentsView.mUtils, value)
+            }
+        }
+
+        @JvmField
+        val DESK_EXPLODE_PROGRESS = RecentsViewFloatProperty(RecentsViewUtils::deskExplodeProgress)
+
+        val TEMP_RECT = Rect()
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
index f6393e4..4cf1eae 100644
--- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java
@@ -39,10 +39,11 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.quickstep.util.AnimUtils;
 import com.android.quickstep.util.SplitSelectStateController;
+import com.android.wm.shell.shared.TypefaceUtils;
+import com.android.wm.shell.shared.TypefaceUtils.FontFamily;
 
 /**
  * A rounded rectangular component containing a single TextView.
@@ -126,33 +127,32 @@
         TextView cancelTextView = findViewById(R.id.split_instructions_text_cancel);
         TextView instructionTextView = findViewById(R.id.split_instructions_text);
 
-        if (FeatureFlags.enableSplitContextually()) {
-            cancelTextView.setVisibility(VISIBLE);
-            cancelTextView.setOnClickListener((v) -> exitSplitSelection());
-            instructionTextView.setText(R.string.toast_contextual_split_select_app);
+        cancelTextView.setVisibility(VISIBLE);
+        cancelTextView.setOnClickListener((v) -> exitSplitSelection());
+        instructionTextView.setText(R.string.toast_contextual_split_select_app);
+        TypefaceUtils.setTypeface(instructionTextView, FontFamily.GSF_BODY_MEDIUM);
 
-            // 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);
+        // 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;
-                }
+            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));
-            });
-        }
+            setTouchDelegate(new TouchDelegate(r, cancelTextView));
+        });
 
         // Set accessibility title, will be announced by a11y tools.
         instructionTextView.setAccessibilityPaneTitle(instructionTextView.getText());
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 5de8d1c..ec6d1c4 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -17,27 +17,29 @@
 package com.android.quickstep.views
 
 import android.graphics.Bitmap
+import android.graphics.Matrix
+import android.util.Log
 import android.view.View
+import android.view.View.OnClickListener
 import com.android.launcher3.Flags.enableRefactorTaskThumbnail
 import com.android.launcher3.model.data.TaskViewItemInfo
 import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.launcher3.util.TransformingTouchDelegate
 import com.android.quickstep.TaskOverlayFactory
 import com.android.quickstep.ViewUtils.addAccessibleChildToList
-import com.android.quickstep.recents.di.RecentsDependencies
-import com.android.quickstep.recents.di.get
-import com.android.quickstep.recents.di.getScope
-import com.android.quickstep.recents.di.inject
-import com.android.quickstep.recents.viewmodel.TaskContainerViewModel
+import com.android.quickstep.recents.domain.usecase.ThumbnailPosition
+import com.android.quickstep.recents.ui.mapper.TaskUiStateMapper
+import com.android.quickstep.recents.ui.viewmodel.TaskData
+import com.android.quickstep.task.thumbnail.TaskContentView
 import com.android.quickstep.task.thumbnail.TaskThumbnailView
-import com.android.quickstep.task.viewmodel.TaskContainerData
-import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
 import com.android.systemui.shared.recents.model.Task
+import com.android.systemui.shared.recents.model.ThumbnailData
 
 /** Holder for all Task dependent information. */
 class TaskContainer(
     val taskView: TaskView,
     val task: Task,
+    val taskContentView: TaskContentView,
     val snapshotView: View,
     val iconView: TaskViewIcon,
     /**
@@ -54,43 +56,25 @@
     taskOverlayFactory: TaskOverlayFactory,
 ) {
     val overlay: TaskOverlayFactory.TaskOverlay<*> = taskOverlayFactory.createOverlay(this)
-    lateinit var taskContainerData: TaskContainerData
-
-    private val taskThumbnailViewModel: TaskThumbnailViewModel by
-        RecentsDependencies.inject(snapshotView)
-
-    // TODO(b/335649589): Ideally create and obtain this from DI.
-    private val taskContainerViewModel: TaskContainerViewModel by lazy {
-        TaskContainerViewModel(
-            sysUiStatusNavFlagsUseCase = RecentsDependencies.get(),
-            getThumbnailUseCase = RecentsDependencies.get(),
-            splashAlphaUseCase = RecentsDependencies.get(),
-        )
-    }
+    var thumbnailPosition: ThumbnailPosition? = null
+    private var overlayEnabledStatus = false
 
     init {
         if (enableRefactorTaskThumbnail()) {
             require(snapshotView is TaskThumbnailView)
-            taskContainerData = RecentsDependencies.get(this)
-            RecentsDependencies.getScope(snapshotView).apply {
-                val taskViewScope = RecentsDependencies.getScope(taskView)
-                linkTo(taskViewScope)
-
-                val taskContainerScope = RecentsDependencies.getScope(this@TaskContainer)
-                linkTo(taskContainerScope)
-            }
         } else {
             require(snapshotView is TaskThumbnailViewDeprecated)
         }
     }
 
-    val splitAnimationThumbnail: Bitmap?
+    internal var thumbnailData: ThumbnailData? = null
+        private set
+
+    val thumbnail: Bitmap?
+        /** If possible don't use this. It should be replaced as part of b/331753115. */
         get() =
-            if (enableRefactorTaskThumbnail()) {
-                taskContainerViewModel.getThumbnail(task.key.id)
-            } else {
-                thumbnailViewDeprecated.thumbnail
-            }
+            if (enableRefactorTaskThumbnail()) thumbnailData?.thumbnail
+            else thumbnailViewDeprecated.thumbnail
 
     val thumbnailView: TaskThumbnailView
         get() {
@@ -104,53 +88,73 @@
             return snapshotView as TaskThumbnailViewDeprecated
         }
 
+    var isThumbnailValid: Boolean = false
+        internal set
+
     val shouldShowSplashView: Boolean
         get() =
-            if (enableRefactorTaskThumbnail())
-                taskContainerViewModel.shouldShowThumbnailSplash(task.key.id)
+            if (enableRefactorTaskThumbnail()) taskView.shouldShowSplash()
             else thumbnailViewDeprecated.shouldShowSplashView()
 
-    val sysUiStatusNavFlags: Int
-        get() =
-            if (enableRefactorTaskThumbnail())
-                taskContainerViewModel.getSysUiStatusNavFlags(task.key.id)
-            else thumbnailViewDeprecated.sysUiStatusNavFlags
-
     /** Builds proto for logging */
     val itemInfo: TaskViewItemInfo
-        get() = TaskViewItemInfo(this)
+        get() = TaskViewItemInfo(taskView, this)
 
     fun bind() {
         digitalWellBeingToast?.bind(task, taskView, snapshotView, stagePosition)
-        if (enableRefactorTaskThumbnail()) {
-            bindThumbnailView()
-        } else {
+        if (!enableRefactorTaskThumbnail()) {
             thumbnailViewDeprecated.bind(task, overlay, taskView)
         }
-        overlay.init()
     }
 
     fun destroy() {
         digitalWellBeingToast?.destroy()
-        snapshotView.scaleX = 1f
-        snapshotView.scaleY = 1f
-        overlay.destroy()
+        taskContentView.scaleX = 1f
+        taskContentView.scaleY = 1f
+        overlay.reset()
         if (enableRefactorTaskThumbnail()) {
-            RecentsDependencies.getInstance().removeScope(snapshotView)
-            RecentsDependencies.getInstance().removeScope(this)
+            isThumbnailValid = false
+            thumbnailData = null
+            thumbnailView.onRecycle()
+        } else {
+            thumbnailViewDeprecated.setShowSplashForSplitSelection(false)
         }
     }
 
-    fun bindThumbnailView() {
-        taskThumbnailViewModel.bind(task.key.id)
-    }
-
     fun setOverlayEnabled(enabled: Boolean) {
         if (!enableRefactorTaskThumbnail()) {
             thumbnailViewDeprecated.setOverlayEnabled(enabled)
         }
     }
 
+    fun setOverlayEnabled(enabled: Boolean, thumbnailPosition: ThumbnailPosition?) {
+        if (enableRefactorTaskThumbnail()) {
+            if (overlayEnabledStatus != enabled || this.thumbnailPosition != thumbnailPosition) {
+                overlayEnabledStatus = enabled
+
+                refreshOverlay(thumbnailPosition)
+            }
+        }
+    }
+
+    fun refreshOverlay(thumbnailPosition: ThumbnailPosition?) {
+        this.thumbnailPosition = thumbnailPosition
+        when {
+            !overlayEnabledStatus -> overlay.reset()
+            thumbnailPosition == null -> {
+                Log.e(TAG, "Thumbnail position was null during overlay refresh", Exception())
+                overlay.reset()
+            }
+            else ->
+                overlay.initOverlay(
+                    task,
+                    thumbnailData?.thumbnail,
+                    thumbnailPosition.matrix,
+                    thumbnailPosition.isRotated,
+                )
+        }
+    }
+
     fun addChildForAccessibility(outChildren: ArrayList<View>) {
         addAccessibleChildToList(iconView.asView(), outChildren)
         addAccessibleChildToList(snapshotView, outChildren)
@@ -158,4 +162,62 @@
         digitalWellBeingToast?.let { addAccessibleChildToList(it, outChildren) }
         overlay.addChildForAccessibility(outChildren)
     }
+
+    fun setState(
+        state: TaskData?,
+        liveTile: Boolean,
+        hasHeader: Boolean,
+        clickCloseListener: OnClickListener?,
+    ) {
+        taskContentView.setState(
+            TaskUiStateMapper.toTaskHeaderState(state, hasHeader, clickCloseListener),
+            TaskUiStateMapper.toTaskThumbnailUiState(state, liveTile),
+            state?.taskId,
+        )
+        thumbnailData = if (state is TaskData.Data) state.thumbnailData else null
+        overlay.setThumbnailState(thumbnailData)
+    }
+
+    fun updateTintAmount(tintAmount: Float) {
+        thumbnailView.updateTintAmount(tintAmount)
+    }
+
+    /**
+     * Updates the progress of the menu opening animation.
+     *
+     * This function propagates the given `progress` value to the `thumbnailView` allowing the
+     * thumbnail view to animate its visual state in sync with the menu's opening/closing
+     * transition.
+     *
+     * @param progress The progress of the menu opening animation (from closed=0 to fully open=1)
+     */
+    fun updateMenuOpenProgress(progress: Float) {
+        thumbnailView.updateMenuOpenProgress(progress)
+    }
+
+    /**
+     * Updates the thumbnail splash progress for a given task.
+     *
+     * This function manages the visual feedback of a "splash" effect that can be displayed over a
+     * thumbnail image, typically during loading or updating. It calculates the alpha (transparency)
+     * of the splash based on the provided progress and then applies this alpha to the thumbnail
+     * view if it should be displayed.
+     *
+     * @param progress The progress of the operation, ranging from 0.0 to 1.0
+     */
+    fun updateThumbnailSplashProgress(progress: Float) {
+        if (enableRefactorTaskThumbnail()) {
+            thumbnailView.updateSplashAlpha(progress)
+        } else {
+            thumbnailViewDeprecated.setSplashAlpha(progress)
+        }
+    }
+
+    fun updateThumbnailMatrix(matrix: Matrix) {
+        thumbnailView.setImageMatrix(matrix)
+    }
+
+    companion object {
+        const val TAG = "TaskContainer"
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskHeaderView.kt b/quickstep/src/com/android/quickstep/views/TaskHeaderView.kt
new file mode 100644
index 0000000..0427402
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/TaskHeaderView.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.FrameLayout
+import android.widget.ImageButton
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.core.view.isGone
+import com.android.launcher3.R
+import com.android.quickstep.task.thumbnail.TaskHeaderUiState
+
+class TaskHeaderView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+    FrameLayout(context, attrs) {
+
+    private val headerTitleView: TextView by lazy { findViewById(R.id.header_app_title) }
+    private val headerIconView: ImageView by lazy { findViewById(R.id.header_app_icon) }
+    private val headerCloseButton: ImageButton by lazy { findViewById(R.id.header_close_button) }
+
+    fun setState(taskHeaderState: TaskHeaderUiState) {
+        when (taskHeaderState) {
+            is TaskHeaderUiState.ShowHeader -> {
+                setHeader(taskHeaderState.header)
+                isGone = false
+            }
+            TaskHeaderUiState.HideHeader -> isGone = true
+        }
+    }
+
+    private fun setHeader(header: TaskHeaderUiState.ThumbnailHeader) {
+        headerTitleView.text = header.title
+        headerIconView.setImageDrawable(header.icon)
+        headerCloseButton.setOnClickListener(header.clickCloseListener)
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
deleted file mode 100644
index 63bc509..0000000
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ /dev/null
@@ -1,430 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep.views;
-
-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;
-
-import android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Outline;
-import android.graphics.Rect;
-import android.graphics.drawable.GradientDrawable;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.RectShape;
-import android.util.AttributeSet;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.app.animation.Interpolators;
-import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.R;
-import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.popup.SystemShortcut;
-import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.TaskOverlayFactory;
-import com.android.quickstep.TaskUtils;
-import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
-import com.android.quickstep.util.TaskCornerRadius;
-
-/**
- * Contains options for a recent task when long-pressing its icon.
- */
-public class TaskMenuView extends AbstractFloatingView {
-
-    private static final Rect sTempRect = new Rect();
-
-    private static final int REVEAL_OPEN_DURATION = enableOverviewIconMenu() ? 417 : 150;
-    private static final int REVEAL_CLOSE_DURATION = enableOverviewIconMenu() ? 333 : 100;
-
-    private RecentsViewContainer mContainer;
-    private TextView mTaskName;
-    @Nullable
-    private AnimatorSet mOpenCloseAnimator;
-    @Nullable
-    private ValueAnimator mRevealAnimator;
-    @Nullable private Runnable mOnClosingStartCallback;
-    private TaskView mTaskView;
-    private TaskContainer mTaskContainer;
-    private LinearLayout mOptionLayout;
-    private float mMenuTranslationYBeforeOpen;
-    private float mMenuTranslationXBeforeOpen;
-
-    public TaskMenuView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    public TaskMenuView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-
-        mContainer = RecentsViewContainer.containerFromContext(context);
-        setClipToOutline(true);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mTaskName = findViewById(R.id.task_name);
-        mOptionLayout = findViewById(R.id.menu_option_layout);
-    }
-
-    @Override
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            BaseDragLayer dl = mContainer.getDragLayer();
-            if (!dl.isEventOverView(this, ev)) {
-                // TODO: log this once we have a new container type for it?
-                close(true);
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @Override
-    protected void handleClose(boolean animate) {
-        animateClose();
-    }
-
-    @Override
-    protected boolean isOfType(int type) {
-        return (type & TYPE_TASK_MENU) != 0;
-    }
-
-    @Override
-    public ViewOutlineProvider getOutlineProvider() {
-        return new ViewOutlineProvider() {
-            @Override
-            public void getOutline(View view, Outline outline) {
-                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(),
-                        TaskCornerRadius.get(view.getContext()));
-            }
-        };
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        if (!(enableOverviewIconMenu()
-                && ((RecentsView) mContainer.getOverviewPanel()).isOnGridBottomRow(mTaskView))) {
-            // TODO(b/326952853): Cap menu height for grid bottom row in a way that doesn't break
-            // additionalTranslationY.
-            int maxMenuHeight = calculateMaxHeight();
-            if (MeasureSpec.getSize(heightMeasureSpec) > maxMenuHeight) {
-                heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxMenuHeight, MeasureSpec.AT_MOST);
-            }
-        }
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-    }
-
-    public void onRotationChanged() {
-        if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) {
-            mOpenCloseAnimator.end();
-        }
-        if (mIsOpen) {
-            mOptionLayout.removeAllViews();
-            if (enableOverviewIconMenu() || !populateAndLayoutMenu()) {
-                close(false);
-            }
-        }
-    }
-
-    /**
-     * Show a task menu for the given taskContainer.
-     */
-    public static boolean showForTask(TaskContainer taskContainer,
-            @Nullable Runnable onClosingStartCallback) {
-        RecentsViewContainer container = RecentsViewContainer.containerFromContext(
-                taskContainer.getTaskView().getContext());
-        final TaskMenuView taskMenuView = (TaskMenuView) container.getLayoutInflater().inflate(
-                        R.layout.task_menu, container.getDragLayer(), false);
-        taskMenuView.setOnClosingStartCallback(onClosingStartCallback);
-        return taskMenuView.populateAndShowForTask(taskContainer);
-    }
-
-    /**
-     * Show a task menu for the given taskContainer.
-     */
-    public static boolean showForTask(TaskContainer taskContainer) {
-        return showForTask(taskContainer, null);
-    }
-
-    private boolean populateAndShowForTask(TaskContainer taskContainer) {
-        if (isAttachedToWindow()) {
-            return false;
-        }
-        mContainer.getDragLayer().addView(this);
-        mTaskView = taskContainer.getTaskView();
-        mTaskContainer = taskContainer;
-        if (!populateAndLayoutMenu()) {
-            return false;
-        }
-        post(this::animateOpen);
-        return true;
-    }
-
-    /** @return true if successfully able to populate task view menu, false otherwise */
-    private boolean populateAndLayoutMenu() {
-        addMenuOptions(mTaskContainer);
-        orientAroundTaskView(mTaskContainer);
-        return true;
-    }
-
-    private void addMenuOptions(TaskContainer taskContainer) {
-        if (enableOverviewIconMenu()) {
-            removeView(mTaskName);
-        } else {
-            mTaskName.setText(TaskUtils.getTitle(getContext(), taskContainer.getTask()));
-            mTaskName.setOnClickListener(v -> close(true));
-        }
-        TaskOverlayFactory.getEnabledShortcuts(mTaskView, taskContainer)
-                .forEach(this::addMenuOption);
-    }
-
-    private void addMenuOption(SystemShortcut menuOption) {
-        LinearLayout menuOptionView = (LinearLayout) mContainer.getLayoutInflater().inflate(
-                R.layout.task_view_menu_option, this, false);
-        if (enableOverviewIconMenu()) {
-            ((GradientDrawable) menuOptionView.getBackground()).setCornerRadius(0);
-        }
-        menuOption.setIconAndLabelFor(
-                menuOptionView.findViewById(R.id.icon), menuOptionView.findViewById(R.id.text));
-        LayoutParams lp = (LayoutParams) menuOptionView.getLayoutParams();
-        mTaskView.getPagedOrientationHandler().setLayoutParamsForTaskMenuOptionItem(lp,
-                menuOptionView, mContainer.getDeviceProfile());
-        // Set an onClick listener on each menu option. The onClick method is responsible for
-        // ending LiveTile mode on the thumbnail if needed.
-        menuOptionView.setOnClickListener(menuOption::onClick);
-        mOptionLayout.addView(menuOptionView);
-    }
-
-    private void orientAroundTaskView(TaskContainer taskContainer) {
-        RecentsView recentsView = mContainer.getOverviewPanel();
-        RecentsPagedOrientationHandler orientationHandler =
-                recentsView.getPagedOrientationHandler();
-        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
-
-        // Get Position
-        DeviceProfile deviceProfile = mContainer.getDeviceProfile();
-        mContainer.getDragLayer().getDescendantRectRelativeToSelf(
-                enableOverviewIconMenu()
-                        ? getIconView().findViewById(R.id.icon_view_menu_anchor)
-                        : taskContainer.getSnapshotView(),
-                sTempRect);
-        Rect insets = mContainer.getDragLayer().getInsets();
-        BaseDragLayer.LayoutParams params = (BaseDragLayer.LayoutParams) getLayoutParams();
-        params.width = orientationHandler.getTaskMenuWidth(
-                taskContainer.getSnapshotView(), deviceProfile,
-                taskContainer.getStagePosition());
-        // Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start
-        params.gravity = Gravity.LEFT;
-        setLayoutParams(params);
-        setScaleX(mTaskView.getScaleX());
-        setScaleY(mTaskView.getScaleY());
-
-        // Set divider spacing
-        ShapeDrawable divider = new ShapeDrawable(new RectShape());
-        divider.getPaint().setColor(getResources().getColor(android.R.color.transparent));
-        int dividerSpacing = (int) getResources().getDimension(R.dimen.task_menu_spacing);
-        mOptionLayout.setShowDividers(
-                enableOverviewIconMenu() ? SHOW_DIVIDER_NONE : SHOW_DIVIDER_MIDDLE);
-
-        orientationHandler.setTaskOptionsMenuLayoutOrientation(
-                deviceProfile, mOptionLayout, dividerSpacing, divider);
-        float thumbnailAlignedX = sTempRect.left - insets.left;
-        float thumbnailAlignedY = sTempRect.top - insets.top;
-
-        // Changing pivot to make computations easier
-        // NOTE: Changing the pivots means the rotated view gets rotated about the new pivots set,
-        // which would render the X and Y position set here incorrect
-        setPivotX(0);
-        setPivotY(0);
-        setRotation(orientationHandler.getDegreesRotated());
-
-        if (enableOverviewIconMenu()) {
-            setTranslationX(thumbnailAlignedX);
-            setTranslationY(thumbnailAlignedY);
-        } else {
-            // Margin that insets the menuView inside the taskView
-            float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin);
-            setTranslationX(orientationHandler.getTaskMenuX(thumbnailAlignedX,
-                    mTaskContainer.getSnapshotView(), deviceProfile, taskInsetMargin,
-                    getIconView()));
-            setTranslationY(orientationHandler.getTaskMenuY(
-                    thumbnailAlignedY, mTaskContainer.getSnapshotView(),
-                    mTaskContainer.getStagePosition(), this, taskInsetMargin,
-                    getIconView()));
-        }
-    }
-
-    private void animateOpen() {
-        mMenuTranslationYBeforeOpen = getTranslationY();
-        mMenuTranslationXBeforeOpen = getTranslationX();
-        animateOpenOrClosed(false);
-        mIsOpen = true;
-    }
-
-    private View getIconView() {
-        return mTaskContainer.getIconView().asView();
-    }
-
-    private void animateClose() {
-        animateOpenOrClosed(true);
-    }
-
-    private void animateOpenOrClosed(boolean closing) {
-        if (mOpenCloseAnimator != null && mOpenCloseAnimator.isRunning()) {
-            mOpenCloseAnimator.cancel();
-        }
-        mOpenCloseAnimator = new AnimatorSet();
-        // If we're opening, we just start from the beginning as a new `TaskMenuView` is created
-        // each time we do the open animation so there will never be a partial value here.
-        float revealAnimationStartProgress = 0f;
-        if (closing && mRevealAnimator != null) {
-            revealAnimationStartProgress = 1f - mRevealAnimator.getAnimatedFraction();
-        }
-        mRevealAnimator = createOpenCloseOutlineProvider()
-                .createRevealAnimator(this, closing, revealAnimationStartProgress);
-        mRevealAnimator.setInterpolator(enableOverviewIconMenu() ? Interpolators.EMPHASIZED
-                : Interpolators.DECELERATE);
-        AnimatorSet.Builder openCloseAnimatorBuilder = mOpenCloseAnimator.play(mRevealAnimator);
-        if (enableOverviewIconMenu()) {
-            IconAppChipView iconAppChip = (IconAppChipView) mTaskContainer.getIconView().asView();
-
-            float additionalTranslationY = 0;
-            if (((RecentsView) mContainer.getOverviewPanel()).isOnGridBottomRow(mTaskView)) {
-                // Animate menu up for enough room to display full menu when task on bottom row.
-                float menuBottom = getHeight() + mMenuTranslationYBeforeOpen;
-                float taskBottom = mTaskView.getHeight() + mTaskView.getPersistentTranslationY();
-                float taskbarTop = mContainer.getDeviceProfile().heightPx
-                        - mContainer.getDeviceProfile().getOverviewActionsClaimedSpaceBelow();
-                float midpoint = (taskBottom + taskbarTop) / 2f;
-                additionalTranslationY = -Math.max(menuBottom - midpoint, 0);
-            }
-            ObjectAnimator translationYAnim = ObjectAnimator.ofFloat(this, TRANSLATION_Y,
-                    closing ? mMenuTranslationYBeforeOpen
-                            : mMenuTranslationYBeforeOpen + additionalTranslationY);
-            translationYAnim.setInterpolator(EMPHASIZED);
-            openCloseAnimatorBuilder.with(translationYAnim);
-
-            ObjectAnimator menuTranslationYAnim = ObjectAnimator.ofFloat(
-                    iconAppChip.getMenuTranslationY(),
-                    MULTI_PROPERTY_VALUE, closing ? 0 : additionalTranslationY);
-            menuTranslationYAnim.setInterpolator(EMPHASIZED);
-            openCloseAnimatorBuilder.with(menuTranslationYAnim);
-
-            float additionalTranslationX = 0;
-            if (mContainer.getDeviceProfile().isLandscape
-                    && mTaskContainer.getStagePosition() == STAGE_POSITION_BOTTOM_OR_RIGHT) {
-                // Animate menu and icon when split task would display off the side of the screen.
-                additionalTranslationX = Math.max(
-                        getTranslationX() + getWidth() - (mContainer.getDeviceProfile().widthPx
-                                - getResources().getDimensionPixelSize(
-                                R.dimen.task_menu_edge_padding) * 2), 0);
-            }
-
-            ObjectAnimator translationXAnim = ObjectAnimator.ofFloat(this, TRANSLATION_X,
-                    closing ? mMenuTranslationXBeforeOpen
-                            : mMenuTranslationXBeforeOpen - additionalTranslationX);
-            translationXAnim.setInterpolator(EMPHASIZED);
-            openCloseAnimatorBuilder.with(translationXAnim);
-
-            ObjectAnimator menuTranslationXAnim = ObjectAnimator.ofFloat(
-                    iconAppChip.getMenuTranslationX(),
-                    MULTI_PROPERTY_VALUE, closing ? 0 : -additionalTranslationX);
-            menuTranslationXAnim.setInterpolator(EMPHASIZED);
-            openCloseAnimatorBuilder.with(menuTranslationXAnim);
-        }
-        openCloseAnimatorBuilder.with(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);
-            });
-        } else {
-            openCloseAnimatorBuilder.with(ObjectAnimator.ofFloat(
-                    mTaskContainer.getThumbnailViewDeprecated(), DIM_ALPHA,
-                    closing ? 0 : TaskView.MAX_PAGE_SCRIM_ALPHA));
-        }
-        mOpenCloseAnimator.addListener(new AnimationSuccessListener() {
-            @Override
-            public void onAnimationStart(Animator animation) {
-                setVisibility(VISIBLE);
-                if (closing && mOnClosingStartCallback != null) {
-                    mOnClosingStartCallback.run();
-                }
-            }
-
-            @Override
-            public void onAnimationSuccess(Animator animator) {
-                if (closing) {
-                    closeComplete();
-                }
-            }
-        });
-        mOpenCloseAnimator.setDuration(closing ? REVEAL_CLOSE_DURATION: REVEAL_OPEN_DURATION);
-        mOpenCloseAnimator.start();
-    }
-
-    private void closeComplete() {
-        mIsOpen = false;
-        mContainer.getDragLayer().removeView(this);
-        mRevealAnimator = null;
-    }
-
-    private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
-        float radius = TaskCornerRadius.get(mContext);
-        Rect fromRect = new Rect(
-                enableOverviewIconMenu() && isLayoutRtl() ? getWidth() : 0,
-                0,
-                enableOverviewIconMenu() && !isLayoutRtl() ? 0 : getWidth(),
-                0);
-        Rect toRect = new Rect(0, 0, getWidth(), getHeight());
-        return new RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect);
-    }
-
-    /**
-     * Calculates max height based on how much space we have available.
-     * If not enough space then the view will scroll. The maximum menu size will sit inside the task
-     * with a margin on the top and bottom.
-     */
-    private int calculateMaxHeight() {
-        float taskInsetMargin = getResources().getDimension(R.dimen.task_card_margin);
-        return mTaskView.getPagedOrientationHandler().getTaskMenuHeight(taskInsetMargin,
-                mContainer.getDeviceProfile(), getTranslationX(), getTranslationY());
-    }
-
-    private void setOnClosingStartCallback(Runnable onClosingStartCallback) {
-        mOnClosingStartCallback = onClosingStartCallback;
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.kt b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
new file mode 100644
index 0000000..7c762f4
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.kt
@@ -0,0 +1,481 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.views
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.animation.ValueAnimator
+import android.content.Context
+import android.graphics.Outline
+import android.graphics.Rect
+import android.graphics.drawable.GradientDrawable
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RectShape
+import android.util.AttributeSet
+import android.view.Gravity
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewOutlineProvider
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.core.content.res.ResourcesCompat
+import com.android.app.animation.Interpolators
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.Flags.enableOverviewIconMenu
+import com.android.launcher3.Flags.enableRefactorTaskThumbnail
+import com.android.launcher3.R
+import com.android.launcher3.anim.AnimationSuccessListener
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider
+import com.android.launcher3.popup.SystemShortcut
+import com.android.launcher3.util.MultiPropertyFactory
+import com.android.launcher3.util.SplitConfigurationOptions
+import com.android.launcher3.views.BaseDragLayer
+import com.android.quickstep.TaskOverlayFactory
+import com.android.quickstep.TaskUtils
+import com.android.quickstep.util.TaskCornerRadius
+import java.util.function.Consumer
+import kotlin.math.max
+
+/** Contains options for a recent task when long-pressing its icon. */
+class TaskMenuView
+@JvmOverloads
+constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int = 0) :
+    AbstractFloatingView(context, attrs, defStyleAttr) {
+    private val recentsViewContainer: RecentsViewContainer =
+        RecentsViewContainer.containerFromContext(context)
+    private val tempRect = Rect()
+    private val taskName: TextView by lazy { findViewById(R.id.task_name) }
+    private val optionLayout: LinearLayout by lazy { findViewById(R.id.menu_option_layout) }
+    private var openCloseAnimator: AnimatorSet? = null
+    private var revealAnimator: ValueAnimator? = null
+    private var onClosingStartCallback: Runnable? = null
+    private lateinit var taskView: TaskView
+    private lateinit var taskContainer: TaskContainer
+    private var menuTranslationXBeforeOpen = 0f
+    private var menuTranslationYBeforeOpen = 0f
+
+    // Spaced claimed below Overview (taskbar and insets)
+    private val taskbarTop by lazy {
+        recentsViewContainer.deviceProfile.heightPx -
+            recentsViewContainer.deviceProfile.overviewActionsClaimedSpaceBelow
+    }
+    private val minMenuTop by lazy { taskContainer.iconView.height.toFloat() }
+    // TODO(b/401476868): Replace overviewRowSpacing with correct margin to the taskbarTop.
+    private val maxMenuBottom by lazy {
+        (taskbarTop - recentsViewContainer.deviceProfile.overviewRowSpacing).toFloat()
+    }
+
+    init {
+        clipToOutline = true
+    }
+
+    override fun onControllerInterceptTouchEvent(ev: MotionEvent): Boolean {
+        if (ev.action == MotionEvent.ACTION_DOWN) {
+            if (!recentsViewContainer.dragLayer.isEventOverView(this, ev)) {
+                // TODO: log this once we have a new container type for it?
+                animateOpenOrClosed(true)
+                return true
+            }
+        }
+        return false
+    }
+
+    override fun handleClose(animate: Boolean) {
+        animateOpenOrClosed(true, animated = false)
+    }
+
+    override fun isOfType(type: Int): Boolean = (type and TYPE_TASK_MENU) != 0
+
+    override fun getOutlineProvider(): ViewOutlineProvider =
+        object : ViewOutlineProvider() {
+            override fun getOutline(view: View, outline: Outline) {
+                outline.setRoundRect(
+                    0,
+                    0,
+                    view.width,
+                    view.height,
+                    TaskCornerRadius.get(view.context),
+                )
+            }
+        }
+
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        var heightMeasure = heightMeasureSpec
+        val maxMenuHeight = calculateMaxHeight()
+        if (MeasureSpec.getSize(heightMeasure) > maxMenuHeight) {
+            heightMeasure = MeasureSpec.makeMeasureSpec(maxMenuHeight, MeasureSpec.AT_MOST)
+        }
+        super.onMeasure(widthMeasureSpec, heightMeasure)
+    }
+
+    fun onRotationChanged() {
+        openCloseAnimator?.let { if (it.isRunning) it.end() }
+        if (mIsOpen) {
+            optionLayout.removeAllViews()
+            if (enableOverviewIconMenu() || !populateAndLayoutMenu()) {
+                close(false)
+            }
+        }
+    }
+
+    private fun populateAndShowForTask(taskContainer: TaskContainer): Boolean {
+        if (isAttachedToWindow) return false
+        recentsViewContainer.dragLayer.addView(this)
+        taskView = taskContainer.taskView
+        this.taskContainer = taskContainer
+        if (!populateAndLayoutMenu()) return false
+        post { this.animateOpen() }
+        return true
+    }
+
+    /** @return true if successfully able to populate task view menu, false otherwise */
+    private fun populateAndLayoutMenu(): Boolean {
+        addMenuOptions(taskContainer)
+        orientAroundTaskView(taskContainer)
+        return true
+    }
+
+    private fun addMenuOptions(taskContainer: TaskContainer) {
+        if (enableOverviewIconMenu()) {
+            removeView(taskName)
+        } else {
+            taskName.text = TaskUtils.getTitle(context, taskContainer.task)
+            taskName.setOnClickListener { close(true) }
+        }
+        TaskOverlayFactory.getEnabledShortcuts(taskView, taskContainer)
+            .forEach(Consumer { menuOption: SystemShortcut<*> -> this.addMenuOption(menuOption) })
+    }
+
+    private fun addMenuOption(menuOption: SystemShortcut<*>) {
+        val menuOptionView =
+            recentsViewContainer.layoutInflater.inflate(R.layout.task_view_menu_option, this, false)
+                as LinearLayout
+        if (enableOverviewIconMenu()) {
+            (menuOptionView.background as GradientDrawable).cornerRadius = 0f
+        }
+        menuOption.setIconAndLabelFor(
+            menuOptionView.findViewById(R.id.icon),
+            menuOptionView.findViewById(R.id.text),
+        )
+        val lp = menuOptionView.layoutParams as LayoutParams
+        taskView.pagedOrientationHandler.setLayoutParamsForTaskMenuOptionItem(
+            lp,
+            menuOptionView,
+            recentsViewContainer.deviceProfile,
+        )
+        // Set an onClick listener on each menu option. The onClick method is responsible for
+        // ending LiveTile mode on the thumbnail if needed.
+        menuOptionView.setOnClickListener { v: View? -> menuOption.onClick(v) }
+        optionLayout.addView(menuOptionView)
+    }
+
+    private fun orientAroundTaskView(taskContainer: TaskContainer) {
+        val recentsView = recentsViewContainer.getOverviewPanel<RecentsView<*, *>>()
+        val orientationHandler = recentsView.pagedOrientationHandler
+        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
+
+        // Get Position
+        val deviceProfile = recentsViewContainer.deviceProfile
+        recentsViewContainer.dragLayer.getDescendantRectRelativeToSelf(
+            if (enableOverviewIconMenu()) iconView.findViewById(R.id.icon_view_menu_anchor)
+            else taskContainer.snapshotView,
+            tempRect,
+        )
+        val insets = recentsViewContainer.dragLayer.getInsets()
+        val params = layoutParams as BaseDragLayer.LayoutParams
+        params.width =
+            orientationHandler.getTaskMenuWidth(
+                taskContainer.snapshotView,
+                deviceProfile,
+                taskContainer.stagePosition,
+            )
+        // Gravity set to Left instead of Start as sTempRect.left measures Left distance not Start
+        params.gravity = Gravity.LEFT
+        layoutParams = params
+        scaleX = taskView.scaleX
+        scaleY = taskView.scaleY
+
+        // Set divider spacing
+        val divider = ShapeDrawable(RectShape())
+        divider.paint.color = resources.getColor(android.R.color.transparent)
+        val dividerSpacing = resources.getDimension(R.dimen.task_menu_spacing).toInt()
+        optionLayout.showDividers =
+            if (enableOverviewIconMenu()) SHOW_DIVIDER_NONE else SHOW_DIVIDER_MIDDLE
+
+        optionLayout.background =
+            if (enableOverviewIconMenu()) {
+                ResourcesCompat.getDrawable(resources, R.drawable.app_chip_menu_bg, context.theme)
+            } else {
+                null
+            }
+
+        orientationHandler.setTaskOptionsMenuLayoutOrientation(
+            deviceProfile,
+            optionLayout,
+            dividerSpacing,
+            divider,
+        )
+        val thumbnailAlignedX = (tempRect.left - insets.left).toFloat()
+        val thumbnailAlignedY = (tempRect.top - insets.top).toFloat()
+
+        // Changing pivot to make computations easier
+        // NOTE: Changing the pivots means the rotated view gets rotated about the new pivots set,
+        // which would render the X and Y position set here incorrect
+        pivotX = 0f
+        pivotY = 0f
+        rotation = orientationHandler.degreesRotated
+
+        if (enableOverviewIconMenu()) {
+            translationX = thumbnailAlignedX
+            translationY = thumbnailAlignedY
+        } else {
+            // Margin that insets the menuView inside the taskView
+            val taskInsetMargin = resources.getDimension(R.dimen.task_card_margin)
+            translationX =
+                orientationHandler.getTaskMenuX(
+                    thumbnailAlignedX,
+                    this.taskContainer.snapshotView,
+                    deviceProfile,
+                    taskInsetMargin,
+                    iconView,
+                )
+            translationY =
+                orientationHandler.getTaskMenuY(
+                    thumbnailAlignedY,
+                    this.taskContainer.snapshotView,
+                    this.taskContainer.stagePosition,
+                    this,
+                    taskInsetMargin,
+                    iconView,
+                )
+        }
+    }
+
+    private fun animateOpen() {
+        menuTranslationYBeforeOpen = translationY
+        menuTranslationXBeforeOpen = translationX
+        animateOpenOrClosed(false)
+        mIsOpen = true
+    }
+
+    private val iconView: View
+        get() = taskContainer.iconView.asView()
+
+    private fun animateOpenOrClosed(closing: Boolean, animated: Boolean = true) {
+        openCloseAnimator?.let { if (it.isRunning) it.cancel() }
+        openCloseAnimator = AnimatorSet()
+        // If we're opening, we just start from the beginning as a new `TaskMenuView` is created
+        // each time we do the open animation so there will never be a partial value here.
+        var revealAnimationStartProgress = 0f
+        if (closing && revealAnimator != null) {
+            revealAnimationStartProgress = 1f - revealAnimator!!.animatedFraction
+        }
+        revealAnimator =
+            createOpenCloseOutlineProvider()
+                .createRevealAnimator(this, closing, revealAnimationStartProgress)
+        revealAnimator!!.interpolator =
+            if (enableOverviewIconMenu()) Interpolators.EMPHASIZED else Interpolators.DECELERATE
+        val openCloseAnimatorBuilder = openCloseAnimator!!.play(revealAnimator)
+        if (enableOverviewIconMenu()) {
+            animateOpenOrCloseAppChip(closing, openCloseAnimatorBuilder)
+        }
+        openCloseAnimatorBuilder.with(
+            ObjectAnimator.ofFloat(this, ALPHA, (if (closing) 0 else 1).toFloat())
+        )
+        if (enableRefactorTaskThumbnail()) {
+            revealAnimator?.addUpdateListener { animation: ValueAnimator ->
+                val animatedFraction = animation.animatedFraction
+                val openProgress = if (closing) (1 - animatedFraction) else animatedFraction
+                taskContainer.updateMenuOpenProgress(openProgress)
+            }
+        } else {
+            openCloseAnimatorBuilder.with(
+                ObjectAnimator.ofFloat(
+                    taskContainer.thumbnailViewDeprecated,
+                    TaskThumbnailViewDeprecated.DIM_ALPHA,
+                    if (closing) 0f else TaskView.MAX_PAGE_SCRIM_ALPHA,
+                )
+            )
+        }
+        openCloseAnimator!!.addListener(
+            object : AnimationSuccessListener() {
+                override fun onAnimationStart(animation: Animator) {
+                    visibility = VISIBLE
+                    if (closing) onClosingStartCallback?.run()
+                }
+
+                override fun onAnimationSuccess(animator: Animator) {
+                    if (closing) closeComplete()
+                }
+            }
+        )
+        val animationDuration =
+            when {
+                animated && closing -> REVEAL_CLOSE_DURATION
+                animated && !closing -> REVEAL_OPEN_DURATION
+                else -> 0L
+            }
+        openCloseAnimator!!.setDuration(animationDuration)
+        openCloseAnimator!!.start()
+    }
+
+    private fun TaskView.isOnGridBottomRow(): Boolean =
+        (recentsViewContainer.getOverviewPanel<View>() as RecentsView<*, *>).isOnGridBottomRow(this)
+
+    private fun closeComplete() {
+        mIsOpen = false
+        recentsViewContainer.dragLayer.removeView(this)
+        revealAnimator = null
+    }
+
+    private fun createOpenCloseOutlineProvider(): RoundedRectRevealOutlineProvider {
+        val radius = TaskCornerRadius.get(mContext)
+        val fromRect =
+            Rect(
+                if (enableOverviewIconMenu() && isLayoutRtl) width else 0,
+                0,
+                if (enableOverviewIconMenu() && !isLayoutRtl) 0 else width,
+                0,
+            )
+        val toRect = Rect(0, 0, width, height)
+        return RoundedRectRevealOutlineProvider(radius, radius, fromRect, toRect)
+    }
+
+    /**
+     * Calculates max height based on how much space we have available. If not enough space then the
+     * view will scroll. The maximum menu size will sit inside the task with a margin on the top and
+     * bottom.
+     */
+    private fun calculateMaxHeight(): Int =
+        taskView.pagedOrientationHandler.getTaskMenuHeight(
+            taskInsetMargin = resources.getDimension(R.dimen.task_card_margin), // taskInsetMargin
+            deviceProfile = recentsViewContainer.deviceProfile,
+            taskMenuX = translationX,
+            taskMenuY =
+                when {
+                    !enableOverviewIconMenu() -> translationY
+                    // Bottom menu can translate up to show more options. So we use the min
+                    // translation allowed to calculate its max height.
+                    taskView.isOnGridBottomRow() -> minMenuTop
+                    else -> menuTranslationYBeforeOpen
+                },
+        )
+
+    private fun setOnClosingStartCallback(onClosingStartCallback: Runnable?) {
+        this.onClosingStartCallback = onClosingStartCallback
+    }
+
+    private fun animateOpenOrCloseAppChip(closing: Boolean, animatorBuilder: AnimatorSet.Builder) {
+        val iconAppChip = taskContainer.iconView.asView() as IconAppChipView
+
+        // Animate menu up for enough room to display full menu when task on bottom row.
+        var additionalTranslationY = 0f
+        if (taskView.isOnGridBottomRow()) {
+            val currentMenuBottom: Float = menuTranslationYBeforeOpen + height
+            additionalTranslationY =
+                if (currentMenuBottom < maxMenuBottom) 0f
+                // Translate menu up for enough room to display full menu when task on bottom row.
+                else maxMenuBottom - currentMenuBottom
+
+            val currentMenuTop = menuTranslationYBeforeOpen + additionalTranslationY
+            // If it translate above the min accepted, it translates to the top of the screen
+            if (currentMenuTop < minMenuTop) {
+                // It subtracts the menuTranslation to make it 0 (top of the screen) + chip size.
+                additionalTranslationY = -menuTranslationYBeforeOpen + minMenuTop
+            }
+        }
+
+        val translationYAnim =
+            ObjectAnimator.ofFloat(
+                this,
+                TRANSLATION_Y,
+                if (closing) menuTranslationYBeforeOpen
+                else menuTranslationYBeforeOpen + additionalTranslationY,
+            )
+        translationYAnim.interpolator = Interpolators.EMPHASIZED
+        animatorBuilder.with(translationYAnim)
+
+        val menuTranslationYAnim: ObjectAnimator =
+            ObjectAnimator.ofFloat(
+                iconAppChip.getMenuTranslationY(),
+                MultiPropertyFactory.MULTI_PROPERTY_VALUE,
+                if (closing) 0f else additionalTranslationY,
+            )
+        menuTranslationYAnim.interpolator = Interpolators.EMPHASIZED
+        animatorBuilder.with(menuTranslationYAnim)
+
+        var additionalTranslationX = 0f
+        if (
+            recentsViewContainer.deviceProfile.isLandscape &&
+                taskContainer.stagePosition ==
+                    SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
+        ) {
+            // Animate menu and icon when split task would display off the side of the screen.
+            additionalTranslationX =
+                max(
+                        (translationX + width -
+                                (recentsViewContainer.deviceProfile.widthPx -
+                                    resources.getDimensionPixelSize(
+                                        R.dimen.task_menu_edge_padding
+                                    ) * 2))
+                            .toDouble(),
+                        0.0,
+                    )
+                    .toFloat()
+        }
+
+        val translationXAnim =
+            ObjectAnimator.ofFloat(
+                this,
+                TRANSLATION_X,
+                if (closing) menuTranslationXBeforeOpen
+                else menuTranslationXBeforeOpen - additionalTranslationX,
+            )
+        translationXAnim.interpolator = Interpolators.EMPHASIZED
+        animatorBuilder.with(translationXAnim)
+
+        val menuTranslationXAnim: ObjectAnimator =
+            ObjectAnimator.ofFloat(
+                iconAppChip.getMenuTranslationX(),
+                MultiPropertyFactory.MULTI_PROPERTY_VALUE,
+                if (closing) 0f else -additionalTranslationX,
+            )
+        menuTranslationXAnim.interpolator = Interpolators.EMPHASIZED
+        animatorBuilder.with(menuTranslationXAnim)
+    }
+
+    companion object {
+        private val REVEAL_OPEN_DURATION = if (enableOverviewIconMenu()) 417L else 150L
+        private val REVEAL_CLOSE_DURATION = if (enableOverviewIconMenu()) 333L else 100L
+
+        /** Show a task menu for the given taskContainer. */
+        /** Show a task menu for the given taskContainer. */
+        @JvmOverloads
+        fun showForTask(
+            taskContainer: TaskContainer,
+            onClosingStartCallback: Runnable? = null,
+        ): Boolean {
+            val container: RecentsViewContainer =
+                RecentsViewContainer.containerFromContext(taskContainer.taskView.context)
+            val taskMenuView =
+                container.layoutInflater.inflate(R.layout.task_menu, container.dragLayer, false)
+                    as TaskMenuView
+            taskMenuView.setOnClosingStartCallback(onClosingStartCallback)
+            return taskMenuView.populateAndShowForTask(taskContainer)
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
index 9f2bb9a..74d76e6 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewDeprecated.java
@@ -46,7 +46,6 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.SystemUiController.SystemUiControllerFlags;
 import com.android.launcher3.util.ViewPool;
@@ -66,8 +65,6 @@
  */
 @Deprecated
 public class TaskThumbnailViewDeprecated extends View implements ViewPool.Reusable {
-    private static final MainThreadInitializedObject<FullscreenDrawParams> TEMP_PARAMS =
-            new MainThreadInitializedObject<>(FullscreenDrawParams::new);
 
     public static final Property<TaskThumbnailViewDeprecated, Float> DIM_ALPHA =
             new FloatProperty<TaskThumbnailViewDeprecated>("dimAlpha") {
@@ -145,8 +142,7 @@
         mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
         mContainer = RecentsViewContainer.containerFromContext(context);
         // Initialize with placeholder value. It is overridden later by TaskView
-        mFullscreenParams = TEMP_PARAMS.get(context);
-
+        mFullscreenParams = new FullscreenDrawParams(context, __ -> 0f, __ -> 0f);
         mDimColor = RecentsView.getForegroundScrimDimColor(context);
         mDimmingPaintAfterClearing.setColor(mDimColor);
     }
@@ -416,7 +412,12 @@
                 thumbnailDataAspect, MAX_PCT_BEFORE_ASPECT_RATIOS_CONSIDERED_DIFFERENT);
     }
 
-    private boolean isThumbnailRotationDifferentFromTask() {
+    /**
+     * Returns whether or not the current thumbnail is a different orientation to the task.
+     * <p>
+     * Used to disable modal state when screenshot doesn't match the device orientation.
+     */
+    public boolean isThumbnailRotationDifferentFromTask() {
         RecentsView recents = mTaskView.getRecentsView();
         if (recents == null || mThumbnailData == null) {
             return false;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index d207edf..55432b8 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -31,14 +31,11 @@
 import android.util.FloatProperty
 import android.util.Log
 import android.view.Display
-import android.view.LayoutInflater
 import android.view.MotionEvent
 import android.view.View
-import android.view.View.OnClickListener
 import android.view.ViewGroup
 import android.view.ViewStub
 import android.view.accessibility.AccessibilityNodeInfo
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
 import android.widget.FrameLayout
 import android.widget.Toast
 import androidx.annotation.IntDef
@@ -46,31 +43,34 @@
 import androidx.core.view.updateLayoutParams
 import com.android.app.animation.Interpolators
 import com.android.launcher3.Flags.enableCursorHoverStates
+import com.android.launcher3.Flags.enableDesktopExplodedView
 import com.android.launcher3.Flags.enableGridOnlyOverview
 import com.android.launcher3.Flags.enableHoverOfChildElementsInTaskview
 import com.android.launcher3.Flags.enableLargeDesktopWindowingTile
 import com.android.launcher3.Flags.enableOverviewIconMenu
 import com.android.launcher3.Flags.enableRefactorTaskThumbnail
+import com.android.launcher3.Flags.enableSeparateExternalDisplayTasks
 import com.android.launcher3.R
 import com.android.launcher3.Utilities
 import com.android.launcher3.anim.AnimatedFloat
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent
 import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.TaskViewItemInfo
 import com.android.launcher3.testing.TestLogging
 import com.android.launcher3.testing.shared.TestProtocol
 import com.android.launcher3.util.CancellableTask
 import com.android.launcher3.util.Executors
+import com.android.launcher3.util.KFloatProperty
+import com.android.launcher3.util.MultiPropertyDelegate
 import com.android.launcher3.util.MultiPropertyFactory
-import com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE
 import com.android.launcher3.util.MultiValueAlpha
 import com.android.launcher3.util.RunnableList
-import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED
-import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition
 import com.android.launcher3.util.TraceHelper
 import com.android.launcher3.util.TransformingTouchDelegate
 import com.android.launcher3.util.ViewPool
+import com.android.launcher3.util.coroutines.DispatcherProvider
 import com.android.launcher3.util.rects.set
 import com.android.quickstep.FullscreenDrawParams
 import com.android.quickstep.RecentsModel
@@ -78,7 +78,14 @@
 import com.android.quickstep.TaskOverlayFactory
 import com.android.quickstep.TaskViewUtils
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler
-import com.android.quickstep.task.thumbnail.TaskThumbnailView
+import com.android.quickstep.recents.di.RecentsDependencies
+import com.android.quickstep.recents.di.get
+import com.android.quickstep.recents.di.inject
+import com.android.quickstep.recents.domain.usecase.ThumbnailPosition
+import com.android.quickstep.recents.ui.viewmodel.TaskData
+import com.android.quickstep.recents.ui.viewmodel.TaskTileUiState
+import com.android.quickstep.recents.ui.viewmodel.TaskViewModel
+import com.android.quickstep.task.thumbnail.TaskContentView
 import com.android.quickstep.util.ActiveGestureErrorDetector
 import com.android.quickstep.util.ActiveGestureLog
 import com.android.quickstep.util.BorderAnimator
@@ -86,10 +93,17 @@
 import com.android.quickstep.util.RecentsOrientedState
 import com.android.quickstep.util.TaskCornerRadius
 import com.android.quickstep.util.TaskRemovedDuringLaunchListener
+import com.android.quickstep.util.displayId
+import com.android.quickstep.util.isExternalDisplay
 import com.android.quickstep.views.RecentsView.UNBOUND_TASK_VIEW_ID
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
 import com.android.systemui.shared.system.ActivityManagerWrapper
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
 
 /** A task in the Recents view. */
 open class TaskView
@@ -127,13 +141,21 @@
         /** Returns whether the task is part of overview grid and not being focused. */
         get() = container.deviceProfile.isTablet && !isLargeTile
 
+    // TODO: b/400532675 - This will not work for empty desks until b/400532675 is fixed.
     val isRunningTask: Boolean
         get() = this === recentsView?.runningTaskView
 
+    val displayId: Int
+        get() = taskContainers.firstOrNull()?.task.displayId
+
+    val isExternalDisplay: Boolean
+        get() = displayId.isExternalDisplay
+
     val isLargeTile: Boolean
         get() =
             this == recentsView?.focusedTaskView ||
-                (enableLargeDesktopWindowingTile() && type == TaskViewType.DESKTOP)
+                (enableLargeDesktopWindowingTile() && type == TaskViewType.DESKTOP) ||
+                (enableSeparateExternalDisplayTasks() && isExternalDisplay)
 
     val recentsView: RecentsView<*, *>?
         get() = parent as? RecentsView<*, *>
@@ -141,14 +163,24 @@
     val pagedOrientationHandler: RecentsPagedOrientationHandler
         get() = orientedState.orientationHandler
 
-    @get:Deprecated("Use [taskContainers] instead.")
-    val firstTask: Task
-        /** Returns the first task bound to this TaskView. */
-        get() = taskContainers[0].task
+    val firstTaskContainer: TaskContainer?
+        get() = taskContainers.firstOrNull()
 
-    @get:Deprecated("Use [taskContainers] instead.")
-    val firstItemInfo: ItemInfo
-        get() = taskContainers[0].itemInfo
+    val firstTask: Task?
+        /** Returns the first task bound to this TaskView. */
+        get() = firstTaskContainer?.task
+
+    val firstItemInfo: ItemInfo?
+        get() = firstTaskContainer?.itemInfo
+
+    /**
+     * A [TaskViewItemInfo] of this TaskView. The [firstTaskContainer] will be used to get some
+     * specific information like user, title etc of the Task. However, these task specific
+     * information will be skipped if the TaskView has no [taskContainers]. Note, please use
+     * [TaskContainer.itemInfo] for [TaskViewItemInfo] on a specific [TaskContainer].
+     */
+    val itemInfo: TaskViewItemInfo
+        get() = TaskViewItemInfo(this, firstTaskContainer)
 
     protected val container: RecentsViewContainer =
         RecentsViewContainer.containerFromContext(context)
@@ -167,12 +199,9 @@
          * Returns addition of translationX that is persistent (e.g. fullscreen and grid), and does
          * not change according to a temporary state (e.g. task offset).
          */
-        get() =
-            (getNonGridTrans(nonGridTranslationX) +
-                getGridTrans(this.gridTranslationX) +
-                getNonGridTrans(nonGridPivotTranslationX))
+        get() = (getNonGridTrans(nonGridTranslationX) + getGridTrans(this.gridTranslationX))
 
-    protected val persistentTranslationY: Float
+    val persistentTranslationY: Float
         /**
          * Returns addition of translationY that is persistent (e.g. fullscreen and grid), and does
          * not change according to a temporary state (e.g. task offset).
@@ -193,11 +222,11 @@
                 SPLIT_SELECT_TRANSLATION_Y,
             )
 
-    protected val primaryDismissTranslationProperty: FloatProperty<TaskView>
+    val primaryDismissTranslationProperty: FloatProperty<TaskView>
         get() =
             pagedOrientationHandler.getPrimaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y)
 
-    protected val secondaryDismissTranslationProperty: FloatProperty<TaskView>
+    val secondaryDismissTranslationProperty: FloatProperty<TaskView>
         get() =
             pagedOrientationHandler.getSecondaryValue(DISMISS_TRANSLATION_X, DISMISS_TRANSLATION_Y)
 
@@ -223,8 +252,43 @@
             )
 
     private val tempCoordinates = FloatArray(2)
-    private val focusBorderAnimator: BorderAnimator?
-    private val hoverBorderAnimator: BorderAnimator?
+    private val focusBorderAnimator: BorderAnimator? =
+        focusBorderAnimator
+            ?: createSimpleBorderAnimator(
+                TaskCornerRadius.get(context).toInt(),
+                context.resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_border_width),
+                this::getThumbnailBounds,
+                this,
+                context
+                    .obtainStyledAttributes(attrs, R.styleable.TaskView, defStyleAttr, defStyleRes)
+                    .getColor(
+                        R.styleable.TaskView_focusBorderColor,
+                        BorderAnimator.DEFAULT_BORDER_COLOR,
+                    ),
+            )
+
+    private val hoverBorderAnimator: BorderAnimator? =
+        hoverBorderAnimator
+            ?: if (enableCursorHoverStates())
+                createSimpleBorderAnimator(
+                    TaskCornerRadius.get(context).toInt(),
+                    context.resources.getDimensionPixelSize(R.dimen.task_hover_border_width),
+                    this::getThumbnailBounds,
+                    this,
+                    context
+                        .obtainStyledAttributes(
+                            attrs,
+                            R.styleable.TaskView,
+                            defStyleAttr,
+                            defStyleRes,
+                        )
+                        .getColor(
+                            R.styleable.TaskView_hoverBorderColor,
+                            BorderAnimator.DEFAULT_BORDER_COLOR,
+                        ),
+                )
+            else null
+
     private val rootViewDisplayId: Int
         get() = rootView.display?.displayId ?: Display.DEFAULT_DISPLAY
 
@@ -236,11 +300,17 @@
 
     var taskViewId = UNBOUND_TASK_VIEW_ID
     var isEndQuickSwitchCuj = false
+    var sysUiStatusNavFlags: Int = 0
+        get() =
+            if (enableRefactorTaskThumbnail()) field
+            else firstTaskContainer?.thumbnailViewDeprecated?.sysUiStatusNavFlags ?: 0
+        private set
 
     // Various animation progress variables.
     // progress: 0 = show icon and no insets; 1 = don't show icon and show full insets.
     protected var fullscreenProgress = 0f
         set(value) {
+            if (value == field && enableOverviewIconMenu()) return
             field = Utilities.boundToRange(value, 0f, 1f)
             onFullscreenProgressChanged(field)
         }
@@ -265,6 +335,12 @@
             onModalnessUpdated(field)
         }
 
+    var splitSplashAlpha = 0f
+        set(value) {
+            field = value
+            applyThumbnailSplashAlpha()
+        }
+
     protected var taskThumbnailSplashAlpha = 0f
         set(value) {
             field = value
@@ -354,12 +430,6 @@
             applyTranslationX()
         }
 
-    protected var nonGridPivotTranslationX = 0f
-        set(value) {
-            field = value
-            applyTranslationX()
-        }
-
     // Used when in SplitScreenSelectState
     private var splitSelectTranslationY = 0f
         set(value) {
@@ -373,28 +443,10 @@
             applyTranslationX()
         }
 
-    private val taskViewAlpha = MultiValueAlpha(this, NUM_ALPHA_CHANNELS)
-
-    protected var stableAlpha
-        set(value) {
-            taskViewAlpha.get(ALPHA_INDEX_STABLE).value = value
-        }
-        get() = taskViewAlpha.get(ALPHA_INDEX_STABLE).value
-
-    var attachAlpha
-        set(value) {
-            taskViewAlpha.get(ALPHA_INDEX_ATTACH).value = value
-        }
-        get() = taskViewAlpha.get(ALPHA_INDEX_ATTACH).value
-
-    var splitAlpha
-        set(value) {
-            splitAlphaProperty.value = value
-        }
-        get() = splitAlphaProperty.value
-
-    val splitAlphaProperty: MultiPropertyFactory<View>.MultiProperty
-        get() = taskViewAlpha.get(ALPHA_INDEX_SPLIT)
+    private val taskViewAlpha = MultiValueAlpha(this, Alpha.entries.size)
+    protected var stableAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.STABLE)
+    var attachAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.ATTACH)
+    var splitAlpha by MultiPropertyDelegate(taskViewAlpha, Alpha.SPLIT)
 
     protected var shouldShowScreenshot = false
         get() = !isRunningTask || field
@@ -440,6 +492,7 @@
     // 1 = The TaskView is settled and no longer transitioning
     private var settledProgress = 1f
         set(value) {
+            if (value == field && enableOverviewIconMenu()) return
             field = value
             onSettledProgressUpdated(field)
         }
@@ -448,23 +501,28 @@
         MultiPropertyFactory(
             this,
             SETTLED_PROGRESS,
-            SETTLED_PROGRESS_INDEX_COUNT,
+            SettledProgress.entries.size,
             { x: Float, y: Float -> x * y },
             1f,
         )
-    private val settledProgressFullscreen =
-        settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_FULLSCREEN)
-    private val settledProgressGesture =
-        settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_GESTURE)
-    private val settledProgressDismiss =
-        settledProgressPropertyFactory.get(SETTLED_PROGRESS_INDEX_DISMISS)
+    private var settledProgressFullscreen by
+        MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Fullscreen)
+    private var settledProgressGesture by
+        MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Gesture)
+    private var settledProgressDismiss by
+        MultiPropertyDelegate(settledProgressPropertyFactory, SettledProgress.Dismiss)
+
+    private var viewModel: TaskViewModel? = null
+    private val dispatcherProvider: DispatcherProvider by RecentsDependencies.inject()
+    private val coroutineScope: CoroutineScope by RecentsDependencies.inject()
+    private val coroutineJobs = mutableListOf<Job>()
 
     /**
      * Returns an animator of [settledProgressDismiss] that transition in with a built-in
      * interpolator.
      */
     fun getDismissIconFadeInAnimator(): ObjectAnimator =
-        ObjectAnimator.ofFloat(settledProgressDismiss, MULTI_PROPERTY_VALUE, 1f).apply {
+        ObjectAnimator.ofFloat(this, SETTLED_PROGRESS_DISMISS, 1f).apply {
             duration = FADE_IN_ICON_DURATION
             interpolator = FADE_IN_ICON_INTERPOLATOR
         }
@@ -476,8 +534,7 @@
      */
     fun getDismissIconFadeOutAnimator(): ObjectAnimator =
         AnimatedFloat { v ->
-                settledProgressDismiss.value =
-                    SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(v)
+                settledProgressDismiss = SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(v)
             }
             .animateToValue(1f, 0f)
 
@@ -490,40 +547,7 @@
     init {
         setOnClickListener { _ -> onClick() }
 
-        val cursorHoverStatesEnabled = enableCursorHoverStates()
-        setWillNotDraw(!cursorHoverStatesEnabled)
-        context.obtainStyledAttributes(attrs, R.styleable.TaskView, defStyleAttr, defStyleRes).use {
-            this.focusBorderAnimator =
-                focusBorderAnimator
-                    ?: createSimpleBorderAnimator(
-                        TaskCornerRadius.get(context).toInt(),
-                        context.resources.getDimensionPixelSize(
-                            R.dimen.keyboard_quick_switch_border_width
-                        ),
-                        { bounds: Rect -> getThumbnailBounds(bounds) },
-                        this,
-                        it.getColor(
-                            R.styleable.TaskView_focusBorderColor,
-                            BorderAnimator.DEFAULT_BORDER_COLOR,
-                        ),
-                    )
-            this.hoverBorderAnimator =
-                hoverBorderAnimator
-                    ?: if (cursorHoverStatesEnabled)
-                        createSimpleBorderAnimator(
-                            TaskCornerRadius.get(context).toInt(),
-                            context.resources.getDimensionPixelSize(
-                                R.dimen.task_hover_border_width
-                            ),
-                            { bounds: Rect -> getThumbnailBounds(bounds) },
-                            this,
-                            it.getColor(
-                                R.styleable.TaskView_hoverBorderColor,
-                                BorderAnimator.DEFAULT_BORDER_COLOR,
-                            ),
-                        )
-                    else null
-        }
+        setWillNotDraw(!enableCursorHoverStates())
     }
 
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
@@ -612,8 +636,12 @@
 
     override fun onRecycle() {
         resetPersistentViewTransforms()
+
+        viewModel = null
         attachAlpha = 1f
         splitAlpha = 1f
+        splitSplashAlpha = 0f
+        taskThumbnailSplashAlpha = 0f
         // Clear any references to the thumbnail (it will be re-read either from the cache or the
         // system on next bind)
         if (!enableRefactorTaskThumbnail()) {
@@ -624,6 +652,7 @@
         borderEnabled = false
         hoverBorderVisible = false
         taskViewId = UNBOUND_TASK_VIEW_ID
+        // TODO(b/390583187): Clean the components UI State when TaskView is recycled.
         taskContainers.forEach { it.destroy() }
     }
 
@@ -635,14 +664,11 @@
         with(info) {
             // Only make actions available if the app icon menu is visible to the user.
             // When modalness is >0, the user is in select mode and the icon menu is hidden.
-            if (modalness == 0f) {
-                addAction(
-                    AccessibilityAction(
-                        R.id.action_close,
-                        context.getText(R.string.accessibility_close),
-                    )
-                )
-
+            // When split selection is active, they should only be able to select the app and not
+            // take any other action.
+            val shouldPopulateAccessibilityMenu =
+                modalness == 0f && recentsView?.isSplitSelectionActive == false
+            if (shouldPopulateAccessibilityMenu) {
                 taskContainers.forEach {
                     TraceHelper.allowIpcs("TV.a11yInfo") {
                         TaskOverlayFactory.getEnabledShortcuts(this@TaskView, it).forEach { shortcut
@@ -660,10 +686,10 @@
 
             recentsView?.let {
                 collectionItemInfo =
-                    AccessibilityNodeInfo.CollectionItemInfo.obtain(
+                    AccessibilityNodeInfo.CollectionItemInfo(
                         0,
                         1,
-                        it.taskViewCount - it.indexOfChild(this@TaskView) - 1,
+                        it.getAccessibilityChildren().indexOf(this@TaskView),
                         1,
                         false,
                     )
@@ -673,11 +699,6 @@
 
     override fun performAccessibilityAction(action: Int, arguments: Bundle?): Boolean {
         // TODO(b/343708271): Add support for multiple tasks per action.
-        if (action == R.id.action_close) {
-            recentsView?.dismissTask(this, true /*animateTaskView*/, true /*removeTask*/)
-            return true
-        }
-
         taskContainers.forEach {
             if (it.digitalWellBeingToast?.handleAccessibilityAction(action) == true) {
                 return true
@@ -694,6 +715,128 @@
         return super.performAccessibilityAction(action, arguments)
     }
 
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        inflateViewStubs()
+    }
+
+    protected open fun inflateViewStubs() {
+        findViewById<ViewStub>(R.id.task_content_view)
+            ?.apply { layoutResource = R.layout.task_content_view }
+            ?.inflate()
+
+        findViewById<ViewStub>(R.id.icon)
+            ?.apply {
+                layoutResource =
+                    if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
+                    else R.layout.icon_view
+            }
+            ?.inflate()
+    }
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        if (enableRefactorTaskThumbnail()) {
+            // The TaskView lifecycle is starts the ViewModel during onBind, and cleans it in
+            // onRecycle. So it should be initialized at this point. TaskView Lifecycle:
+            // `bind` -> `onBind` ->  onAttachedToWindow() -> onDetachFromWindow -> onRecycle
+            coroutineJobs +=
+                coroutineScope.launch(dispatcherProvider.main) {
+                    viewModel!!.state.collectLatest(::updateTaskViewState)
+                }
+        }
+    }
+
+    private fun updateTaskViewState(state: TaskTileUiState) {
+        sysUiStatusNavFlags = state.sysUiStatusNavFlags
+
+        // Updating containers
+        val mapOfTasks = state.tasks.associateBy { it.taskId }
+        taskContainers.forEach { container ->
+            val taskId = container.task.key.id
+            val containerState = mapOfTasks[taskId]
+            val shouldHaveHeader = (type == TaskViewType.DESKTOP) && enableDesktopExplodedView()
+            container.setState(
+                state = containerState,
+                liveTile = state.isLiveTile,
+                hasHeader = shouldHaveHeader,
+                clickCloseListener =
+                    if (shouldHaveHeader) {
+                        {
+                            // Update the layout UI to remove this task from the layout grid, and
+                            // remove the task from ActivityManager afterwards.
+                            recentsView?.dismissTask(
+                                taskId,
+                                /* animate= */ true,
+                                /* removeTask= */ true,
+                            )
+                        }
+                    } else {
+                        null
+                    },
+            )
+            updateThumbnailValidity(container)
+            val thumbnailPosition =
+                updateThumbnailMatrix(
+                    container = container,
+                    width = container.thumbnailView.width,
+                    height = container.thumbnailView.height,
+                )
+            container.setOverlayEnabled(state.taskOverlayEnabled, thumbnailPosition)
+
+            if (enableOverviewIconMenu()) {
+                setIconState(container, containerState)
+            }
+        }
+    }
+
+    private fun updateThumbnailValidity(container: TaskContainer) {
+        container.isThumbnailValid =
+            viewModel?.isThumbnailValid(
+                thumbnail = container.thumbnailData,
+                width = container.thumbnailView.width,
+                height = container.thumbnailView.height,
+            ) ?: return
+        applyThumbnailSplashAlpha()
+    }
+
+    /**
+     * Updates the thumbnail's transformation matrix and rotation state within a TaskContainer.
+     *
+     * This function is called to reposition the thumbnail in the following scenarios:
+     * - When the TTV's size changes (onSizeChanged), and it's displaying a SnapshotSplash.
+     * - When drawing a snapshot (drawSnapshot).
+     *
+     * @param container The TaskContainer holding the thumbnail to be updated.
+     * @param width The desired width of the thumbnail's container.
+     * @param height The desired height of the thumbnail's container.
+     */
+    private fun updateThumbnailMatrix(
+        container: TaskContainer,
+        width: Int,
+        height: Int,
+    ): ThumbnailPosition? {
+        val thumbnailPosition =
+            viewModel?.getThumbnailPosition(container.thumbnailData, width, height, isLayoutRtl)
+                ?: return null
+        container.updateThumbnailMatrix(thumbnailPosition.matrix)
+        return thumbnailPosition
+    }
+
+    override fun onDetachedFromWindow() {
+        super.onDetachedFromWindow()
+        if (enableRefactorTaskThumbnail()) {
+            // The jobs are being cancelled in the background thread. So we make a copy of the list
+            // to prevent cleaning a new job that might be added to this list during onAttach
+            // or another moment in the lifecycle.
+            val coroutineJobsToCancel = coroutineJobs.toList()
+            coroutineJobs.clear()
+            coroutineScope.launch(dispatcherProvider.background) {
+                coroutineJobsToCancel.forEach { it.cancel("TaskView detaching from window") }
+            }
+        }
+    }
+
     /** Updates this task view to the given {@param task}. */
     open fun bind(
         task: Task,
@@ -701,10 +844,12 @@
         taskOverlayFactory: TaskOverlayFactory,
     ) {
         cancelPendingLoadTasks()
+        this.orientedState = orientedState // Needed for dependencies
         taskContainers =
             listOf(
                 createTaskContainer(
                     task,
+                    R.id.task_content_view,
                     R.id.snapshot,
                     R.id.icon,
                     R.id.show_windows,
@@ -716,18 +861,56 @@
         onBind(orientedState)
     }
 
-    open fun onBind(orientedState: RecentsOrientedState) {
-        taskContainers.forEach {
-            it.bind()
+    protected open fun onBind(orientedState: RecentsOrientedState) {
+        if (enableRefactorTaskThumbnail()) {
+            val scopeId = context
+            Log.d(TAG, "onBind $scopeId ${orientedState.containerInterface}")
+            viewModel =
+                TaskViewModel(
+                        taskViewType = type,
+                        recentsViewData = RecentsDependencies.get(scopeId),
+                        getTaskUseCase = RecentsDependencies.get(scopeId),
+                        getSysUiStatusNavFlagsUseCase = RecentsDependencies.get(scopeId),
+                        isThumbnailValidUseCase = RecentsDependencies.get(scopeId),
+                        getThumbnailPositionUseCase = RecentsDependencies.get(scopeId),
+                        dispatcherProvider = RecentsDependencies.get(scopeId),
+                    )
+                    .apply { bind(*taskIds) }
+        }
+
+        taskContainers.forEach { container ->
+            container.bind()
             if (enableRefactorTaskThumbnail()) {
-                it.thumbnailView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
+                container.taskContentView.cornerRadius =
+                    thumbnailFullscreenParams.currentCornerRadius
+                container.taskContentView.doOnSizeChange { width, height ->
+                    updateThumbnailValidity(container)
+                    val thumbnailPosition = updateThumbnailMatrix(container, width, height)
+                    container.refreshOverlay(thumbnailPosition)
+                }
             }
         }
         setOrientationState(orientedState)
     }
 
+    private fun applyThumbnailSplashAlpha() {
+        val alpha = getSplashAlphaProgress()
+        taskContainers.forEach { it.updateThumbnailSplashProgress(alpha) }
+    }
+
+    private fun getSplashAlphaProgress(): Float =
+        when {
+            !enableRefactorTaskThumbnail() -> taskThumbnailSplashAlpha
+            splitSplashAlpha > 0f -> splitSplashAlpha
+            shouldShowSplash() -> taskThumbnailSplashAlpha
+            else -> 0f
+        }
+
+    internal fun shouldShowSplash(): Boolean = taskContainers.any { !it.isThumbnailValid }
+
     protected fun createTaskContainer(
         task: Task,
+        @IdRes taskContentViewId: Int,
         @IdRes thumbnailViewId: Int,
         @IdRes iconViewId: Int,
         @IdRes showWindowViewId: Int,
@@ -735,27 +918,13 @@
         @StagePosition stagePosition: Int,
         taskOverlayFactory: TaskOverlayFactory,
     ): TaskContainer {
-        val existingThumbnailView: View = findViewById(thumbnailViewId)!!
-        val snapshotView =
-            when {
-                !enableRefactorTaskThumbnail() -> existingThumbnailView
-                existingThumbnailView is TaskThumbnailView -> existingThumbnailView
-                else -> {
-                    val indexOfSnapshotView = indexOfChild(existingThumbnailView)
-                    LayoutInflater.from(context)
-                        .inflate(R.layout.task_thumbnail, this, false)
-                        .also {
-                            it.id = thumbnailViewId
-                            addView(it, indexOfSnapshotView, existingThumbnailView.layoutParams)
-                            removeView(existingThumbnailView)
-                        }
-                }
-            }
-        val iconView = getOrInflateIconView(iconViewId)
+        val iconView = findViewById<View>(iconViewId) as TaskViewIcon
+        val taskContentView = findViewById<TaskContentView>(taskContentViewId)
         return TaskContainer(
             this,
             task,
-            snapshotView,
+            taskContentView,
+            taskContentView.findViewById(thumbnailViewId),
             iconView,
             TransformingTouchDelegate(iconView.asView()),
             stagePosition,
@@ -765,18 +934,6 @@
         )
     }
 
-    protected fun getOrInflateIconView(@IdRes iconViewId: Int): TaskViewIcon {
-        val iconView = findViewById<View>(iconViewId)!!
-        return iconView as? TaskViewIcon
-            ?: (iconView as ViewStub)
-                .apply {
-                    layoutResource =
-                        if (enableOverviewIconMenu()) R.layout.icon_app_chip_view
-                        else R.layout.icon_view
-                }
-                .inflate() as TaskViewIcon
-    }
-
     fun containsMultipleTasks() = taskContainers.size > 1
 
     /**
@@ -805,11 +962,7 @@
      * Updates TaskView scaling and translation required to support variable width if enabled, while
      * ensuring TaskView fits into screen in fullscreen.
      */
-    open fun updateTaskSize(
-        lastComputedTaskSize: Rect,
-        lastComputedGridTaskSize: Rect,
-        lastComputedCarouselTaskSize: Rect,
-    ) {
+    open fun updateTaskSize(lastComputedTaskSize: Rect, lastComputedGridTaskSize: Rect) {
         val thumbnailPadding = container.deviceProfile.overviewTaskThumbnailTopMarginPx
         val taskWidth = lastComputedTaskSize.width()
         val taskHeight = lastComputedTaskSize.height()
@@ -837,12 +990,7 @@
             expectedHeight = boxHeight + thumbnailPadding
 
             // Scale to to fit task Rect.
-            nonGridScale =
-                if (enableGridOnlyOverview()) {
-                    lastComputedCarouselTaskSize.width() / taskWidth.toFloat()
-                } else {
-                    taskWidth / boxWidth.toFloat()
-                }
+            nonGridScale = taskWidth / boxWidth.toFloat()
 
             // Align to top of task Rect.
             boxTranslationY = (expectedHeight - thumbnailPadding - taskHeight) / 2.0f
@@ -864,7 +1012,7 @@
     protected open fun updateThumbnailSize() {
         // TODO(b/271468547), we should default to setting translations only on the snapshot instead
         //  of a hybrid of both margins and translations
-        taskContainers[0].snapshotView.updateLayoutParams<LayoutParams> {
+        firstTaskContainer?.taskContentView?.updateLayoutParams<LayoutParams> {
             topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
         }
         taskContainers.forEach { it.digitalWellBeingToast?.setupLayout() }
@@ -878,11 +1026,11 @@
             val thumbnailBounds = Rect()
             if (relativeToDragLayer) {
                 container.dragLayer.getDescendantRectRelativeToSelf(
-                    it.snapshotView,
+                    it.taskContentView,
                     thumbnailBounds,
                 )
             } else {
-                thumbnailBounds.set(it.snapshotView)
+                thumbnailBounds.set(it.taskContentView)
             }
             bounds.union(thumbnailBounds)
         }
@@ -924,7 +1072,7 @@
                 }
             }
         }
-        if (needsUpdate(changes, FLAG_UPDATE_ICON)) {
+        if (needsUpdate(changes, FLAG_UPDATE_ICON) && !enableOverviewIconMenu()) {
             taskContainers.forEach {
                 if (visible) {
                     recentsModel.iconCache
@@ -955,10 +1103,23 @@
         pendingIconLoadRequests.clear()
     }
 
+    protected open fun setIconState(container: TaskContainer, state: TaskData?) {
+        if (enableOverviewIconMenu()) {
+            if (state is TaskData.Data) {
+                setIcon(container.iconView, state.icon)
+                container.iconView.setText(state.title)
+                container.digitalWellBeingToast?.initialize()
+            } else {
+                setIcon(container.iconView, null)
+                container.iconView.setText(null)
+            }
+        }
+    }
+
     protected open fun onIconLoaded(taskContainer: TaskContainer) {
         setIcon(taskContainer.iconView, taskContainer.task.icon)
         if (enableOverviewIconMenu()) {
-            setText(taskContainer.iconView, taskContainer.task.title)
+            taskContainer.iconView.setText(taskContainer.task.title)
         }
         taskContainer.digitalWellBeingToast?.initialize()
     }
@@ -966,7 +1127,7 @@
     protected open fun onIconUnloaded(taskContainer: TaskContainer) {
         setIcon(taskContainer.iconView, null)
         if (enableOverviewIconMenu()) {
-            setText(taskContainer.iconView, null)
+            taskContainer.iconView.setText(null)
         }
     }
 
@@ -991,10 +1152,6 @@
         }
     }
 
-    protected fun setText(iconView: TaskViewIcon, text: CharSequence?) {
-        iconView.setText(text)
-    }
-
     @JvmOverloads
     open fun setShouldShowScreenshot(
         shouldShowScreenshot: Boolean,
@@ -1030,7 +1187,7 @@
         Log.d("b/310064698", "${taskIds.contentToString()} - onClick - callbackList: $callbackList")
         container.statsLogManager
             .logger()
-            .withItemInfo(firstItemInfo)
+            .withItemInfo(itemInfo)
             .log(LauncherEvent.LAUNCHER_TASK_LAUNCH_TAP)
     }
 
@@ -1101,6 +1258,7 @@
                 recentsView.stateManager,
                 recentsView,
                 recentsView.depthController,
+                /* transitionInfo= */ null,
             )
             addListener(
                 object : AnimatorListenerAdapter() {
@@ -1134,6 +1292,7 @@
      * @return CompletionStage to indicate the animation completion or null if the launch failed.
      */
     open fun launchAsStaticTile(): RunnableList? {
+        val firstTaskContainer = firstTaskContainer ?: return null
         TestLogging.recordEvent(
             TestProtocol.SEQUENCE_MAIN,
             "startActivityFromRecentsAsync",
@@ -1141,11 +1300,11 @@
         )
         val opts =
             container.getActivityLaunchOptions(this, null).apply {
-                options.launchDisplayId = display?.displayId ?: Display.DEFAULT_DISPLAY
+                options.launchDisplayId = displayId
             }
         if (
             ActivityManagerWrapper.getInstance()
-                .startActivityFromRecents(taskContainers[0].task.key, opts.options)
+                .startActivityFromRecents(firstTaskContainer.task.key, opts.options)
         ) {
             Log.d(
                 TAG,
@@ -1184,18 +1343,18 @@
         isQuickSwitch: Boolean = false,
         callback: (launched: Boolean) -> Unit,
     ) {
+        val firstTaskContainer = firstTaskContainer ?: return
         TestLogging.recordEvent(
             TestProtocol.SEQUENCE_MAIN,
             "startActivityFromRecentsAsync",
             taskIds.contentToString(),
         )
-        val firstContainer = taskContainers[0]
         val failureListener = TaskRemovedDuringLaunchListener(context.applicationContext)
         if (isQuickSwitch) {
             // We only listen for failures to launch in quickswitch because the during this
             // gesture launcher is in the background state, vs other launches which are in
             // the actual overview state
-            failureListener.register(container, firstContainer.task.key.id) {
+            failureListener.register(container, firstTaskContainer.task.key.id) {
                 notifyTaskLaunchFailed("launchWithoutAnimation")
                 recentsView?.let {
                     // Disable animations for now, as it is an edge case and the app usually
@@ -1227,12 +1386,13 @@
                     if (isQuickSwitch) {
                         setFreezeRecentTasksReordering()
                     }
-                    disableStartingWindow = firstContainer.shouldShowSplashView
+                    // TODO(b/331754864): Update this to use TV.shouldShowSplash
+                    disableStartingWindow = firstTaskContainer.shouldShowSplashView
                 }
         Executors.UI_HELPER_EXECUTOR.execute {
             if (
                 !ActivityManagerWrapper.getInstance()
-                    .startActivityFromRecents(firstContainer.task.key, opts)
+                    .startActivityFromRecents(firstTaskContainer.task.key, opts)
             ) {
                 // If the call to start activity failed, then post the result immediately,
                 // otherwise, wait for the animation start callback from the activity options
@@ -1259,14 +1419,6 @@
         Toast.makeText(context, R.string.activity_not_available, Toast.LENGTH_SHORT).show()
     }
 
-    fun initiateSplitSelect(splitPositionOption: SplitPositionOption) {
-        recentsView?.initiateSplitSelect(
-            this,
-            splitPositionOption.stagePosition,
-            SplitConfigurationOptions.getLogEventForPosition(splitPositionOption.stagePosition),
-        )
-    }
-
     /**
      * Returns `true` if user is already in split select mode and this tap was to choose the second
      * app. `false` otherwise
@@ -1283,7 +1435,7 @@
             container.task,
             container.iconView.drawable,
             container.snapshotView,
-            container.splitAnimationThumbnail,
+            container.thumbnail,
             /* intent */ null,
             /* user */ null,
             container.itemInfo,
@@ -1324,7 +1476,8 @@
         return if (enableOverviewIconMenu() && menuContainer.iconView is IconAppChipView) {
             menuContainer.iconView.revealAnim(/* isRevealing= */ true)
             TaskMenuView.showForTask(menuContainer) {
-                menuContainer.iconView.revealAnim(/* isRevealing= */ false)
+                val isAnimated = !recentsView.isSplitSelectionActive
+                menuContainer.iconView.revealAnim(/* isRevealing= */ false, isAnimated)
                 if (enableHoverOfChildElementsInTaskview()) {
                     recentsView.setTaskBorderEnabled(true)
                 }
@@ -1450,7 +1603,7 @@
     fun startIconFadeInOnGestureComplete() {
         iconFadeInOnGestureCompleteAnimator?.cancel()
         iconFadeInOnGestureCompleteAnimator =
-            ObjectAnimator.ofFloat(settledProgressGesture, MULTI_PROPERTY_VALUE, 1f).apply {
+            ObjectAnimator.ofFloat(this, SETTLED_PROGRESS_GESTURE, 1f).apply {
                 duration = FADE_IN_ICON_DURATION
                 interpolator = Interpolators.LINEAR
                 addListener(
@@ -1466,13 +1619,15 @@
 
     fun setIconVisibleForGesture(isVisible: Boolean) {
         iconFadeInOnGestureCompleteAnimator?.cancel()
-        settledProgressGesture.value = if (isVisible) 1f else 0f
+        settledProgressGesture = if (isVisible) 1f else 0f
     }
 
     /** Set a color tint on the snapshot and supporting views. */
     open fun setColorTint(amount: Float, tintColor: Int) {
         taskContainers.forEach {
-            if (!enableRefactorTaskThumbnail()) {
+            if (enableRefactorTaskThumbnail()) {
+                it.updateTintAmount(amount)
+            } else {
                 it.thumbnailViewDeprecated.dimAlpha = amount
             }
             it.iconView.setIconColorTint(tintColor, amount)
@@ -1512,7 +1667,7 @@
     protected fun getScrollAdjustment(gridEnabled: Boolean) =
         if (gridEnabled) gridTranslationX else nonGridTranslationX
 
-    protected fun getOffsetAdjustment(gridEnabled: Boolean) = getScrollAdjustment(gridEnabled)
+    fun getOffsetAdjustment(gridEnabled: Boolean) = getScrollAdjustment(gridEnabled)
 
     fun getSizeAdjustment(fullscreenEnabled: Boolean) = if (fullscreenEnabled) nonGridScale else 1f
 
@@ -1523,14 +1678,6 @@
         updateFullscreenParams()
     }
 
-    protected open fun applyThumbnailSplashAlpha() {
-        if (!enableRefactorTaskThumbnail()) {
-            taskContainers.forEach {
-                it.thumbnailViewDeprecated.setSplashAlpha(taskThumbnailSplashAlpha)
-            }
-        }
-    }
-
     private fun applyTranslationX() {
         translationX =
             dismissTranslationX +
@@ -1558,10 +1705,12 @@
 
     protected open fun onFullscreenProgressChanged(fullscreenProgress: Float) {
         taskContainers.forEach {
-            it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
+            if (!enableOverviewIconMenu()) {
+                it.iconView.setVisibility(if (fullscreenProgress < 1) VISIBLE else INVISIBLE)
+            }
             it.overlay.setFullscreenProgress(fullscreenProgress)
         }
-        settledProgressFullscreen.value =
+        settledProgressFullscreen =
             SETTLED_PROGRESS_FAST_OUT_INTERPOLATOR.getInterpolation(1 - fullscreenProgress)
         updateFullscreenParams()
     }
@@ -1570,7 +1719,7 @@
         updateFullscreenParams(thumbnailFullscreenParams)
         taskContainers.forEach {
             if (enableRefactorTaskThumbnail()) {
-                it.thumbnailView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
+                it.taskContentView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
             } else {
                 it.thumbnailViewDeprecated.setFullscreenParams(thumbnailFullscreenParams)
             }
@@ -1595,7 +1744,6 @@
         gridTranslationX = 0f
         gridTranslationY = 0f
         boxTranslationY = 0f
-        nonGridPivotTranslationX = 0f
         taskContainers.forEach {
             it.snapshotView.translationX = 0f
             it.snapshotView.translationY = 0f
@@ -1620,7 +1768,7 @@
         dismissScale = 1f
         translationZ = 0f
         setIconVisibleForGesture(true)
-        settledProgressDismiss.value = 1f
+        settledProgressDismiss = 1f
         setColorTint(0f, 0)
     }
 
@@ -1642,23 +1790,25 @@
 
     companion object {
         private const val TAG = "TaskView"
+
+        private enum class Alpha {
+            STABLE,
+            ATTACH,
+            SPLIT,
+        }
+
+        private enum class SettledProgress {
+            Fullscreen,
+            Gesture,
+            Dismiss,
+        }
+
         const val FLAG_UPDATE_ICON = 1
         const val FLAG_UPDATE_THUMBNAIL = FLAG_UPDATE_ICON shl 1
         const val FLAG_UPDATE_CORNER_RADIUS = FLAG_UPDATE_THUMBNAIL shl 1
         const val FLAG_UPDATE_ALL =
             (FLAG_UPDATE_ICON or FLAG_UPDATE_THUMBNAIL or FLAG_UPDATE_CORNER_RADIUS)
 
-        const val SETTLED_PROGRESS_INDEX_FULLSCREEN = 0
-        const val SETTLED_PROGRESS_INDEX_GESTURE = 1
-        const val SETTLED_PROGRESS_INDEX_DISMISS = 2
-        const val SETTLED_PROGRESS_INDEX_COUNT = 3
-
-        private const val ALPHA_INDEX_STABLE = 0
-        private const val ALPHA_INDEX_ATTACH = 1
-        private const val ALPHA_INDEX_SPLIT = 2
-
-        private const val NUM_ALPHA_CHANNELS = 3
-
         /** The maximum amount that a task view can be scrimmed, dimmed or tinted. */
         const val MAX_PAGE_SCRIM_ALPHA = 0.4f
         const val FADE_IN_ICON_DURATION: Long = 120
@@ -1675,104 +1825,45 @@
         private val SYSTEM_GESTURE_EXCLUSION_RECT = listOf(Rect())
 
         private val SETTLED_PROGRESS: FloatProperty<TaskView> =
-            object : FloatProperty<TaskView>("settleTransition") {
-                override fun setValue(taskView: TaskView, v: Float) {
-                    taskView.settledProgress = v
-                }
+            KFloatProperty(TaskView::settledProgress)
 
-                override fun get(taskView: TaskView) = taskView.settledProgress
-            }
+        private val SETTLED_PROGRESS_GESTURE: FloatProperty<TaskView> =
+            KFloatProperty(TaskView::settledProgressGesture)
+
+        private val SETTLED_PROGRESS_DISMISS: FloatProperty<TaskView> =
+            KFloatProperty(TaskView::settledProgressDismiss)
 
         private val SPLIT_SELECT_TRANSLATION_X: FloatProperty<TaskView> =
-            object : FloatProperty<TaskView>("splitSelectTranslationX") {
-                override fun setValue(taskView: TaskView, v: Float) {
-                    taskView.splitSelectTranslationX = v
-                }
-
-                override fun get(taskView: TaskView) = taskView.splitSelectTranslationX
-            }
+            KFloatProperty(TaskView::splitSelectTranslationX)
 
         private val SPLIT_SELECT_TRANSLATION_Y: FloatProperty<TaskView> =
-            object : FloatProperty<TaskView>("splitSelectTranslationY") {
-                override fun setValue(taskView: TaskView, v: Float) {
-                    taskView.splitSelectTranslationY = v
-                }
-
-                override fun get(taskView: TaskView) = taskView.splitSelectTranslationY
-            }
+            KFloatProperty(TaskView::splitSelectTranslationY)
 
         private val DISMISS_TRANSLATION_X: FloatProperty<TaskView> =
-            object : FloatProperty<TaskView>("dismissTranslationX") {
-                override fun setValue(taskView: TaskView, v: Float) {
-                    taskView.dismissTranslationX = v
-                }
-
-                override fun get(taskView: TaskView) = taskView.dismissTranslationX
-            }
+            KFloatProperty(TaskView::dismissTranslationX)
 
         private val DISMISS_TRANSLATION_Y: FloatProperty<TaskView> =
-            object : FloatProperty<TaskView>("dismissTranslationY") {
-                override fun setValue(taskView: TaskView, v: Float) {
-                    taskView.dismissTranslationY = v
-                }
-
-                override fun get(taskView: TaskView) = taskView.dismissTranslationY
-            }
+            KFloatProperty(TaskView::dismissTranslationY)
 
         private val TASK_OFFSET_TRANSLATION_X: FloatProperty<TaskView> =
-            object : FloatProperty<TaskView>("taskOffsetTranslationX") {
-                override fun setValue(taskView: TaskView, v: Float) {
-                    taskView.taskOffsetTranslationX = v
-                }
-
-                override fun get(taskView: TaskView) = taskView.taskOffsetTranslationX
-            }
+            KFloatProperty(TaskView::taskOffsetTranslationX)
 
         private val TASK_OFFSET_TRANSLATION_Y: FloatProperty<TaskView> =
-            object : FloatProperty<TaskView>("taskOffsetTranslationY") {
-                override fun setValue(taskView: TaskView, v: Float) {
-                    taskView.taskOffsetTranslationY = v
-                }
-
-                override fun get(taskView: TaskView) = taskView.taskOffsetTranslationY
-            }
+            KFloatProperty(TaskView::taskOffsetTranslationY)
 
         private val TASK_RESISTANCE_TRANSLATION_X: FloatProperty<TaskView> =
-            object : FloatProperty<TaskView>("taskResistanceTranslationX") {
-                override fun setValue(taskView: TaskView, v: Float) {
-                    taskView.taskResistanceTranslationX = v
-                }
-
-                override fun get(taskView: TaskView) = taskView.taskResistanceTranslationX
-            }
+            KFloatProperty(TaskView::taskResistanceTranslationX)
 
         private val TASK_RESISTANCE_TRANSLATION_Y: FloatProperty<TaskView> =
-            object : FloatProperty<TaskView>("taskResistanceTranslationY") {
-                override fun setValue(taskView: TaskView, v: Float) {
-                    taskView.taskResistanceTranslationY = v
-                }
-
-                override fun get(taskView: TaskView) = taskView.taskResistanceTranslationY
-            }
+            KFloatProperty(TaskView::taskResistanceTranslationY)
 
         @JvmField
         val GRID_END_TRANSLATION_X: FloatProperty<TaskView> =
-            object : FloatProperty<TaskView>("gridEndTranslationX") {
-                override fun setValue(taskView: TaskView, v: Float) {
-                    taskView.gridEndTranslationX = v
-                }
-
-                override fun get(taskView: TaskView) = taskView.gridEndTranslationX
-            }
+            KFloatProperty(TaskView::gridEndTranslationX)
 
         @JvmField
-        val DISMISS_SCALE: FloatProperty<TaskView> =
-            object : FloatProperty<TaskView>("dismissScale") {
-                override fun setValue(taskView: TaskView, v: Float) {
-                    taskView.dismissScale = v
-                }
+        val DISMISS_SCALE: FloatProperty<TaskView> = KFloatProperty(TaskView::dismissScale)
 
-                override fun get(taskView: TaskView) = taskView.dismissScale
-            }
+        @JvmField val SPLIT_ALPHA: FloatProperty<TaskView> = KFloatProperty(TaskView::splitAlpha)
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskViewIcon.java b/quickstep/src/com/android/quickstep/views/TaskViewIcon.java
index 94739cb..80e3a2b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskViewIcon.java
+++ b/quickstep/src/com/android/quickstep/views/TaskViewIcon.java
@@ -48,6 +48,11 @@
     void setModalAlpha(float alpha);
 
     /**
+     * Sets the opacity of the view for flex split state.
+     */
+    void setFlexSplitAlpha(float alpha);
+
+    /**
      * Returns this icon view's drawable.
      */
     @Nullable Drawable getDrawable();
diff --git a/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java b/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java
index c319cb1..cb7254f 100644
--- a/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java
+++ b/quickstep/src_protolog/com/android/launcher3/util/StateManagerProtoLogProxy.java
@@ -16,22 +16,25 @@
 
 package com.android.launcher3.util;
 
-import static com.android.launcher3.Flags.enableStateManagerProtoLog;
 import static com.android.quickstep.util.QuickstepProtoLogGroup.LAUNCHER_STATE_MANAGER;
 import static com.android.quickstep.util.QuickstepProtoLogGroup.isProtoLogInitialized;
 
+import android.window.DesktopModeFlags.DesktopModeFlag;
+
 import androidx.annotation.NonNull;
 
 import com.android.internal.protolog.ProtoLog;
+import com.android.launcher3.Flags;
 
 /**
  * Proxy class used for StateManager ProtoLog support.
  */
 public class StateManagerProtoLogProxy {
-
+    private static final DesktopModeFlag ENABLE_STATE_MANAGER_PROTO_LOG =
+            new DesktopModeFlag(Flags::enableStateManagerProtoLog, true);
     public static void logGoToState(
             @NonNull Object fromState, @NonNull Object toState, @NonNull String trace) {
-        if (!enableStateManagerProtoLog() || !isProtoLogInitialized()) return;
+        if (!ENABLE_STATE_MANAGER_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return;
         ProtoLog.d(LAUNCHER_STATE_MANAGER,
                 "StateManager.goToState: fromState: %s, toState: %s, partial trace:\n%s",
                 fromState,
@@ -41,7 +44,7 @@
 
     public static void logCreateAtomicAnimation(
             @NonNull Object fromState, @NonNull Object toState, @NonNull String trace) {
-        if (!enableStateManagerProtoLog() || !isProtoLogInitialized()) return;
+        if (!ENABLE_STATE_MANAGER_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return;
         ProtoLog.d(LAUNCHER_STATE_MANAGER, "StateManager.createAtomicAnimation: "
                         + "fromState: %s, toState: %s, partial trace:\n%s",
                 fromState,
@@ -50,17 +53,17 @@
     }
 
     public static void logOnStateTransitionStart(@NonNull Object state) {
-        if (!enableStateManagerProtoLog() || !isProtoLogInitialized()) return;
+        if (!ENABLE_STATE_MANAGER_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return;
         ProtoLog.d(LAUNCHER_STATE_MANAGER, "StateManager.onStateTransitionStart: state: %s", state);
     }
 
     public static void logOnStateTransitionEnd(@NonNull Object state) {
-        if (!enableStateManagerProtoLog() || !isProtoLogInitialized()) return;
+        if (!ENABLE_STATE_MANAGER_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return;
         ProtoLog.d(LAUNCHER_STATE_MANAGER, "StateManager.onStateTransitionEnd: state: %s", state);
     }
 
     public static void logCancelAnimation(boolean animationOngoing, @NonNull String trace) {
-        if (!enableStateManagerProtoLog() || !isProtoLogInitialized()) return;
+        if (!ENABLE_STATE_MANAGER_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return;
         ProtoLog.d(LAUNCHER_STATE_MANAGER,
                 "StateManager.cancelAnimation: animation ongoing: %b, partial trace:\n%s",
                 animationOngoing,
diff --git a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
index be1a4e8..18a5338 100644
--- a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
@@ -159,31 +159,37 @@
         ProtoLog.d(ACTIVE_GESTURE_LOG, "cleanUpRecentsAnimation");
     }
 
-    public static void logOnInputEventUserLocked() {
-        ActiveGestureLog.INSTANCE.addLog(
-                "TIS.onInputEvent: Cannot process input event: user is locked");
+    public static void logOnInputEventUserLocked(int displayId) {
+        ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                "TIS.onInputEvent(displayId=%d): Cannot process input event: user is locked",
+                displayId));
         if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
-                "TIS.onInputEvent: Cannot process input event: user is locked");
+                "TIS.onInputEvent(displayId=%d): Cannot process input event: user is locked",
+                displayId);
     }
 
-    public static void logOnInputIgnoringFollowingEvents() {
-        ActiveGestureLog.INSTANCE.addLog("TIS.onMotionEvent: A new gesture has been started, "
+    public static void logOnInputIgnoringFollowingEvents(int displayId) {
+        ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                "TIS.onMotionEvent(displayId=%d): A new gesture has been started, "
                         + "but a previously-requested recents animation hasn't started. "
-                        + "Ignoring all following motion events.",
+                        + "Ignoring all following motion events.", displayId),
                 RECENTS_ANIMATION_START_PENDING);
         if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
-        ProtoLog.d(ACTIVE_GESTURE_LOG, "TIS.onMotionEvent: A new gesture has been started, "
-                + "but a previously-requested recents animation hasn't started. "
-                + "Ignoring all following motion events.");
+        ProtoLog.d(ACTIVE_GESTURE_LOG,
+                "TIS.onMotionEvent(displayId=%d): A new gesture has been started, "
+                        + "but a previously-requested recents animation hasn't started. "
+                        + "Ignoring all following motion events.", displayId);
     }
 
-    public static void logOnInputEventThreeButtonNav() {
-        ActiveGestureLog.INSTANCE.addLog("TIS.onInputEvent: Cannot process input event: "
-                + "using 3-button nav and event is not a trackpad event");
+    public static void logOnInputEventThreeButtonNav(int displayId) {
+        ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
+                "TIS.onInputEvent(displayId=%d): Cannot process input event: "
+                        + "using 3-button nav and event is not a trackpad event", displayId));
         if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
-        ProtoLog.d(ACTIVE_GESTURE_LOG, "TIS.onInputEvent: Cannot process input event: "
-                + "using 3-button nav and event is not a trackpad event");
+        ProtoLog.d(ACTIVE_GESTURE_LOG,
+                "TIS.onInputEvent(displayId=%d): Cannot process input event: "
+                        + "using 3-button nav and event is not a trackpad event", displayId);
     }
 
     public static void logPreloadRecentsAnimation() {
@@ -322,61 +328,84 @@
     }
 
     public static void logOnInputEventActionUp(
-            int x, int y, int action, @NonNull String classification) {
+            int x, int y, int action, @NonNull String classification, int displayId) {
         String actionString = MotionEvent.actionToString(action);
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
-                "onMotionEvent(%d, %d): %s, %s", x, y, actionString, classification),
+                "onMotionEvent(%d, %d): %s, %s, displayId=%d",
+                        x,
+                        y,
+                        actionString,
+                        classification,
+                        displayId),
                 /* gestureEvent= */ action == ACTION_DOWN
                         ? MOTION_DOWN
                         : MOTION_UP);
         if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
-                "onMotionEvent(%d, %d): %s, %s", x, y, actionString, classification);
+                "onMotionEvent(%d, %d): %s, %s, displayId=%d",
+                x,
+                y,
+                actionString,
+                classification,
+                displayId);
     }
 
     public static void logOnInputEventActionMove(
-            @NonNull String action, @NonNull String classification, int pointerCount) {
+            @NonNull String action,
+            @NonNull String classification,
+            int pointerCount,
+            int displayId) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
-                        "onMotionEvent: %s, %s, pointerCount: %d",
+                        "onMotionEvent: %s, %s, pointerCount: %d, displayId=%d",
                         action,
                         classification,
-                        pointerCount),
+                        pointerCount,
+                        displayId),
                 MOTION_MOVE);
         if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
-                "onMotionEvent: %s, %s, pointerCount: %d", action, classification, pointerCount);
+                "onMotionEvent: %s, %s, pointerCount: %d, displayId=%d",
+                action,
+                classification,
+                pointerCount,
+                displayId);
     }
 
     public static void logOnInputEventGenericAction(
-            @NonNull String action, @NonNull String classification) {
+            @NonNull String action, @NonNull String classification, int displayId) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
-                "onMotionEvent: %s, %s", action, classification));
+                "onMotionEvent: %s, %s, displayId=%d", action, classification, displayId));
         if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
-        ProtoLog.d(ACTIVE_GESTURE_LOG, "onMotionEvent: %s, %s", action, classification);
+        ProtoLog.d(ACTIVE_GESTURE_LOG,
+                "onMotionEvent: %s, %s, displayId=%d", action, classification, displayId);
     }
 
     public static void logOnInputEventNavModeSwitched(
-            @NonNull String startNavMode, @NonNull String currentNavMode) {
+            int displayId, @NonNull String startNavMode, @NonNull String currentNavMode) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
-                "TIS.onInputEvent: Navigation mode switched mid-gesture (%s -> %s); "
+                "TIS.onInputEvent(displayId=%d): Navigation mode switched mid-gesture (%s -> %s); "
                         + "cancelling gesture.",
+                        displayId,
                         startNavMode,
                         currentNavMode),
                 NAVIGATION_MODE_SWITCHED);
         if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
-                "TIS.onInputEvent: Navigation mode switched mid-gesture (%s -> %s); "
+                "TIS.onInputEvent(displayId=%d): Navigation mode switched mid-gesture (%s -> %s); "
                         + "cancelling gesture.",
+                displayId,
                 startNavMode,
                 currentNavMode);
     }
 
-    public static void logUnknownInputEvent(@NonNull String event) {
+    public static void logUnknownInputEvent(int displayId, @NonNull String event) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
-                "TIS.onInputEvent: Cannot process input event: received unknown event %s", event));
+                "TIS.onInputEvent(displayId=%d): Cannot process input event: "
+                        + "received unknown event %s", displayId, event));
         if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
-                "TIS.onInputEvent: Cannot process input event: received unknown event %s", event);
+                "TIS.onInputEvent(displayId=%d): Cannot process input event: "
+                        + "received unknown event %s", displayId, event);
     }
 
     public static void logFinishRunningRecentsAnimation(boolean toHome) {
@@ -395,11 +424,11 @@
 
     public static void logOnRecentsAnimationStart(int appCount) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
-                "RecentsAnimationCallbacks.onAnimationStart (canceled): %d", appCount),
+                "RecentsAnimationCallbacks.onAnimationStart: %d", appCount),
                 /* gestureEvent= */ ON_START_RECENTS_ANIMATION);
         if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
         ProtoLog.d(ACTIVE_GESTURE_LOG,
-                "RecentsAnimationCallbacks.onAnimationStart (canceled): %d", appCount);
+                "RecentsAnimationCallbacks.onAnimationStart: %d", appCount);
     }
 
     public static void logStartRecentsAnimationCallback(@NonNull String callback) {
@@ -433,11 +462,13 @@
         ProtoLog.d(ACTIVE_GESTURE_LOG, "Launching side task id=%d", taskId);
     }
 
-    public static void logOnInputEventActionDown(@NonNull ActiveGestureLog.CompoundString reason) {
+    public static void logOnInputEventActionDown(
+            int displayId, @NonNull ActiveGestureLog.CompoundString reason) {
         ActiveGestureLog.INSTANCE.addLog(new ActiveGestureLog.CompoundString(
-                "TIS.onMotionEvent: ").append(reason));
+                "TIS.onMotionEvent(displayId=%d): ", displayId).append(reason));
         if (!enableActiveGestureProtoLog() || !isProtoLogInitialized()) return;
-        ProtoLog.d(ACTIVE_GESTURE_LOG, "TIS.onMotionEvent: %s", reason.toString());
+        ProtoLog.d(ACTIVE_GESTURE_LOG,
+                "TIS.onMotionEvent(displayId=%d): %s", displayId, reason.toString());
     }
 
     public static void logStartNewTask(@NonNull ActiveGestureLog.CompoundString tasks) {
diff --git a/quickstep/src_protolog/com/android/quickstep/util/RecentsWindowProtoLogProxy.java b/quickstep/src_protolog/com/android/quickstep/util/RecentsWindowProtoLogProxy.java
index 2c9ae33..99888fb 100644
--- a/quickstep/src_protolog/com/android/quickstep/util/RecentsWindowProtoLogProxy.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/RecentsWindowProtoLogProxy.java
@@ -16,14 +16,16 @@
 
 package com.android.quickstep.util;
 
-import static com.android.launcher3.Flags.enableRecentsWindowProtoLog;
 import static com.android.quickstep.util.QuickstepProtoLogGroup.RECENTS_WINDOW;
 import static com.android.quickstep.util.QuickstepProtoLogGroup.isProtoLogInitialized;
 
+import android.window.DesktopModeFlags;
+
 import androidx.annotation.NonNull;
 
 import com.android.internal.protolog.ProtoLog;
 import com.android.internal.protolog.common.IProtoLogGroup;
+import com.android.launcher3.Flags;
 
 /**
  * Proxy class used for Recents Window ProtoLog support.
@@ -35,19 +37,20 @@
  * method. Or, if an existing entry needs to be modified, simply update it here.
  */
 public class RecentsWindowProtoLogProxy {
-
+    private static final DesktopModeFlags.DesktopModeFlag ENABLE_RECENTS_WINDOW_PROTO_LOG =
+            new DesktopModeFlags.DesktopModeFlag(Flags::enableRecentsWindowProtoLog, true);
     public static void logOnStateSetStart(@NonNull String stateName) {
-        if (!enableRecentsWindowProtoLog() || !isProtoLogInitialized()) return;
+        if (!ENABLE_RECENTS_WINDOW_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return;
         ProtoLog.d(RECENTS_WINDOW, "onStateSetStart: %s", stateName);
     }
 
     public static void logOnStateSetEnd(@NonNull String stateName) {
-        if (!enableRecentsWindowProtoLog() || !isProtoLogInitialized()) return;
+        if (!ENABLE_RECENTS_WINDOW_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return;
         ProtoLog.d(RECENTS_WINDOW, "onStateSetEnd: %s", stateName);
     }
 
     public static void logStartRecentsWindow(boolean isShown, boolean windowViewIsNull) {
-        if (!enableRecentsWindowProtoLog() || !isProtoLogInitialized()) return;
+        if (!ENABLE_RECENTS_WINDOW_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return;
         ProtoLog.d(RECENTS_WINDOW,
                 "Starting recents window: isShow= %b, windowViewIsNull=%b",
                 isShown,
@@ -55,7 +58,7 @@
     }
 
     public static void logCleanup(boolean isShown) {
-        if (!enableRecentsWindowProtoLog() || !isProtoLogInitialized()) return;
+        if (!ENABLE_RECENTS_WINDOW_PROTO_LOG.isTrue() || !isProtoLogInitialized()) return;
         ProtoLog.d(RECENTS_WINDOW, "Cleaning up recents window: isShow= %b", isShown);
     }
 }
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
deleted file mode 100644
index 47d2bfc..0000000
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/FakeTaskThumbnailViewModel.kt
+++ /dev/null
@@ -1,35 +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.quickstep.task.thumbnail
-
-import android.graphics.Matrix
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
-import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
-
-class FakeTaskThumbnailViewModel : TaskThumbnailViewModel {
-    override val dimProgress = MutableStateFlow(0f)
-    override val splashAlpha = MutableStateFlow(0f)
-    override val uiState = MutableStateFlow<TaskThumbnailUiState>(Uninitialized)
-
-    override fun bind(taskId: Int) {
-        // no-op
-    }
-
-    override fun getThumbnailPositionState(width: Int, height: Int, isRtl: Boolean) =
-        Matrix.IDENTITY_MATRIX
-}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/SplashHelper.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/SplashHelper.kt
new file mode 100644
index 0000000..8cc09d4
--- /dev/null
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/SplashHelper.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.thumbnail
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+
+object SplashHelper {
+    private val BITMAP_RECT_COLORS = listOf(Color.GREEN, Color.RED, Color.BLUE, Color.CYAN)
+
+    fun createSplash(): Bitmap = createBitmap(width = 20, height = 20, rectColorRotation = 1)
+
+    fun createBitmap(width: Int, height: Int, rectColorRotation: Int = 0): Bitmap =
+        Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply {
+            Canvas(this).apply {
+                val paint = Paint()
+                paint.color = BITMAP_RECT_COLORS[rectColorRotation % 4]
+                drawRect(0f, 0f, width / 2f, height / 2f, paint)
+                paint.color = BITMAP_RECT_COLORS[(1 + rectColorRotation) % 4]
+                drawRect(width / 2f, 0f, width.toFloat(), height / 2f, paint)
+                paint.color = BITMAP_RECT_COLORS[(2 + rectColorRotation) % 4]
+                drawRect(0f, height / 2f, width / 2f, height.toFloat(), paint)
+                paint.color = BITMAP_RECT_COLORS[(3 + rectColorRotation) % 4]
+                drawRect(width / 2f, height / 2f, width.toFloat(), height.toFloat(), paint)
+            }
+        }
+}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskContentViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskContentViewScreenshotTest.kt
new file mode 100644
index 0000000..d36faa2
--- /dev/null
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskContentViewScreenshotTest.kt
@@ -0,0 +1,113 @@
+/*
+ * 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.thumbnail
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.BitmapDrawable
+import android.view.LayoutInflater
+import com.android.launcher3.R
+import com.android.quickstep.task.thumbnail.SplashHelper.createSplash
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
+import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.Displays
+import platform.test.screenshot.ViewScreenshotTestRule
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+/** Screenshot tests for [TaskContentView]. */
+@RunWith(ParameterizedAndroidJunit4::class)
+class TaskContentViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+
+    @get:Rule
+    val screenshotRule =
+        ViewScreenshotTestRule(
+            emulationSpec,
+            ViewScreenshotGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)),
+        )
+
+    @Test
+    fun taskContentView_recyclesToUninitialized() {
+        screenshotRule.screenshotTest("taskContentView_uninitialized") { activity ->
+            activity.actionBar?.hide()
+            val taskContentView = createTaskContentView(activity)
+            taskContentView.setState(
+                TaskHeaderUiState.HideHeader,
+                BackgroundOnly(Color.YELLOW),
+                null,
+            )
+            taskContentView.onRecycle()
+            taskContentView
+        }
+    }
+
+    @Test
+    fun taskContentView_shows_thumbnail_and_header() {
+        screenshotRule.screenshotTest("taskContentView_shows_thumbnail_and_header") { activity ->
+            activity.actionBar?.hide()
+            createTaskContentView(activity).apply {
+                setState(
+                    TaskHeaderUiState.ShowHeader(
+                        TaskHeaderUiState.ThumbnailHeader(
+                            BitmapDrawable(activity.resources, createSplash()),
+                            "test",
+                        ) {}
+                    ),
+                    BackgroundOnly(Color.YELLOW),
+                    null,
+                )
+            }
+        }
+    }
+
+    @Test
+    fun taskContentView_scaled_roundRoundedCorners() {
+        screenshotRule.screenshotTest("taskContentView_scaledRoundedCorners") { activity ->
+            activity.actionBar?.hide()
+            createTaskContentView(activity).apply {
+                scaleX = 0.75f
+                scaleY = 0.3f
+                setState(TaskHeaderUiState.HideHeader, BackgroundOnly(Color.YELLOW), null)
+            }
+        }
+    }
+
+    private fun createTaskContentView(context: Context): TaskContentView {
+        val taskContentView =
+            LayoutInflater.from(context).inflate(R.layout.task_content_view, null, false)
+                as TaskContentView
+        taskContentView.cornerRadius = CORNER_RADIUS
+        return taskContentView
+    }
+
+    companion object {
+        @Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() =
+            DeviceEmulationSpec.forDisplays(
+                Displays.Phone,
+                isDarkTheme = false,
+                isLandscape = false,
+            )
+
+        const val CORNER_RADIUS = 56f
+    }
+}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskHeaderViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskHeaderViewScreenshotTest.kt
new file mode 100644
index 0000000..e30554e
--- /dev/null
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskHeaderViewScreenshotTest.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.thumbnail
+
+import android.content.Context
+import android.graphics.drawable.BitmapDrawable
+import android.view.LayoutInflater
+import com.android.launcher3.R
+import com.android.quickstep.task.thumbnail.SplashHelper.createSplash
+import com.android.quickstep.views.TaskHeaderView
+import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.Displays
+import platform.test.screenshot.ViewScreenshotTestRule
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+/** Screenshot tests for [TaskHeaderView]. */
+@RunWith(ParameterizedAndroidJunit4::class)
+class TaskHeaderViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    @get:Rule
+    val screenshotRule =
+        ViewScreenshotTestRule(
+            emulationSpec,
+            ViewScreenshotGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)),
+        )
+
+    @Test
+    fun taskHeaderView_showHeader() {
+        screenshotRule.screenshotTest("taskHeaderView_showHeader") { activity ->
+            activity.actionBar?.hide()
+            createTaskHeaderView(activity).apply {
+                setState(
+                    TaskHeaderUiState.ShowHeader(
+                        TaskHeaderUiState.ThumbnailHeader(
+                            BitmapDrawable(activity.resources, createSplash()),
+                            "Example",
+                        ) {}
+                    )
+                )
+            }
+        }
+    }
+
+    private fun createTaskHeaderView(context: Context): TaskHeaderView {
+        val taskHeaderView =
+            LayoutInflater.from(context).inflate(R.layout.task_header_view, null, false)
+                as TaskHeaderView
+        return taskHeaderView
+    }
+
+    companion object {
+        @Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() =
+            DeviceEmulationSpec.forDisplays(
+                Displays.Tablet,
+                isDarkTheme = false,
+                isLandscape = true,
+            )
+    }
+}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
index 49fe614..45df735 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
@@ -17,10 +17,17 @@
 
 import android.content.Context
 import android.graphics.Color
+import android.graphics.Matrix
+import android.graphics.drawable.BitmapDrawable
 import android.view.LayoutInflater
+import android.view.Surface.ROTATION_0
 import com.android.launcher3.R
-import com.android.quickstep.recents.di.RecentsDependencies
-import com.android.quickstep.task.viewmodel.TaskThumbnailViewModel
+import com.android.quickstep.task.thumbnail.SplashHelper.createBitmap
+import com.android.quickstep.task.thumbnail.SplashHelper.createSplash
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
 import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager
 import org.junit.Rule
 import org.junit.Test
@@ -43,10 +50,8 @@
             ViewScreenshotGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)),
         )
 
-    private val taskThumbnailViewModel = FakeTaskThumbnailViewModel()
-
     @Test
-    fun taskThumbnailView_uninitialized() {
+    fun taskThumbnailView_uninitializedByDefault() {
         screenshotRule.screenshotTest("taskThumbnailView_uninitialized") { activity ->
             activity.actionBar?.hide()
             createTaskThumbnailView(activity)
@@ -54,24 +59,180 @@
     }
 
     @Test
+    fun taskThumbnailView_resetsToUninitialized() {
+        screenshotRule.screenshotTest("taskThumbnailView_uninitialized") { activity ->
+            activity.actionBar?.hide()
+            val taskThumbnailView = createTaskThumbnailView(activity)
+            taskThumbnailView.setState(Uninitialized)
+            taskThumbnailView
+        }
+    }
+
+    @Test
+    fun taskThumbnailView_recyclesToUninitialized() {
+        screenshotRule.screenshotTest("taskThumbnailView_uninitialized") { activity ->
+            activity.actionBar?.hide()
+            val taskThumbnailView = createTaskThumbnailView(activity)
+            taskThumbnailView.setState(BackgroundOnly(Color.YELLOW))
+            taskThumbnailView.onRecycle()
+            taskThumbnailView
+        }
+    }
+
+    @Test
     fun taskThumbnailView_backgroundOnly() {
         screenshotRule.screenshotTest("taskThumbnailView_backgroundOnly") { activity ->
             activity.actionBar?.hide()
-            taskThumbnailViewModel.uiState.value = TaskThumbnailUiState.BackgroundOnly(Color.YELLOW)
-            createTaskThumbnailView(activity)
+            createTaskThumbnailView(activity).apply { setState(BackgroundOnly(Color.YELLOW)) }
+        }
+    }
+
+    @Test
+    fun taskThumbnailView_liveTile() {
+        screenshotRule.screenshotTest("taskThumbnailView_liveTile") { activity ->
+            activity.actionBar?.hide()
+            createTaskThumbnailView(activity).apply { setState(TaskThumbnailUiState.LiveTile) }
+        }
+    }
+
+    @Test
+    fun taskThumbnailView_image() {
+        screenshotRule.screenshotTest("taskThumbnailView_image") { activity ->
+            activity.actionBar?.hide()
+            createTaskThumbnailView(activity).apply {
+                setState(
+                    SnapshotSplash(
+                        Snapshot(
+                            createBitmap(VIEW_ENV_WIDTH, VIEW_ENV_HEIGHT),
+                            ROTATION_0,
+                            Color.DKGRAY,
+                        ),
+                        null,
+                    )
+                )
+            }
+        }
+    }
+
+    @Test
+    fun taskThumbnailView_image_withImageMatrix() {
+        screenshotRule.screenshotTest("taskThumbnailView_image_withMatrix") { activity ->
+            activity.actionBar?.hide()
+            createTaskThumbnailView(activity).apply {
+                val lessThanHeightMatchingAspectRatio = (VIEW_ENV_HEIGHT / 2) - 200
+                setState(
+                    SnapshotSplash(
+                        Snapshot(
+                            createBitmap(
+                                width = VIEW_ENV_WIDTH / 2,
+                                height = lessThanHeightMatchingAspectRatio,
+                            ),
+                            ROTATION_0,
+                            Color.DKGRAY,
+                        ),
+                        null,
+                    )
+                )
+                setImageMatrix(Matrix().apply { postScale(2f, 2f) })
+            }
+        }
+    }
+
+    @Test
+    fun taskThumbnailView_splash() {
+        screenshotRule.screenshotTest("taskThumbnailView_partial_splash") { activity ->
+            activity.actionBar?.hide()
+            createTaskThumbnailView(activity).apply {
+                setState(
+                    SnapshotSplash(
+                        Snapshot(
+                            createBitmap(VIEW_ENV_WIDTH, VIEW_ENV_HEIGHT),
+                            ROTATION_0,
+                            Color.DKGRAY,
+                        ),
+                        BitmapDrawable(activity.resources, createSplash()),
+                    )
+                )
+                updateSplashAlpha(0.5f)
+            }
+        }
+    }
+
+    @Test
+    fun taskThumbnailView_splash_withImageMatrix() {
+        screenshotRule.screenshotTest("taskThumbnailView_partial_splash_withMatrix") { activity ->
+            activity.actionBar?.hide()
+            createTaskThumbnailView(activity).apply {
+                val lessThanHeightMatchingAspectRatio = (VIEW_ENV_HEIGHT / 2) - 200
+                setState(
+                    SnapshotSplash(
+                        Snapshot(
+                            createBitmap(
+                                width = VIEW_ENV_WIDTH / 2,
+                                height = lessThanHeightMatchingAspectRatio,
+                            ),
+                            ROTATION_0,
+                            Color.DKGRAY,
+                        ),
+                        BitmapDrawable(activity.resources, createSplash()),
+                    )
+                )
+                setImageMatrix(Matrix().apply { postScale(2f, 2f) })
+                updateSplashAlpha(0.5f)
+            }
+        }
+    }
+
+    @Test
+    fun taskThumbnailView_dimmed_tintAmount() {
+        screenshotRule.screenshotTest("taskThumbnailView_dimmed_40") { activity ->
+            activity.actionBar?.hide()
+            createTaskThumbnailView(activity).apply {
+                setState(BackgroundOnly(Color.YELLOW))
+                updateTintAmount(.4f)
+            }
+        }
+    }
+
+    @Test
+    fun taskThumbnailView_dimmed_menuOpen() {
+        screenshotRule.screenshotTest("taskThumbnailView_dimmed_40") { activity ->
+            activity.actionBar?.hide()
+            createTaskThumbnailView(activity).apply {
+                setState(BackgroundOnly(Color.YELLOW))
+                updateMenuOpenProgress(1f)
+            }
+        }
+    }
+
+    @Test
+    fun taskThumbnailView_dimmed_tintAmountAndMenuOpen() {
+        screenshotRule.screenshotTest("taskThumbnailView_dimmed_80") { activity ->
+            activity.actionBar?.hide()
+            createTaskThumbnailView(activity).apply {
+                setState(BackgroundOnly(Color.YELLOW))
+                updateTintAmount(.8f)
+                updateMenuOpenProgress(1f)
+            }
+        }
+    }
+
+    @Test
+    fun taskThumbnailView_scaled_roundRoundedCorners() {
+        screenshotRule.screenshotTest("taskThumbnailView_scaledRoundedCorners") { activity ->
+            activity.actionBar?.hide()
+            createTaskThumbnailView(activity).apply {
+                scaleX = 0.75f
+                scaleY = 0.3f
+                setState(BackgroundOnly(Color.YELLOW))
+            }
         }
     }
 
     private fun createTaskThumbnailView(context: Context): TaskThumbnailView {
-        val di = RecentsDependencies.initialize(context)
         val taskThumbnailView =
             LayoutInflater.from(context).inflate(R.layout.task_thumbnail, null, false)
                 as TaskThumbnailView
-        taskThumbnailView.cornerRadius = CORNER_RADIUS
-        val ttvDiScopeId = di.getScope(taskThumbnailView).scopeId
-        di.provide(TaskThumbnailViewData::class.java, ttvDiScopeId) { TaskThumbnailViewData() }
-        di.provide(TaskThumbnailViewModel::class.java, ttvDiScopeId) { taskThumbnailViewModel }
-
         return taskThumbnailView
     }
 
@@ -85,6 +246,7 @@
                 isLandscape = false,
             )
 
-        const val CORNER_RADIUS = 56f
+        const val VIEW_ENV_WIDTH = 1440
+        const val VIEW_ENV_HEIGHT = 3120
     }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchTransitionManagerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManagerTest.kt
similarity index 80%
rename from quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchTransitionManagerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManagerTest.kt
index ae96c09c..7ebef45 100644
--- a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchTransitionManagerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/desktop/DesktopAppLaunchTransitionManagerTest.kt
@@ -27,13 +27,10 @@
 import android.window.TransitionFilter
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.quickstep.SystemUiProxy
-import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS
 import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.google.common.truth.Truth.assertThat
-import org.junit.After
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -44,7 +41,6 @@
 import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
-import org.mockito.quality.Strictness
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -52,12 +48,6 @@
 
     @get:Rule val mSetFlagsRule = SetFlagsRule()
 
-    private val mockitoSession =
-        mockitoSession()
-            .strictness(Strictness.LENIENT)
-            .mockStatic(DesktopModeStatus::class.java)
-            .startMocking()
-
     private val context = mock<Context>()
     private val systemUiProxy = mock<SystemUiProxy>()
     private lateinit var transitionManager: DesktopAppLaunchTransitionManager
@@ -69,16 +59,8 @@
         transitionManager = DesktopAppLaunchTransitionManager(context, systemUiProxy)
     }
 
-    @After
-    fun tearDown() {
-        mockitoSession.finishMocking()
-    }
-
     @Test
-    @EnableFlags(
-        FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
-        FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX,
-    )
+    @EnableFlags(FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
     fun registerTransitions_appLaunchFlagEnabled_registersTransition() {
         transitionManager.registerTransitions()
 
@@ -86,10 +68,7 @@
     }
 
     @Test
-    @DisableFlags(
-        FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
-        FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX,
-    )
+    @DisableFlags(FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
     fun registerTransitions_appLaunchFlagDisabled_doesntRegisterTransition() {
         transitionManager.registerTransitions()
 
@@ -97,10 +76,7 @@
     }
 
     @Test
-    @EnableFlags(
-        FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS,
-        FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX,
-    )
+    @EnableFlags(FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX)
     fun registerTransitions_usesCorrectFilter() {
         transitionManager.registerTransitions()
         val filterArgumentCaptor = argumentCaptor<TransitionFilter>()
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
index d4dd580..91f9e53 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/AppEventProducerTest.java
@@ -35,10 +35,13 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.AllModulesForTest;
+import com.android.launcher3.util.SandboxContext;
 import com.android.launcher3.util.UserIconInfo;
 import com.android.systemui.shared.system.SysUiStatsLog;
 
@@ -51,6 +54,9 @@
 
 import java.util.Arrays;
 
+import dagger.BindsInstance;
+import dagger.Component;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class AppEventProducerTest {
@@ -72,7 +78,9 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = new SandboxContext(getApplicationContext());
-        mContext.putObject(UserCache.INSTANCE, mUserCache);
+        mContext.initDaggerComponent(
+                DaggerAppEventProducerTest_TestComponent.builder().bindUserCache(mUserCache)
+        );
         mAppEventProducer = new AppEventProducer(mContext, null);
     }
 
@@ -129,4 +137,15 @@
                 .build());
         return itemBuilder.build();
     }
+
+    @LauncherAppSingleton
+    @Component(modules = { AllModulesForTest.class })
+    interface TestComponent extends LauncherAppComponent {
+        @Component.Builder
+        interface Builder extends LauncherAppComponent.Builder {
+            @BindsInstance
+            AppEventProducerTest.TestComponent.Builder bindUserCache(UserCache userCache);
+            @Override LauncherAppComponent build();
+        }
+    }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt
index 0005df6..09c62aa 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/QuickstepModelDelegateTest.kt
@@ -19,7 +19,6 @@
 import android.app.prediction.AppTarget
 import android.app.prediction.AppTargetEvent
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION
 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION
 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WALLPAPERS
@@ -54,11 +53,17 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         modelHelper = LauncherModelHelper()
-        underTest = QuickstepModelDelegate(modelHelper.sandboxContext)
+        underTest =
+            QuickstepModelDelegate(
+                modelHelper.sandboxContext,
+                modelHelper.sandboxContext.appComponent.idp,
+                modelHelper.sandboxContext.appComponent.packageManagerHelper,
+                "", /* dbFileName */
+            )
         underTest.mAllAppsState.predictor = allAppsPredictor
         underTest.mHotseatState.predictor = hotseatPredictor
         underTest.mWidgetsRecommendationState.predictor = widgetRecommendationPredictor
-        underTest.mApp = LauncherAppState.getInstance(modelHelper.sandboxContext)
+        underTest.mModel = modelHelper.model
         underTest.mDataModel = BgDataModel()
     }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
index 4ea74df..d445189 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/WidgetsPredictionsRequesterTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.model
 
+import android.app.prediction.AppPredictionManager
+import android.app.prediction.AppPredictor
 import android.app.prediction.AppTarget
 import android.app.prediction.AppTargetEvent
 import android.app.prediction.AppTargetId
@@ -36,9 +38,15 @@
 import com.android.launcher3.model.WidgetPredictionsRequester.notOnUiSurfaceFilter
 import com.android.launcher3.util.ActivityContextWrapper
 import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.Executors.MODEL_EXECUTOR
+import com.android.launcher3.util.TestUtil
 import com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
+import com.android.launcher3.widget.PendingAddWidgetInfo
 import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
 import java.util.function.Predicate
 import junit.framework.Assert.assertNotNull
 import org.junit.Before
@@ -46,6 +54,9 @@
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.whenever
 
 @RunWith(AndroidJUnit4::class)
 class WidgetsPredictionsRequesterTest {
@@ -67,11 +78,26 @@
 
     @Mock private lateinit var iconCache: IconCache
 
+    @Mock private lateinit var apmMock: AppPredictionManager
+
+    @Mock private lateinit var predictorMock: AppPredictor
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         mUserHandle = myUserHandle()
-        context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+
+        whenever(apmMock.createAppPredictionSession(any())).thenReturn(predictorMock)
+
+        context =
+            object : ActivityContextWrapper(ApplicationProvider.getApplicationContext()) {
+                override fun getSystemService(name: String): Any? {
+                    if (name == "app_prediction") {
+                        return apmMock
+                    }
+                    return super.getSystemService(name)
+                }
+            }
         testInvariantProfile = LauncherAppState.getIDP(context)
         deviceProfile = testInvariantProfile.getDeviceProfile(context).copy(context)
 
@@ -114,22 +140,68 @@
                 buildExpectedAppTargetEvent(
                     /*pkg=*/ APP_1_PACKAGE_NAME,
                     /*providerClassName=*/ APP_1_PROVIDER_A_CLASS_NAME,
-                    /*user=*/ mUserHandle
+                    /*user=*/ mUserHandle,
                 ),
                 buildExpectedAppTargetEvent(
                     /*pkg=*/ APP_1_PACKAGE_NAME,
                     /*providerClassName=*/ APP_1_PROVIDER_B_CLASS_NAME,
-                    /*user=*/ mUserHandle
+                    /*user=*/ mUserHandle,
                 ),
                 buildExpectedAppTargetEvent(
                     /*pkg=*/ APP_2_PACKAGE_NAME,
                     /*providerClassName=*/ APP_2_PROVIDER_1_CLASS_NAME,
-                    /*user=*/ mUserHandle
-                )
+                    /*user=*/ mUserHandle,
+                ),
             )
     }
 
     @Test
+    fun request_invokesCallbackWithPredictedItems() {
+        TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
+            val underTest = WidgetPredictionsRequester(context, TEST_UI_SURFACE, allWidgets)
+            val existingWidgets = arrayListOf(widget1aInfo, widget1bInfo)
+            val predictions =
+                listOf(
+                    // (existing) already on surface
+                    AppTarget(
+                        AppTargetId(APP_1_PACKAGE_NAME),
+                        APP_1_PACKAGE_NAME,
+                        APP_1_PROVIDER_B_CLASS_NAME,
+                        mUserHandle,
+                    ),
+                    // eligible
+                    AppTarget(
+                        AppTargetId(APP_2_PACKAGE_NAME),
+                        APP_2_PACKAGE_NAME,
+                        APP_2_PROVIDER_1_CLASS_NAME,
+                        mUserHandle,
+                    ),
+                )
+            doAnswer {
+                    underTest.onTargetsAvailable(predictions)
+                    null
+                }
+                .whenever(predictorMock)
+                .requestPredictionUpdate()
+            val testCountDownLatch = CountDownLatch(1)
+            val listener =
+                WidgetPredictionsRequester.WidgetPredictionsListener { itemInfos ->
+                    if (itemInfos.size == 1 && itemInfos[0] is PendingAddWidgetInfo) {
+                        // only one item was eligible.
+                        testCountDownLatch.countDown()
+                    } else {
+                        println("Unexpected prediction items found: ${itemInfos.size}")
+                    }
+                }
+
+            underTest.request(existingWidgets, listener)
+            TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) {}
+
+            assertThat(testCountDownLatch.await(TEST_TIMEOUT, TimeUnit.SECONDS)).isTrue()
+        }
+    }
+
+    @Test
     fun filterPredictions_notOnUiSurfaceFilter_returnsOnlyEligiblePredictions() {
         val widgetsAlreadyOnSurface = arrayListOf(widget1bInfo)
         val filter: Predicate<WidgetItem> = notOnUiSurfaceFilter(widgetsAlreadyOnSurface)
@@ -141,15 +213,15 @@
                     AppTargetId(APP_1_PACKAGE_NAME),
                     APP_1_PACKAGE_NAME,
                     APP_1_PROVIDER_B_CLASS_NAME,
-                    mUserHandle
+                    mUserHandle,
                 ),
                 // eligible
                 AppTarget(
                     AppTargetId(APP_2_PACKAGE_NAME),
                     APP_2_PACKAGE_NAME,
                     APP_2_PROVIDER_1_CLASS_NAME,
-                    mUserHandle
-                )
+                    mUserHandle,
+                ),
             )
 
         // only 2 was eligible
@@ -167,27 +239,27 @@
                     AppTargetId(APP_1_PACKAGE_NAME),
                     APP_1_PACKAGE_NAME,
                     "$APP_1_PACKAGE_NAME.SomeActivity",
-                    mUserHandle
+                    mUserHandle,
                 ),
                 AppTarget(
                     AppTargetId(APP_2_PACKAGE_NAME),
                     APP_2_PACKAGE_NAME,
                     "$APP_2_PACKAGE_NAME.SomeActivity2",
-                    mUserHandle
+                    mUserHandle,
                 ),
             )
 
         assertThat(filterPredictions(predictions, allWidgets, filter)).isEmpty()
     }
 
-    private fun createWidgetItem(
-        providerInfo: AppWidgetProviderInfo,
-    ): WidgetItem {
+    private fun createWidgetItem(providerInfo: AppWidgetProviderInfo): WidgetItem {
         val widgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(context, providerInfo)
         return WidgetItem(widgetInfo, testInvariantProfile, iconCache, context)
     }
 
     companion object {
+        const val TEST_TIMEOUT = 3L
+
         const val TEST_UI_SURFACE = "widgets_test"
         const val BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets"
 
@@ -203,13 +275,13 @@
         private fun buildExpectedAppTargetEvent(
             pkg: String,
             providerClassName: String,
-            userHandle: UserHandle
+            userHandle: UserHandle,
         ): AppTargetEvent {
             val appTarget =
                 AppTarget.Builder(
                         /*id=*/ AppTargetId("widget:$pkg"),
                         /*packageName=*/ pkg,
-                        /*user=*/ userHandle
+                        /*user=*/ userHandle,
                     )
                     .setClassName(providerClassName)
                     .build()
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
index 5cee434..d2abed8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
@@ -18,19 +18,24 @@
 
 import android.content.ComponentName
 import android.content.Intent
+import android.os.Process
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.Flags.enableRefactorTaskThumbnail
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_NOT_PINNABLE
 import com.android.launcher3.model.data.TaskViewItemInfo.Companion.createTaskViewAtom
 import com.android.launcher3.pm.UserCache
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.android.launcher3.util.AllModulesForTest
+import com.android.launcher3.util.SandboxContext
 import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.launcher3.util.TransformingTouchDelegate
 import com.android.launcher3.util.UserIconInfo
 import com.android.quickstep.TaskOverlayFactory
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay
 import com.android.quickstep.recents.di.RecentsDependencies
+import com.android.quickstep.task.thumbnail.TaskContentView
 import com.android.quickstep.task.thumbnail.TaskThumbnailView
 import com.android.quickstep.views.RecentsView
 import com.android.quickstep.views.TaskContainer
@@ -41,6 +46,9 @@
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.Task.TaskKey
 import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -66,8 +74,15 @@
         whenever(recentsView.indexOfChild(taskView)).thenReturn(TASK_VIEW_INDEX)
         whenever(userInfo.isPrivate).thenReturn(false)
         whenever(userCache.getUserInfo(any())).thenReturn(userInfo)
-        context.putObject(UserCache.INSTANCE, userCache)
-        RecentsDependencies.initialize(context)
+        context.initDaggerComponent(
+            DaggerTaskViewItemInfoTest_TestComponent.builder().bindUserCache(userCache)
+        )
+        RecentsDependencies.maybeInitialize(context)
+    }
+
+    @After
+    fun tearDown() {
+        RecentsDependencies.destroy(context)
     }
 
     @Test
@@ -76,7 +91,7 @@
         whenever(taskView.type).thenReturn(TaskViewType.SINGLE)
         whenever(taskView.taskContainers).thenReturn(taskContainers)
 
-        val taskViewItemInfo = TaskViewItemInfo(taskContainers[0])
+        val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0])
 
         assertThat(taskViewItemInfo.taskViewAtom)
             .isEqualTo(
@@ -97,7 +112,7 @@
         whenever(taskView.type).thenReturn(TaskViewType.GROUPED)
         whenever(taskView.taskContainers).thenReturn(taskContainers)
 
-        val taskViewItemInfo = TaskViewItemInfo(taskContainers[0])
+        val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0])
 
         assertThat(taskViewItemInfo.taskViewAtom)
             .isEqualTo(
@@ -122,7 +137,7 @@
         whenever(taskView.type).thenReturn(TaskViewType.DESKTOP)
         whenever(taskView.taskContainers).thenReturn(taskContainers)
 
-        val taskViewItemInfo = TaskViewItemInfo(taskContainers[0])
+        val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0])
 
         assertThat(taskViewItemInfo.taskViewAtom)
             .isEqualTo(
@@ -143,7 +158,7 @@
         whenever(taskView.taskContainers).thenReturn(taskContainers)
         whenever(userInfo.isPrivate).thenReturn(true)
 
-        val taskViewItemInfo = TaskViewItemInfo(taskContainers[0])
+        val taskViewItemInfo = TaskViewItemInfo(taskContainers[0].taskView, taskContainers[0])
 
         assertThat(taskViewItemInfo.taskViewAtom)
             .isEqualTo(
@@ -158,6 +173,25 @@
             .isEqualTo(FLAG_NOT_PINNABLE)
     }
 
+    @Test
+    fun emptyDesktopTask() {
+        whenever(taskView.type).thenReturn(TaskViewType.DESKTOP)
+
+        val taskViewItemInfo = TaskViewItemInfo(taskView = taskView, taskContainer = null)
+
+        assertThat(taskViewItemInfo.taskViewAtom)
+            .isEqualTo(
+                createTaskViewAtom(
+                    type = 2,
+                    index = TASK_VIEW_INDEX,
+                    componentName = "",
+                    cardinality = 0,
+                )
+            )
+        assertThat(taskViewItemInfo.user).isEqualTo(Process.myUserHandle())
+        assertThat(taskViewItemInfo.intent).isNotNull()
+    }
+
     private fun createTask(id: Int) =
         Task(TaskKey(id, 0, Intent(), ComponentName(PACKAGE, CLASS), 0, 2000))
 
@@ -165,6 +199,7 @@
         return TaskContainer(
             taskView,
             task,
+            mock<TaskContentView>(),
             if (enableRefactorTaskThumbnail()) mock<TaskThumbnailView>()
             else mock<TaskThumbnailViewDeprecated>(),
             mock<TaskViewIcon>(),
@@ -176,6 +211,17 @@
         )
     }
 
+    @LauncherAppSingleton
+    @Component(modules = [AllModulesForTest::class])
+    interface TestComponent : LauncherAppComponent {
+        @Component.Builder
+        interface Builder : LauncherAppComponent.Builder {
+            @BindsInstance fun bindUserCache(userCache: UserCache): Builder
+
+            override fun build(): TestComponent
+        }
+    }
+
     companion object {
         const val PACKAGE = "package"
         const val CLASS = "class"
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
similarity index 98%
rename from quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
index df98606..b39c3f1 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/FallbackTaskbarUIControllerTest.kt
@@ -17,7 +17,7 @@
 
 package com.android.launcher3.taskbar
 
-import androidx.test.runner.AndroidJUnit4
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.launcher3.statemanager.StateManager
 import com.android.quickstep.RecentsActivity
 import com.android.quickstep.fallback.RecentsState
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
index cfa12e2..50d6aff 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarAutohideSuspendControllerTest.kt
@@ -17,10 +17,12 @@
 package com.android.launcher3.taskbar
 
 import android.animation.AnimatorTestRule
+import androidx.test.core.app.ApplicationProvider
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING
 import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER
 import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TOUCHING
+import com.android.launcher3.taskbar.rules.SandboxParams
 import com.android.launcher3.taskbar.rules.TaskbarModeRule
 import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT
 import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
@@ -34,6 +36,10 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
 
 @RunWith(LauncherMultivalentJUnit::class)
 @EmulatedDevices(["pixelTablet2023"])
@@ -41,16 +47,15 @@
 
     @get:Rule(order = 0)
     val context =
-        TaskbarWindowSandboxContext.create { builder ->
-            builder.bindSystemUiProxy(
-                object : SystemUiProxy(this) {
-                    override fun notifyTaskbarAutohideSuspend(suspend: Boolean) {
-                        super.notifyTaskbarAutohideSuspend(suspend)
-                        latestSuspendNotification = suspend
-                    }
+        TaskbarWindowSandboxContext.create(
+            SandboxParams({
+                spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) { proxy ->
+                    doAnswer { latestSuspendNotification = it.getArgument(0) }
+                        .whenever(proxy)
+                        .notifyTaskbarAutohideSuspend(anyOrNull())
                 }
-            )
-        }
+            })
+        )
     @get:Rule(order = 1) val animatorTestRule = AnimatorTestRule(this)
     @get:Rule(order = 2) val taskbarModeRule = TaskbarModeRule(context)
     @get:Rule(order = 3) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
similarity index 98%
rename from quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
index d064f4a..26f1197 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarBaseTestCase.kt
@@ -99,7 +99,7 @@
                 keyboardQuickSwitchController,
                 taskbarPinningController,
                 optionalBubbleControllers,
-                taskbarDesktopModeController
+                taskbarDesktopModeController,
             )
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarControllerTestUtil.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarControllerTestUtil.kt
index 6e2f74a..0e066cd 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarControllerTestUtil.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarControllerTestUtil.kt
@@ -17,15 +17,16 @@
 package com.android.launcher3.taskbar
 
 import android.content.Context
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import com.android.launcher3.ConstantItem
 import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.TestUtil
 import kotlin.properties.ReadWriteProperty
 import kotlin.reflect.KProperty
 
 object TaskbarControllerTestUtil {
     inline fun runOnMainSync(crossinline runTest: () -> Unit) {
-        getInstrumentation().runOnMainSync { runTest() }
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) { runTest() }
     }
 
     /** Returns a property to read/write the value of a [ConstantItem]. */
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
similarity index 97%
rename from quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
index e619e7c..2431020 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarKeyguardControllerTest.kt
@@ -16,6 +16,7 @@
 package com.android.launcher3.taskbar
 
 import android.app.KeyguardManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BACK_DISABLED
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING
@@ -23,6 +24,7 @@
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.kotlin.any
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.never
@@ -30,6 +32,7 @@
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
+@RunWith(AndroidJUnit4::class)
 class TaskbarKeyguardControllerTest : TaskbarBaseTestCase() {
 
     private val baseDragLayer: TaskbarDragLayer = mock()
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
index c682990..a8f3500 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarNavButtonControllerTest.java
@@ -40,7 +40,6 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks;
 import com.android.quickstep.SystemUiProxy;
@@ -65,9 +64,6 @@
     SystemUiProxy mockSystemUiProxy;
 
     @Mock
-    ContextualEduStatsManager mockContextualEduStatsManager;
-
-    @Mock
     TouchInteractionService mockService;
     @Mock
     Handler mockHandler;
@@ -118,7 +114,6 @@
                 mockService,
                 mCallbacks,
                 mockSystemUiProxy,
-                mockContextualEduStatsManager,
                 mockHandler,
                 mockContextualSearchInvoker);
     }
@@ -132,8 +127,8 @@
     @Test
     public void testPressBack_updateContextualEduData() {
         mNavButtonController.onButtonClick(BUTTON_BACK, mockView);
-        verify(mockContextualEduStatsManager, times(1))
-                .updateEduStats(/* isTrackpad= */ eq(false), eq(GestureType.BACK));
+        verify(mockSystemUiProxy, times(1))
+                .updateContextualEduStats(/* isTrackpad= */ eq(false), eq(GestureType.BACK));
     }
 
     @Test
@@ -223,8 +218,8 @@
     @Test
     public void testPressHome_updateContextualEduData() {
         mNavButtonController.onButtonClick(BUTTON_HOME, mockView);
-        verify(mockContextualEduStatsManager, times(1))
-                .updateEduStats(/* isTrackpad= */ eq(false), eq(GestureType.HOME));
+        verify(mockSystemUiProxy, times(1))
+                .updateContextualEduStats(/* isTrackpad= */ eq(false), eq(GestureType.HOME));
     }
 
     @Test
@@ -236,8 +231,8 @@
     @Test
     public void testPressRecents_updateContextualEduData() {
         mNavButtonController.onButtonClick(BUTTON_RECENTS, mockView);
-        verify(mockContextualEduStatsManager, times(1))
-                .updateEduStats(/* isTrackpad= */ eq(false), eq(GestureType.OVERVIEW));
+        verify(mockSystemUiProxy, times(1))
+                .updateContextualEduStats(/* isTrackpad= */ eq(false), eq(GestureType.OVERVIEW));
     }
 
     @Test
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
index 011ba7e..3761044 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarOverflowTest.kt
@@ -19,23 +19,38 @@
 import android.animation.AnimatorTestRule
 import android.content.ComponentName
 import android.content.Intent
+import android.os.Process
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.core.app.ApplicationProvider
+import com.android.launcher3.Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR
 import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW
+import com.android.launcher3.R
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
+import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems
 import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
+import com.android.launcher3.taskbar.rules.DisplayControllerModule
+import com.android.launcher3.taskbar.rules.MockedRecentsModelHelper
 import com.android.launcher3.taskbar.rules.MockedRecentsModelTestRule
+import com.android.launcher3.taskbar.rules.SandboxParams
 import com.android.launcher3.taskbar.rules.TaskbarModeRule
 import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED
 import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT
 import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
+import com.android.launcher3.taskbar.rules.TaskbarSandboxComponent
 import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
 import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
 import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
+import com.android.launcher3.util.AllModulesForTest
+import com.android.launcher3.util.FakePrefsModule
 import com.android.launcher3.util.LauncherMultivalentJUnit
 import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
 import com.android.launcher3.util.TestUtil.getOnUiThread
+import com.android.quickstep.RecentsModel
 import com.android.quickstep.SystemUiProxy
 import com.android.quickstep.util.DesktopTask
 import com.android.systemui.shared.recents.model.Task
@@ -44,10 +59,16 @@
 import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR
 import com.android.wm.shell.desktopmode.IDesktopTaskListener
 import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
 
 @RunWith(LauncherMultivalentJUnit::class)
 @EmulatedDevices(["pixelTablet2023"])
@@ -57,48 +78,68 @@
     FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
     FLAG_ENABLE_BUBBLE_BAR,
 )
+@DisableFlags(FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR)
 class TaskbarOverflowTest {
     @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
 
+    val mockRecentsModelHelper: MockedRecentsModelHelper = MockedRecentsModelHelper()
+
     @get:Rule(order = 1)
     val context =
-        TaskbarWindowSandboxContext.create { builder ->
-            builder.bindSystemUiProxy(
-                object : SystemUiProxy(this) {
-                    override fun setDesktopTaskListener(listener: IDesktopTaskListener?) {
-                        desktopTaskListener = listener
+        TaskbarWindowSandboxContext.create(
+            SandboxParams(
+                {
+                    spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) { proxy ->
+                        doAnswer { desktopTaskListener = it.getArgument(0) }
+                            .whenever(proxy)
+                            .setDesktopTaskListener(anyOrNull())
                     }
-                }
+                },
+                DaggerTaskbarOverflowComponent.builder()
+                    .bindRecentsModel(mockRecentsModelHelper.mockRecentsModel),
             )
-        }
+        )
 
-    @get:Rule(order = 2) val recentsModel = MockedRecentsModelTestRule(context)
+    @get:Rule(order = 2) val recentsModel = MockedRecentsModelTestRule(mockRecentsModelHelper)
 
     @get:Rule(order = 3) val taskbarModeRule = TaskbarModeRule(context)
 
     @get:Rule(order = 4) val animatorTestRule = AnimatorTestRule(this)
 
-    @get:Rule(order = 5) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+    @get:Rule(order = 5)
+    val taskbarUnitTestRule = TaskbarUnitTestRule(this, context, this::onControllersInitialized)
 
     @InjectController lateinit var taskbarViewController: TaskbarViewController
     @InjectController lateinit var recentAppsController: TaskbarRecentAppsController
     @InjectController lateinit var bubbleBarViewController: BubbleBarViewController
     @InjectController lateinit var bubbleStashController: BubbleStashController
+    @InjectController lateinit var keyboardQuickSwitchController: KeyboardQuickSwitchController
 
     private var desktopTaskListener: IDesktopTaskListener? = null
 
-    @Before
-    fun ensureRunningAppsShowing() {
+    private var currentControllerInitCallback: () -> Unit = {}
+        set(value) {
+            runOnMainSync { value.invoke() }
+            field = value
+        }
+
+    private fun onControllersInitialized() {
         runOnMainSync {
             if (!recentAppsController.canShowRunningApps) {
                 recentAppsController.onDestroy()
                 recentAppsController.canShowRunningApps = true
                 recentAppsController.init(taskbarUnitTestRule.activityContext.controllers)
             }
-            recentsModel.resolvePendingTaskRequests()
+
+            currentControllerInitCallback.invoke()
         }
     }
 
+    @Before
+    fun ensureRunningAppsShowing() {
+        runOnMainSync { recentsModel.resolvePendingTaskRequests() }
+    }
+
     @Test
     @TaskbarMode(PINNED)
     fun testTaskbarWithMaxNumIcons_pinned() {
@@ -137,11 +178,61 @@
 
     @Test
     @TaskbarMode(PINNED)
+    fun testOverflownTaskbarWithNoSpaceForRecentApps_pinned() {
+        val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+
+        // Create two "recent" desktop tasks, and then add enough hotseat items so the taskbar
+        // reaches max number of items with hotseat item icons, all apps and divider icons only.
+        // I.e. so all desktop tasks are in taskbar overflow.
+        createDesktopTask(2)
+        runOnMainSync {
+            val taskbarView: TaskbarView =
+                taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+            taskbarView.updateItems(
+                createHotseatItems(maxNumberOfTaskbarIcons - initialIconCount),
+                recentAppsController.shownTasks,
+            )
+        }
+
+        // Verify that taskbar overflow view is shown (eventhough it exceeds max taskbar icons).
+        assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumberOfTaskbarIcons + 1)
+        assertThat(taskbarOverflowIconIndex).isEqualTo(maxNumberOfTaskbarIcons)
+        assertThat(overflowItems).containsExactlyElementsIn(0..1)
+    }
+
+    @Test
+    @TaskbarMode(PINNED)
+    fun testOverflownTaskbarWithNoSpaceForRecentApps_singleRecent_pinned() {
+        val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+
+        // Create a "recent" desktop task, and then add enough hotseat items so the taskbar
+        // reaches max number of items with hotseat item icons, all apps and divider icons only.
+        // I.e. so the single desktop tasks is in taskbar overflow.
+        createDesktopTask(1)
+        runOnMainSync {
+            val taskbarView: TaskbarView =
+                taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+            val hotseatItems = createHotseatItems(maxNumberOfTaskbarIcons - initialIconCount)
+
+            taskbarView.updateItems(
+                recentAppsController.updateHotseatItemInfos(hotseatItems as Array<ItemInfo?>),
+                recentAppsController.shownTasks,
+            )
+        }
+
+        // Verify that recent task is shown (eventhough it exceeds max taskbar icons), and that
+        // the taskbar overflow view is not added for the single recent app.
+        assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumberOfTaskbarIcons + 1)
+        assertThat(taskbarOverflowIconIndex).isEqualTo(-1)
+    }
+
+    @Test
+    @TaskbarMode(PINNED)
     fun testBubbleBarReducesTaskbarMaxNumIcons_pinned() {
         var initialMaxNumIconViews = maxNumberOfTaskbarIcons
         assertThat(initialMaxNumIconViews).isGreaterThan(0)
 
-        runOnMainSync { bubbleBarViewController.setHiddenForBubbles(false) }
+        currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(false) }
 
         val maxNumIconViews = addRunningAppsAndVerifyOverflowState(2)
         assertThat(maxNumIconViews).isLessThan(initialMaxNumIconViews)
@@ -155,7 +246,7 @@
         var initialMaxNumIconViews = maxNumberOfTaskbarIcons
         assertThat(initialMaxNumIconViews).isGreaterThan(0)
 
-        runOnMainSync { bubbleBarViewController.setHiddenForBubbles(false) }
+        currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(false) }
 
         val maxNumIconViews = addRunningAppsAndVerifyOverflowState(2)
         assertThat(maxNumIconViews).isLessThan(initialMaxNumIconViews)
@@ -173,7 +264,7 @@
     fun testBubbleBarReducesTaskbarMaxNumIcons_transientBubbleInitiallyStashed() {
         var initialMaxNumIconViews = maxNumberOfTaskbarIcons
         assertThat(initialMaxNumIconViews).isGreaterThan(0)
-        runOnMainSync {
+        currentControllerInitCallback = {
             bubbleStashController.stashBubbleBarImmediate()
             bubbleBarViewController.setHiddenForBubbles(false)
         }
@@ -192,7 +283,7 @@
     @Test
     @TaskbarMode(TRANSIENT)
     fun testStashingBubbleBarMaintainsMaxNumIcons_transient() {
-        runOnMainSync { bubbleBarViewController.setHiddenForBubbles(false) }
+        currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(false) }
 
         val initialNumIcons = currentNumberOfTaskbarIcons
         val maxNumIconViews = addRunningAppsAndVerifyOverflowState(2)
@@ -206,15 +297,13 @@
     @Test
     @TaskbarMode(PINNED)
     fun testHidingBubbleBarIncreasesMaxNumIcons_pinned() {
-        runOnMainSync { bubbleBarViewController.setHiddenForBubbles(false) }
+        currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(false) }
 
         val initialNumIcons = currentNumberOfTaskbarIcons
         val initialMaxNumIconViews = addRunningAppsAndVerifyOverflowState(5)
 
-        runOnMainSync {
-            bubbleBarViewController.setHiddenForBubbles(true)
-            animatorTestRule.advanceTimeBy(150)
-        }
+        currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(true) }
+        runOnMainSync { animatorTestRule.advanceTimeBy(150) }
 
         val maxNumIconViews = maxNumberOfTaskbarIcons
         assertThat(maxNumIconViews).isGreaterThan(initialMaxNumIconViews)
@@ -227,15 +316,13 @@
     @Test
     @TaskbarMode(TRANSIENT)
     fun testHidingBubbleBarIncreasesMaxNumIcons_transient() {
-        runOnMainSync { bubbleBarViewController.setHiddenForBubbles(false) }
+        currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(false) }
 
         val initialNumIcons = currentNumberOfTaskbarIcons
         val initialMaxNumIconViews = addRunningAppsAndVerifyOverflowState(5)
 
-        runOnMainSync {
-            bubbleBarViewController.setHiddenForBubbles(true)
-            animatorTestRule.advanceTimeBy(150)
-        }
+        currentControllerInitCallback = { bubbleBarViewController.setHiddenForBubbles(true) }
+        runOnMainSync { animatorTestRule.advanceTimeBy(150) }
 
         val maxNumIconViews = maxNumberOfTaskbarIcons
         assertThat(maxNumIconViews).isGreaterThan(initialMaxNumIconViews)
@@ -245,16 +332,127 @@
         assertThat(taskbarIconsCentered).isTrue()
     }
 
-    private fun createDesktopTask(tasksToAdd: Int) {
-        val tasks =
-            (0..<tasksToAdd).map {
-                Task(Task.TaskKey(it, 0, Intent(), ComponentName("", ""), 0, 2000))
-            }
-        recentsModel.updateRecentTasks(listOf(DesktopTask(tasks)))
-        desktopTaskListener?.onTasksVisibilityChanged(
-            context.virtualDisplay.display.displayId,
-            tasksToAdd,
+    @Test
+    @TaskbarMode(PINNED)
+    fun testPressingOverflowButtonOpensKeyboardQuickSwitch() {
+        val maxNumIconViews = maxNumberOfTaskbarIcons
+        // Assume there are at least all apps and divider icon, as they would appear once running
+        // apps are added, even if not present initially.
+        val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+
+        val targetOverflowSize = 5
+        val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize
+        createDesktopTask(createdTasks)
+
+        assertThat(taskbarOverflowIconIndex).isEqualTo(initialIconCount)
+        tapOverflowIcon()
+        // Keyboard quick switch view is shown only after list of recent task is asynchronously
+        // retrieved from the recents model.
+        runOnMainSync { recentsModel.resolvePendingTaskRequests() }
+
+        assertThat(getOnUiThread { keyboardQuickSwitchController.isShownFromTaskbar }).isTrue()
+        assertThat(getOnUiThread { keyboardQuickSwitchController.shownTaskIds() })
+            .containsExactlyElementsIn(0..<createdTasks)
+
+        tapOverflowIcon()
+        assertThat(keyboardQuickSwitchController.isShown).isFalse()
+    }
+
+    @Test
+    @TaskbarMode(PINNED)
+    fun testHotseatItemTasksNotShownInRecents() {
+        val maxNumIconViews = maxNumberOfTaskbarIcons
+        // Assume there are at least all apps and divider icon, as they would appear once running
+        // apps are added, even if not present initially.
+        val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+        val hotseatItems = createHotseatItems(1)
+
+        val targetOverflowSize = 5
+        val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize
+        createDesktopTaskWithTasksFromPackages(
+            listOf("fake") +
+                listOf(hotseatItems[0]?.targetPackage ?: "") +
+                List(createdTasks - 2) { "fake" }
         )
+
+        runOnMainSync {
+            val taskbarView: TaskbarView =
+                taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+            taskbarView.updateItems(
+                recentAppsController.updateHotseatItemInfos(hotseatItems as Array<ItemInfo?>),
+                recentAppsController.shownTasks,
+            )
+        }
+
+        assertThat(maxNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
+        assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
+        assertThat(taskbarOverflowIconIndex).isEqualTo(initialIconCount + hotseatItems.size)
+        assertThat(overflowItems)
+            .containsExactlyElementsIn(listOf(0) + (2..targetOverflowSize + 1).toList())
+    }
+
+    @Test
+    @TaskbarMode(PINNED)
+    fun testHotseatItemTasksNotShownInKQS() {
+        val maxNumIconViews = maxNumberOfTaskbarIcons
+        // Assume there are at least all apps and divider icon, as they would appear once running
+        // apps are added, even if not present initially.
+        val initialIconCount = currentNumberOfTaskbarIcons.coerceAtLeast(2)
+        val hotseatItems = createHotseatItems(1)
+
+        val targetOverflowSize = 5
+        val createdTasks = maxNumIconViews - initialIconCount + targetOverflowSize
+        createDesktopTaskWithTasksFromPackages(
+            listOf("fake") +
+                listOf(hotseatItems[0]?.targetPackage ?: "") +
+                List(createdTasks - 2) { "fake" }
+        )
+
+        runOnMainSync {
+            val taskbarView: TaskbarView =
+                taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
+            taskbarView.updateItems(
+                recentAppsController.updateHotseatItemInfos(hotseatItems as Array<ItemInfo?>),
+                recentAppsController.shownTasks,
+            )
+        }
+
+        tapOverflowIcon()
+        // Keyboard quick switch view is shown only after list of recent task is asynchronously
+        // retrieved from the recents model.
+        runOnMainSync { recentsModel.resolvePendingTaskRequests() }
+
+        assertThat(getOnUiThread { keyboardQuickSwitchController.isShownFromTaskbar }).isTrue()
+        assertThat(getOnUiThread { keyboardQuickSwitchController.shownTaskIds() })
+            .containsExactlyElementsIn(listOf(0) + (2..<createdTasks).toList())
+    }
+
+    private fun createDesktopTask(tasksToAdd: Int) {
+        createDesktopTaskWithTasksFromPackages((0..<tasksToAdd).map { "fake" })
+    }
+
+    private fun createDesktopTaskWithTasksFromPackages(packages: List<String>) {
+        val tasks =
+            packages.mapIndexed({ index, p ->
+                Task(
+                    Task.TaskKey(
+                        index,
+                        0,
+                        Intent().apply { `package` = p },
+                        ComponentName(p, ""),
+                        Process.myUserHandle().identifier,
+                        2000,
+                    )
+                )
+            })
+
+        recentsModel.updateRecentTasks(listOf(DesktopTask(deskId = 0, tasks)))
+        for (task in 1..tasks.size) {
+            desktopTaskListener?.onTasksVisibilityChanged(
+                context.virtualDisplay.display.displayId,
+                task,
+            )
+        }
         runOnMainSync { recentsModel.resolvePendingTaskRequests() }
     }
 
@@ -296,6 +494,28 @@
             }
         }
 
+    private val overflowItems: List<Int>
+        get() {
+            return getOnUiThread {
+                val overflowIcon =
+                    taskbarViewController.iconViews.firstOrNull { it is TaskbarOverflowView }
+
+                if (overflowIcon is TaskbarOverflowView) {
+                    overflowIcon.itemIds
+                } else {
+                    emptyList()
+                }
+            }
+        }
+
+    private fun tapOverflowIcon() {
+        runOnMainSync {
+            val overflowIcon =
+                taskbarViewController.iconViews.firstOrNull { it is TaskbarOverflowView }
+            assertThat(overflowIcon?.callOnClick()).isTrue()
+        }
+    }
+
     /**
      * Adds enough running apps for taskbar to enter overflow of `targetOverflowSize`, and verifies
      * * max number of icons in the taskbar remains unchanged
@@ -318,6 +538,24 @@
         assertThat(currentNumberOfTaskbarIcons).isEqualTo(maxNumIconViews)
         assertThat(taskbarOverflowIconIndex)
             .isEqualTo(if (targetOverflowSize > 0) initialIconCount else -1)
+        if (targetOverflowSize > 0) {
+            assertThat(overflowItems).containsExactlyElementsIn(0..targetOverflowSize)
+        }
         return maxNumIconViews
     }
 }
+
+/** TaskbarOverflowComponent used to bind the RecentsModel. */
+@LauncherAppSingleton
+@Component(
+    modules = [AllModulesForTest::class, FakePrefsModule::class, DisplayControllerModule::class]
+)
+interface TaskbarOverflowComponent : TaskbarSandboxComponent {
+
+    @Component.Builder
+    interface Builder : TaskbarSandboxComponent.Builder {
+        @BindsInstance fun bindRecentsModel(model: RecentsModel): Builder
+
+        override fun build(): TaskbarOverflowComponent
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarPopupControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarPopupControllerTest.kt
new file mode 100644
index 0000000..6bb3205
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarPopupControllerTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import com.android.launcher3.AbstractFloatingView
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR
+import com.android.launcher3.Flags.FLAG_ENABLE_PINNING_APP_WITH_CONTEXT_MENU
+import com.android.launcher3.R
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
+import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatWorkspaceItem
+import com.android.launcher3.taskbar.TaskbarViewTestUtil.createRecents
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule
+import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
+import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.android.quickstep.util.GroupTask
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(LauncherMultivalentJUnit::class)
+@EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
+@DisableFlags(FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR, FLAG_ENABLE_PINNING_APP_WITH_CONTEXT_MENU)
+class TaskbarPopupControllerTest {
+    @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
+
+    @get:Rule(order = 1) val context = TaskbarWindowSandboxContext.create()
+
+    @get:Rule(order = 2) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
+
+    @InjectController lateinit var popupController: TaskbarPopupController
+
+    private val taskbarContext: TaskbarActivityContext
+        get() = taskbarUnitTestRule.activityContext
+
+    private lateinit var taskbarView: TaskbarView
+    private lateinit var hotseatIcon: BubbleTextView
+    private lateinit var recentTaskIcon: BubbleTextView
+
+    @Before
+    fun setup() {
+        taskbarContext.controllers.uiController.init(taskbarContext.controllers)
+        runOnMainSync { taskbarView = taskbarContext.dragLayer.findViewById(R.id.taskbar_view) }
+
+        val hotseatItems = arrayOf(createHotseatWorkspaceItem())
+        val recentItems = createRecents(2)
+        runOnMainSync {
+            taskbarView.updateItems(hotseatItems, recentItems)
+            hotseatIcon =
+                taskbarView.iconViews.filterIsInstance<BubbleTextView>().first {
+                    it.tag is WorkspaceItemInfo
+                }
+            recentTaskIcon =
+                taskbarView.iconViews.filterIsInstance<BubbleTextView>().first {
+                    it.tag is GroupTask
+                }
+        }
+    }
+
+    @Test
+    fun showForIcon_hotseatItem() {
+        assertThat(hasPopupMenu()).isFalse()
+        runOnMainSync { popupController.showForIcon(hotseatIcon) }
+        assertThat(hasPopupMenu()).isTrue()
+    }
+
+    @Test
+    fun showForIcon_recentTask() {
+        assertThat(hasPopupMenu()).isFalse()
+        runOnMainSync { popupController.showForIcon(recentTaskIcon) }
+        assertThat(hasPopupMenu()).isTrue()
+    }
+
+    private fun hasPopupMenu(): Boolean {
+        return AbstractFloatingView.hasOpenView(
+            taskbarContext,
+            AbstractFloatingView.TYPE_ACTION_POPUP,
+        )
+    }
+}
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
similarity index 90%
rename from quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
index ed0c928..8376bc1 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarRecentAppsControllerTest.kt
@@ -21,29 +21,38 @@
 import android.content.Context
 import android.content.Intent
 import android.content.res.Resources
+import android.graphics.Rect
 import android.os.Process
 import android.os.UserHandle
-import android.platform.test.rule.TestWatcher
-import android.testing.AndroidTestingRunner
+import android.platform.test.annotations.EnableFlags
+import androidx.test.annotation.UiThreadTest
 import com.android.internal.R
 import com.android.launcher3.BubbleTextView.RunningAppState
+import com.android.launcher3.Flags
 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION
 import com.android.launcher3.model.data.AppInfo
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.TaskItemInfo
 import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.taskbar.TaskbarRecentAppsController.TaskState
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.quickstep.RecentsModel
 import com.android.quickstep.RecentsModel.RecentTasksChangedListener
 import com.android.quickstep.TaskIconCache
 import com.android.quickstep.util.DesktopTask
 import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.SingleTask
+import com.android.quickstep.util.SplitTask
 import com.android.systemui.shared.recents.model.Task
+import com.android.wm.shell.shared.split.SplitScreenConstants
 import com.google.common.truth.Truth.assertThat
 import java.util.function.Consumer
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
+import org.junit.rules.TestWatcher
 import org.junit.runner.Description
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
@@ -56,7 +65,9 @@
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
-@RunWith(AndroidTestingRunner::class)
+@UiThreadTest
+@RunWith(LauncherMultivalentJUnit::class)
+@EnableFlags(Flags.FLAG_ENABLE_MULTI_INSTANCE_MENU_TASKBAR)
 class TaskbarRecentAppsControllerTest : TaskbarBaseTestCase() {
 
     @get:Rule val mockitoRule = MockitoJUnit.rule()
@@ -86,6 +97,9 @@
     private var canShowRunningAndRecentAppsAtInit = true
     private var recentTasksChangedListener: RecentTasksChangedListener? = null
 
+    val recentShownTasks: List<Task>
+        get() = recentAppsController.shownTasks.flatMap { it.tasks }
+
     @Before
     fun setUp() {
         super.setup()
@@ -139,7 +153,7 @@
     @Test
     fun canShowRunningAndRecentAppsIsFalseAfterInit_getTasksOnlyCalledInInit() {
         // getTasks() should have been called once from init().
-        verify(mockRecentsModel, times(1)).getTasks(any<Consumer<List<GroupTask>>>())
+        verify(mockRecentsModel, times(1)).getTasks(any<Consumer<List<GroupTask>>>(), any())
         recentAppsController.canShowRunningApps = false
         recentAppsController.canShowRecentApps = false
         prepareHotseatAndRunningAndRecentApps(
@@ -148,29 +162,28 @@
             recentTaskPackages = listOf(RECENT_PACKAGE_1, RECENT_PACKAGE_2),
         )
         // Verify that getTasks() was not called again after the init().
-        verify(mockRecentsModel, times(1)).getTasks(any<Consumer<List<GroupTask>>>())
+        verify(mockRecentsModel, times(1)).getTasks(any<Consumer<List<GroupTask>>>(), any())
     }
 
     @Test
     fun getDesktopItemState_nullItemInfo_returnsNotRunning() {
         setInDesktopMode(true)
-        assertThat(recentAppsController.getDesktopItemState(/* itemInfo= */ null))
-            .isEqualTo(RunningAppState.NOT_RUNNING)
+        val taskState = recentAppsController.getDesktopItemState(/* itemInfo= */ null)
+        assertThat(taskState).isEqualTo(TaskState(RunningAppState.NOT_RUNNING))
     }
 
     @Test
     fun getDesktopItemState_noItemPackage_returnsNotRunning() {
         setInDesktopMode(true)
-        assertThat(recentAppsController.getDesktopItemState(ItemInfo()))
-            .isEqualTo(RunningAppState.NOT_RUNNING)
+        val taskState = recentAppsController.getDesktopItemState(ItemInfo())
+        assertThat(taskState).isEqualTo(TaskState(RunningAppState.NOT_RUNNING))
     }
 
     @Test
     fun getDesktopItemState_noMatchingTasks_returnsNotRunning() {
         setInDesktopMode(true)
-        val itemInfo = createItemInfo("package")
-        assertThat(recentAppsController.getDesktopItemState(itemInfo))
-            .isEqualTo(RunningAppState.NOT_RUNNING)
+        val taskState = recentAppsController.getDesktopItemState(createItemInfo("package"))
+        assertThat(taskState).isEqualTo(TaskState(RunningAppState.NOT_RUNNING))
     }
 
     @Test
@@ -178,10 +191,10 @@
         setInDesktopMode(true)
         val visibleTask = createTask(id = 1, "visiblePackage", isVisible = true)
         updateRecentTasks(runningTasks = listOf(visibleTask), recentTaskPackages = emptyList())
-        val itemInfo = createItemInfo("visiblePackage")
 
-        assertThat(recentAppsController.getDesktopItemState(itemInfo))
-            .isEqualTo(RunningAppState.RUNNING)
+        val taskState = recentAppsController.getDesktopItemState(createItemInfo("visiblePackage"))
+
+        assertThat(taskState).isEqualTo(TaskState(RunningAppState.RUNNING, taskId = 1))
     }
 
     @Test
@@ -189,10 +202,10 @@
         setInDesktopMode(true)
         val minimizedTask = createTask(id = 1, "minimizedPackage", isVisible = false)
         updateRecentTasks(runningTasks = listOf(minimizedTask), recentTaskPackages = emptyList())
-        val itemInfo = createItemInfo("minimizedPackage")
 
-        assertThat(recentAppsController.getDesktopItemState(itemInfo))
-            .isEqualTo(RunningAppState.MINIMIZED)
+        val taskState = recentAppsController.getDesktopItemState(createItemInfo("minimizedPackage"))
+
+        assertThat(taskState).isEqualTo(TaskState(RunningAppState.MINIMIZED, taskId = 1))
     }
 
     @Test
@@ -206,10 +219,10 @@
                 ),
             recentTaskPackages = emptyList(),
         )
-        val itemInfo = createItemInfo("package")
 
-        assertThat(recentAppsController.getDesktopItemState(itemInfo))
-            .isEqualTo(RunningAppState.RUNNING)
+        val taskState = recentAppsController.getDesktopItemState(createItemInfo("package"))
+
+        assertThat(taskState).isEqualTo(TaskState(RunningAppState.RUNNING, taskId = 2))
     }
 
     @Test
@@ -223,10 +236,11 @@
                 ),
             recentTaskPackages = emptyList(),
         )
-        val itemInfo = createItemInfo("package", USER_HANDLE_2)
 
-        assertThat(recentAppsController.getDesktopItemState(itemInfo))
-            .isEqualTo(RunningAppState.NOT_RUNNING)
+        val taskState =
+            recentAppsController.getDesktopItemState(createItemInfo("package", USER_HANDLE_2))
+
+        assertThat(taskState).isEqualTo(TaskState(RunningAppState.NOT_RUNNING))
     }
 
     @Test
@@ -323,8 +337,12 @@
         assertThat(hotseatItem1.taskId).isEqualTo(1)
     }
 
+    /**
+     * Tests that in desktop mode, when two tasks have the same package name and one is in the
+     * hotseat, only the hotseat item represents the app, and no duplicate is shown in recent apps.
+     */
     @Test
-    fun updateHotseatItemInfos_inDesktopMode_twoRunningTasksSamePackage_hotseatCoversFirstTask() {
+    fun updateHotseatItemInfos_inDesktopMode_twoRunningTasksSamePackage_onlyHotseatCoversTask() {
         setInDesktopMode(true)
 
         val newHotseatItems =
@@ -338,16 +356,15 @@
                 recentTaskPackages = emptyList(),
             )
 
-        // First task is in Hotseat Items
+        // The task is in Hotseat Items
         assertThat(newHotseatItems).hasLength(2)
         assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java)
         assertThat(newHotseatItems[1]).isNotInstanceOf(TaskItemInfo::class.java)
         val hotseatItem1 = newHotseatItems[0] as TaskItemInfo
-        assertThat(hotseatItem1.taskId).isEqualTo(1)
-        // Second task is in shownTasks
-        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
-        assertThat(shownTasks)
-            .containsExactlyElementsIn(listOf(createTask(id = 2, HOTSEAT_PACKAGE_1)))
+        assertThat(hotseatItem1.targetPackage).isEqualTo(HOTSEAT_PACKAGE_1)
+
+        // The other task of the same package is not in recentShownTasks
+        assertThat(recentShownTasks).isEmpty()
     }
 
     @Test
@@ -430,8 +447,7 @@
             runningTasks = listOf(task1, task2),
             recentTaskPackages = emptyList(),
         )
-        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
-        assertThat(shownTasks).containsExactlyElementsIn(listOf(task1, task2))
+        assertThat(recentShownTasks).containsExactlyElementsIn(listOf(task1, task2))
     }
 
     @Test
@@ -526,12 +542,15 @@
             recentTaskPackages = emptyList(),
         )
 
-        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
-        assertThat(shownTasks).isEqualTo(listOf(task1, task2))
+        assertThat(recentShownTasks).isEqualTo(listOf(task1, task2))
     }
 
+    /**
+     * Tests that when multiple instances of the same app are running in desktop mode and the app is
+     * not in the hotseat, only one instance is shown in the recent apps section.
+     */
     @Test
-    fun onRecentTasksChanged_inDesktopMode_multiInstance_shownTasks_maintainsOrder() {
+    fun onRecentTasksChanged_inDesktopMode_multiInstance_noHotseat_shownTasksHasOneInstance() {
         setInDesktopMode(true)
         val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
         val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_1)
@@ -541,43 +560,9 @@
             recentTaskPackages = emptyList(),
         )
 
-        prepareHotseatAndRunningAndRecentApps(
-            hotseatPackages = emptyList(),
-            runningTasks = listOf(task2, task1),
-            recentTaskPackages = emptyList(),
-        )
-
-        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
-        assertThat(shownTasks).isEqualTo(listOf(task1, task2))
-    }
-
-    @Test
-    fun updateHotseatItems_inDesktopMode_multiInstanceHotseatPackage_shownItems_maintainsOrder() {
-        setInDesktopMode(true)
-        val task1 = createTask(id = 1, RUNNING_APP_PACKAGE_1)
-        val task2 = createTask(id = 2, RUNNING_APP_PACKAGE_1)
-        prepareHotseatAndRunningAndRecentApps(
-            hotseatPackages = listOf(RUNNING_APP_PACKAGE_1),
-            runningTasks = listOf(task1, task2),
-            recentTaskPackages = emptyList(),
-        )
-        updateRecentTasks( // Trigger a recent-tasks change before calling updateHotseatItems()
-            runningTasks = listOf(task2, task1),
-            recentTaskPackages = emptyList(),
-        )
-
-        prepareHotseatAndRunningAndRecentApps(
-            hotseatPackages = listOf(RUNNING_APP_PACKAGE_1),
-            runningTasks = listOf(task2, task1),
-            recentTaskPackages = emptyList(),
-        )
-
-        val newHotseatItems = recentAppsController.shownHotseatItems
-        assertThat(newHotseatItems).hasSize(1)
-        assertThat(newHotseatItems[0]).isInstanceOf(TaskItemInfo::class.java)
-        assertThat((newHotseatItems[0] as TaskItemInfo).taskId).isEqualTo(1)
-        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
-        assertThat(shownTasks).isEqualTo(listOf(task2))
+        // Assert that recentShownTasks contains only one instance of the app
+        assertThat(recentShownTasks).hasSize(1)
+        assertThat(recentShownTasks[0].key.packageName).isEqualTo(RUNNING_APP_PACKAGE_1)
     }
 
     @Test
@@ -859,8 +844,7 @@
             runningTasks = runningTasks,
             recentTaskPackages = emptyList(),
         )
-        val shownTasks = recentAppsController.shownTasks.map { it.task1 }
-        assertThat(shownTasks).contains(runningTask)
+        assertThat(recentShownTasks).contains(runningTask)
         assertThat(recentAppsController.runningTaskIds).containsExactlyElementsIn(listOf(1))
     }
 
@@ -893,7 +877,7 @@
         val allTasks =
             ArrayList<GroupTask>().apply {
                 if (!runningTasks.isEmpty()) {
-                    add(DesktopTask(ArrayList(runningTasks)))
+                    add(DesktopTask(deskId = 0, ArrayList(runningTasks)))
                 }
                 addAll(recentTasks)
             }
@@ -903,7 +887,7 @@
                 taskListChangeId
             }
             .whenever(mockRecentsModel)
-            .getTasks(any<Consumer<List<GroupTask>>>())
+            .getTasks(any<Consumer<List<GroupTask>>>(), any())
         recentTasksChangedListener?.onRecentTasksChanged()
     }
 
@@ -934,15 +918,21 @@
         return packageNames.map { packageName ->
             if (packageName.startsWith("split")) {
                 val splitPackages = packageName.split("_")
-                GroupTask(
+                SplitTask(
                     createTask(100, splitPackages[0]),
                     createTask(101, splitPackages[1]),
-                    /* splitBounds = */ null,
+                    SplitConfigurationOptions.SplitBounds(
+                        /* leftTopBounds = */ Rect(),
+                        /* rightBottomBounds = */ Rect(),
+                        /* leftTopTaskId = */ -1,
+                        /* rightBottomTaskId = */ -1,
+                        /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50,
+                    ),
                 )
             } else {
                 // Use the number at the end of the test packageName as the id.
                 val id = 1000 + packageName[packageName.length - 1].code
-                GroupTask(createTask(id, packageName))
+                SingleTask(createTask(id, packageName))
             }
         }
     }
@@ -967,7 +957,9 @@
     }
 
     private fun setInDesktopMode(inDesktopMode: Boolean) {
-        whenever(taskbarControllers.taskbarDesktopModeController.areDesktopTasksVisible)
+        whenever(taskbarControllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar())
+            .thenReturn(inDesktopMode)
+        whenever(taskbarControllers.taskbarDesktopModeController.isInDesktopMode)
             .thenReturn(inDesktopMode)
     }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
index 4c94067..ba53dcd 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarScrimViewControllerTest.kt
@@ -20,11 +20,12 @@
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
-import android.view.KeyEvent
 import android.view.View.GONE
 import android.view.View.VISIBLE
+import androidx.test.core.app.ApplicationProvider
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
+import com.android.launcher3.taskbar.rules.SandboxParams
 import com.android.launcher3.taskbar.rules.TaskbarModeRule
 import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.PINNED
 import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode.TRANSIENT
@@ -44,6 +45,10 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
 
 @RunWith(LauncherMultivalentJUnit::class)
 @EmulatedDevices(["pixelTablet2023"])
@@ -51,16 +56,14 @@
     @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
     @get:Rule(order = 1)
     val context =
-        TaskbarWindowSandboxContext.create { builder ->
-            builder.bindSystemUiProxy(
-                object : SystemUiProxy(this) {
-                    override fun onBackEvent(backEvent: KeyEvent?) {
-                        super.onBackEvent(backEvent)
-                        backPressed = true
-                    }
+        TaskbarWindowSandboxContext.create(
+            SandboxParams({
+                spy(SystemUiProxy(ApplicationProvider.getApplicationContext())) {
+                    doAnswer { backPressed = true }.whenever(it).onBackEvent(anyOrNull())
                 }
-            )
-        }
+            })
+        )
+
     @get:Rule(order = 2) val taskbarModeRule = TaskbarModeRule(context)
     @get:Rule(order = 3) val animatorTestRule = AnimatorTestRule(this)
     @get:Rule(order = 4) val taskbarUnitTestRule = TaskbarUnitTestRule(this, context)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
index 5e438bd..021e1e4 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarStashControllerTest.kt
@@ -53,11 +53,11 @@
 import com.android.launcher3.util.LauncherMultivalentJUnit
 import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED
-import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE
 import com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.TruthJUnit.assume
 import org.junit.After
-import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -82,11 +82,6 @@
 
     private val activityContext by taskbarUnitTestRule::activityContext
 
-    // Disable hardware keyboard mode during tests.
-    @Before fun enableSoftwareIme() = TaskbarStashController.enableSoftwareImeForTests(true)
-
-    @After fun resetIme() = TaskbarStashController.enableSoftwareImeForTests(false)
-
     @After fun cancelTimeoutIfExists() = stashController.cancelTimeoutIfExists()
 
     @Test
@@ -544,8 +539,10 @@
     @Test
     @TaskbarMode(PINNED)
     fun testAnimatePinnedTaskbar_imeShown_replacesIconsWithHandle() {
+        assume().that(activityContext.isHardwareKeyboard).isFalse()
+
         getInstrumentation().runOnMainSync {
-            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, false)
+            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, false)
             animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME)
         }
         assertThat(viewController.areIconsVisible()).isFalse()
@@ -555,8 +552,10 @@
     @Test
     @TaskbarMode(PINNED)
     fun testAnimatePinnedTaskbar_imeHidden_replacesHandleWithIcons() {
+        assume().that(activityContext.isHardwareKeyboard).isFalse()
+
         getInstrumentation().runOnMainSync {
-            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
+            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, true)
             animatorTestRule.advanceTimeBy(0)
         }
 
@@ -571,9 +570,11 @@
     @Test
     @TaskbarMode(PINNED)
     fun testAnimatePinnedTaskbar_imeHidden_verifyAnimationDuration() {
+        assume().that(activityContext.isHardwareKeyboard).isFalse()
+
         // Start with IME shown.
         getInstrumentation().runOnMainSync {
-            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
+            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, true)
             animatorTestRule.advanceTimeBy(0)
         }
 
@@ -596,8 +597,10 @@
     @Test
     @TaskbarMode(THREE_BUTTONS)
     fun testAnimateThreeButtonsTaskbar_imeShown_hidesIconsAndBg() {
+        assume().that(activityContext.isHardwareKeyboard).isFalse()
+
         getInstrumentation().runOnMainSync {
-            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, false)
+            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, false)
             animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME)
         }
         assertThat(viewController.areIconsVisible()).isFalse()
@@ -607,8 +610,10 @@
     @Test
     @TaskbarMode(THREE_BUTTONS)
     fun testAnimateThreeButtonsTaskbar_imeHidden_showsIconsAndBg() {
+        assume().that(activityContext.isHardwareKeyboard).isFalse()
+
         getInstrumentation().runOnMainSync {
-            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, false)
+            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, false)
             animatorTestRule.advanceTimeBy(TASKBAR_STASH_DURATION_FOR_IME)
         }
 
@@ -625,8 +630,10 @@
     @Test
     @TaskbarMode(PINNED)
     fun testSetSystemGestureInProgress_whileImeShown_unstashesTaskbar() {
+        assume().that(activityContext.isHardwareKeyboard).isFalse()
+
         getInstrumentation().runOnMainSync {
-            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_SHOWING, true)
+            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, true)
             animatorTestRule.advanceTimeBy(0)
         }
 
@@ -641,6 +648,19 @@
 
     @Test
     @TaskbarMode(PINNED)
+    fun testSysuiStateImeShowingInApp_hardwareKeyboardWithPinnedMode_notStashedForIme() {
+        assume().that(activityContext.isHardwareKeyboard).isTrue()
+
+        getInstrumentation().runOnMainSync {
+            stashController.updateStateForFlag(FLAG_IN_APP, true)
+            stashController.updateStateForSysuiFlags(SYSUI_STATE_IME_VISIBLE, true)
+        }
+
+        assertThat(stashController.isStashed).isFalse()
+    }
+
+    @Test
+    @TaskbarMode(PINNED)
     fun testUnlockTransition_pinnedMode_fadesOutHandle() {
         getInstrumentation().runOnMainSync {
             stashController.updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED, true)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt
index 0bb404b..24ed81f 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTest.kt
@@ -19,7 +19,6 @@
 import android.platform.test.flag.junit.FlagsParameterization
 import android.platform.test.flag.junit.FlagsParameterization.allCombinationsOf
 import android.platform.test.flag.junit.SetFlagsRule
-import com.android.launcher3.Flags.FLAG_TASKBAR_RECENTS_LAYOUT_TRANSITION
 import com.android.launcher3.R
 import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
 import com.android.launcher3.taskbar.TaskbarIconType.ALL_APPS
@@ -34,6 +33,7 @@
 import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.ForceRtl
 import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
 import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
+import com.android.window.flags.Flags.FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -54,7 +54,7 @@
                 } else {
                     listOf("onDevice") // Unused.
                 }
-            val flags = allCombinationsOf(FLAG_TASKBAR_RECENTS_LAYOUT_TRANSITION)
+            val flags = allCombinationsOf(FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION)
             return devices.flatMap { d -> flags.map { f -> arrayOf(d, f) } } // Cartesian product.
         }
     }
@@ -154,4 +154,13 @@
         }
         assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT, DIVIDER, RECENT)
     }
+
+    @Test
+    fun testUpdateItem_addHotseatItemAfterRecentsItem_hotseatItemBeforeDivider() {
+        runOnMainSync {
+            taskbarView.updateItems(emptyArray(), createRecents(1))
+            taskbarView.updateItems(createHotseatItems(1), createRecents(1))
+        }
+        assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT, DIVIDER, RECENT)
+    }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
index a6bdbb0..e52aacf 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewTestUtil.kt
@@ -28,6 +28,7 @@
 import com.android.launcher3.taskbar.TaskbarIconType.OVERFLOW
 import com.android.launcher3.taskbar.TaskbarIconType.RECENT
 import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.SingleTask
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.Task.TaskKey
 import com.google.common.truth.FailureMetadata
@@ -45,18 +46,20 @@
 
     /** Creates an array of fake hotseat items. */
     fun createHotseatItems(size: Int): Array<ItemInfo> {
-        return Array(size) {
-            WorkspaceItemInfo(
-                    AppInfo(TEST_COMPONENT, "Test App $it", Process.myUserHandle(), Intent())
-                )
-                .apply { id = it }
-        }
+        return Array(size) { createHotseatWorkspaceItem(it) }
+    }
+
+    fun createHotseatWorkspaceItem(id: Int = 0): WorkspaceItemInfo {
+        return WorkspaceItemInfo(
+                AppInfo(TEST_COMPONENT, "Test App $id", Process.myUserHandle(), Intent())
+            )
+            .apply { this.id = id }
     }
 
     /** Creates a list of fake recent tasks. */
     fun createRecents(size: Int): List<GroupTask> {
         return List(size) {
-            GroupTask(
+            SingleTask(
                 Task().apply {
                     key =
                         TaskKey(
@@ -74,13 +77,13 @@
 }
 
 /** A `Truth` [Subject] with extensions for verifying [TaskbarView]. */
-class TaskbarViewSubject(failureMetadata: FailureMetadata, private val view: TaskbarView) :
+class TaskbarViewSubject(failureMetadata: FailureMetadata, private val view: TaskbarView?) :
     Subject(failureMetadata, view) {
 
     /** Verifies that the types of icons match [expectedTypes] in order. */
     fun hasIconTypes(vararg expectedTypes: TaskbarIconType) {
         val actualTypes =
-            view.iconViews.map {
+            view?.iconViews?.map {
                 when (it) {
                     view.allAppsButtonContainer -> ALL_APPS
                     view.taskbarDividerViewContainer -> DIVIDER
@@ -99,9 +102,9 @@
     /** Verifies that recents from [startIndex] have IDs that match [expectedIds] in order. */
     fun hasRecentsOrder(startIndex: Int, expectedIds: List<Int>) {
         val actualIds =
-            view.iconViews.slice(startIndex..<expectedIds.size).map {
+            view?.iconViews?.slice(startIndex..<startIndex + expectedIds.size)?.flatMap {
                 assertThat(it.tag).isInstanceOf(GroupTask::class.java)
-                (it.tag as? GroupTask)?.task1?.key?.id
+                (it.tag as GroupTask).tasks.map { task -> task.key.id }
             }
         assertThat(actualIds).containsExactlyElementsIn(expectedIds).inOrder()
     }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt
index 78d8e5d..2df4fab 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/TaskbarViewWithLayoutTransitionTest.kt
@@ -18,12 +18,15 @@
 
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
-import com.android.launcher3.Flags.FLAG_TASKBAR_RECENTS_LAYOUT_TRANSITION
+import android.view.View
+import com.android.launcher3.Flags.FLAG_TASKBAR_OVERFLOW
 import com.android.launcher3.R
+import com.android.launcher3.statehandlers.DesktopVisibilityController
 import com.android.launcher3.taskbar.TaskbarControllerTestUtil.runOnMainSync
 import com.android.launcher3.taskbar.TaskbarIconType.ALL_APPS
 import com.android.launcher3.taskbar.TaskbarIconType.DIVIDER
 import com.android.launcher3.taskbar.TaskbarIconType.HOTSEAT
+import com.android.launcher3.taskbar.TaskbarIconType.OVERFLOW
 import com.android.launcher3.taskbar.TaskbarIconType.RECENT
 import com.android.launcher3.taskbar.TaskbarViewTestUtil.assertThat
 import com.android.launcher3.taskbar.TaskbarViewTestUtil.createHotseatItems
@@ -33,14 +36,17 @@
 import com.android.launcher3.taskbar.rules.TaskbarWindowSandboxContext
 import com.android.launcher3.util.LauncherMultivalentJUnit
 import com.android.launcher3.util.LauncherMultivalentJUnit.EmulatedDevices
+import com.android.window.flags.Flags.FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION
+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.whenever
 
 @RunWith(LauncherMultivalentJUnit::class)
 @EmulatedDevices(["pixelFoldable2023", "pixelTablet2023"])
-@EnableFlags(FLAG_TASKBAR_RECENTS_LAYOUT_TRANSITION)
+@EnableFlags(FLAG_ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION, FLAG_TASKBAR_OVERFLOW)
 class TaskbarViewWithLayoutTransitionTest {
 
     @get:Rule(order = 0) val setFlagsRule = SetFlagsRule()
@@ -49,6 +55,15 @@
 
     private lateinit var taskbarView: TaskbarView
 
+    private val iconViews: Array<View>
+        get() = taskbarView.iconViews
+
+    private val desktopVisibilityController: DesktopVisibilityController
+        get() = DesktopVisibilityController.INSTANCE[context]
+
+    private val maxShownRecents: Int
+        get() = taskbarView.maxNumIconViews - 2 // Account for All Apps and Divider.
+
     @Before
     fun obtainView() {
         taskbarView = taskbarUnitTestRule.activityContext.dragLayer.findViewById(R.id.taskbar_view)
@@ -131,4 +146,207 @@
         }
         assertThat(taskbarView).hasIconTypes(RECENT, DIVIDER, HOTSEAT, ALL_APPS)
     }
+
+    @Test
+    fun testUpdateItems_addRecentsItem_viewAddedOnRight() {
+        runOnMainSync {
+            taskbarView.updateItems(emptyArray(), createRecents(1))
+            val prevIconViews = iconViews
+
+            val newRecents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), newRecents)
+
+            assertThat(taskbarView).hasRecentsOrder(startIndex = 2, expectedIds = listOf(0, 1))
+            assertThat(iconViews[2]).isSameInstanceAs(prevIconViews[2])
+            assertThat(iconViews.last() in prevIconViews).isFalse()
+        }
+    }
+
+    @Test
+    @ForceRtl
+    fun testUpdateItems_rtl_addRecentsItem_viewAddedOnLeft() {
+        runOnMainSync {
+            taskbarView.updateItems(emptyArray(), createRecents(1))
+            val prevIconViews = iconViews
+
+            val newRecents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), newRecents)
+
+            assertThat(taskbarView).hasRecentsOrder(startIndex = 0, expectedIds = listOf(1, 0))
+            assertThat(iconViews[1]).isSameInstanceAs(prevIconViews.first())
+            assertThat(iconViews.first() in prevIconViews).isFalse()
+        }
+    }
+
+    @Test
+    fun testUpdateItems_removeFirstRecentsItem_correspondingViewRemoved() {
+        runOnMainSync {
+            val recents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), recents)
+
+            val expectedViewToRemove = iconViews[2]
+            assertThat(expectedViewToRemove.tag).isEqualTo(recents.first())
+
+            taskbarView.updateItems(emptyArray(), listOf(recents.last()))
+            assertThat(expectedViewToRemove in iconViews).isFalse()
+        }
+    }
+
+    @Test
+    fun testUpdateItems_removeLastRecentsItem_correspondingViewRemoved() {
+        runOnMainSync {
+            val recents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), recents)
+
+            val expectedViewToRemove = iconViews[3]
+            assertThat(expectedViewToRemove.tag).isEqualTo(recents.last())
+
+            taskbarView.updateItems(emptyArray(), listOf(recents.first()))
+            assertThat(expectedViewToRemove in iconViews).isFalse()
+        }
+    }
+
+    @Test
+    @ForceRtl
+    fun testUpdateItems_rtl_removeFirstRecentsItem_correspondingViewRemoved() {
+        runOnMainSync {
+            val recents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), recents)
+
+            val expectedViewToRemove = iconViews[1]
+            assertThat(expectedViewToRemove.tag).isEqualTo(recents.first())
+
+            taskbarView.updateItems(emptyArray(), listOf(recents.last()))
+            assertThat(expectedViewToRemove in iconViews).isFalse()
+        }
+    }
+
+    @Test
+    @ForceRtl
+    fun testUpdateItems_rtl_removeLastRecentsItem_correspondingViewRemoved() {
+        runOnMainSync {
+            val recents = createRecents(2)
+            taskbarView.updateItems(emptyArray(), recents)
+
+            val expectedViewToRemove = iconViews[0]
+            assertThat(expectedViewToRemove.tag).isEqualTo(recents.last())
+
+            taskbarView.updateItems(emptyArray(), listOf(recents.first()))
+            assertThat(expectedViewToRemove in iconViews).isFalse()
+        }
+    }
+
+    @Test
+    fun testUpdateItems_desktopMode_hotseatItem_noDivider() {
+        whenever(desktopVisibilityController.isInDesktopMode(context.displayId)).thenReturn(true)
+        runOnMainSync { taskbarView.updateItems(createHotseatItems(1), emptyList()) }
+        assertThat(taskbarView).hasIconTypes(ALL_APPS, HOTSEAT)
+    }
+
+    @Test
+    @ForceRtl
+    fun testUpdateItems_rtlAndDesktopMode_hotseatItem_noDivider() {
+        whenever(desktopVisibilityController.isInDesktopMode(context.displayId)).thenReturn(true)
+        runOnMainSync { taskbarView.updateItems(createHotseatItems(1), emptyList()) }
+        assertThat(taskbarView).hasIconTypes(HOTSEAT, ALL_APPS)
+    }
+
+    @Test
+    fun testUpdateItems_desktopMode_recentItem_hasDivider() {
+        whenever(desktopVisibilityController.isInDesktopMode(context.displayId)).thenReturn(true)
+        runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(1)) }
+        assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, RECENT)
+    }
+
+    @Test
+    @ForceRtl
+    fun testUpdateItems_rtlAndDesktopMode_recentItem_hasDivider() {
+        whenever(desktopVisibilityController.isInDesktopMode(context.displayId)).thenReturn(true)
+        runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(1)) }
+        assertThat(taskbarView).hasIconTypes(RECENT, DIVIDER, ALL_APPS)
+    }
+
+    @Test
+    fun testUpdateItems_maxRecents_noOverflow() {
+        runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(maxShownRecents)) }
+        assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, *RECENT * maxShownRecents)
+    }
+
+    @Test
+    fun testUpdateItems_moreThanMaxRecents_overflowShownBeforeRecents() {
+        val recentsSize = maxShownRecents + 2
+        runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(recentsSize)) }
+
+        val expectedNumRecents = RECENT * getExpectedNumRecentsWithOverflow()
+        assertThat(taskbarView).hasIconTypes(ALL_APPS, DIVIDER, OVERFLOW, *expectedNumRecents)
+    }
+
+    @Test
+    @ForceRtl
+    fun testUpdateItems_rtl_moreThanMaxRecents_overflowShownAfterRecents() {
+        val recentsSize = maxShownRecents + 2
+        runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(recentsSize)) }
+
+        val expectedRecents = RECENT * getExpectedNumRecentsWithOverflow()
+        assertThat(taskbarView).hasIconTypes(*expectedRecents, OVERFLOW, DIVIDER, ALL_APPS)
+    }
+
+    @Test
+    fun testUpdateItems_moreThanMaxRecentsWithHotseat_fewerRecentsShown() {
+        val hotseatSize = 4
+        val recentsSize = maxShownRecents + 2
+        runOnMainSync {
+            taskbarView.updateItems(createHotseatItems(hotseatSize), createRecents(recentsSize))
+        }
+
+        val expectedRecents = RECENT * getExpectedNumRecentsWithOverflow(hotseatSize)
+        assertThat(taskbarView)
+            .hasIconTypes(ALL_APPS, *HOTSEAT * hotseatSize, DIVIDER, OVERFLOW, *expectedRecents)
+    }
+
+    @Test
+    @ForceRtl
+    fun testUpdateItems_rtl_moreThanMaxRecentsWithHotseat_fewerRecentsShown() {
+        val hotseatSize = 4
+        val recentsSize = maxShownRecents + 2
+        runOnMainSync {
+            taskbarView.updateItems(createHotseatItems(hotseatSize), createRecents(recentsSize))
+        }
+
+        val expectedRecents = RECENT * getExpectedNumRecentsWithOverflow(hotseatSize)
+        assertThat(taskbarView)
+            .hasIconTypes(*expectedRecents, OVERFLOW, DIVIDER, *HOTSEAT * hotseatSize, ALL_APPS)
+    }
+
+    @Test
+    fun testUpdateItems_moreThanMaxRecents_verifyShownRecentsOrder() {
+        val recentsSize = maxShownRecents + 2
+        runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(recentsSize)) }
+
+        val expectedNumRecents = getExpectedNumRecentsWithOverflow()
+        assertThat(taskbarView)
+            .hasRecentsOrder(
+                startIndex = iconViews.size - expectedNumRecents,
+                expectedIds = ((recentsSize - expectedNumRecents)..<recentsSize).toList(),
+            )
+    }
+
+    @Test
+    @ForceRtl
+    fun testUpdateItems_rtl_moreThanMaxRecents_verifyShownRecentsReversed() {
+        val recentsSize = maxShownRecents + 2
+        runOnMainSync { taskbarView.updateItems(emptyArray(), createRecents(recentsSize)) }
+
+        val expectedNumRecents = getExpectedNumRecentsWithOverflow()
+        assertThat(taskbarView)
+            .hasRecentsOrder(
+                startIndex = 0,
+                expectedIds = ((recentsSize - expectedNumRecents)..<recentsSize).toList().reversed(),
+            )
+    }
+
+    /** Returns the number of expected recents outside of the overflow based on [hotseatSize]. */
+    private fun getExpectedNumRecentsWithOverflow(hotseatSize: Int = 0): Int {
+        return maxShownRecents - hotseatSize - 1 // Account for overflow.
+    }
 }
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
index eae181f..da362bd 100644
--- 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
@@ -44,7 +44,7 @@
             )
         val listener = TestBubbleAnimatorListener()
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
-            bubbleAnimator.animateNewBubble(selectedBubbleIndex = 2, listener)
+            bubbleAnimator.animateNewBubble(selectedBubbleIndex = 2, listener = listener)
         }
 
         assertThat(bubbleAnimator.isRunning).isTrue()
@@ -94,7 +94,9 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
             bubbleAnimator.animateNewAndRemoveOld(
                 selectedBubbleIndex = 3,
-                removedBubbleIndex = 2,
+                newlySelectedBubbleIndex = 2,
+                removedBubbleIndex = 1,
+                addedBubbleIndex = 3,
                 listener,
             )
         }
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 44070cf..61a6975 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
@@ -20,8 +20,10 @@
 import android.graphics.Color
 import android.graphics.Path
 import android.graphics.PointF
+import android.graphics.Rect
 import android.graphics.drawable.ColorDrawable
 import android.view.LayoutInflater
+import android.view.MotionEvent
 import android.view.View
 import android.view.View.INVISIBLE
 import android.view.View.VISIBLE
@@ -35,10 +37,13 @@
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.R
+import com.android.launcher3.taskbar.TaskbarInsetsController
 import com.android.launcher3.taskbar.bubbles.BubbleBarBubble
 import com.android.launcher3.taskbar.bubbles.BubbleBarOverflow
 import com.android.launcher3.taskbar.bubbles.BubbleBarParentViewHeightUpdateNotifier
 import com.android.launcher3.taskbar.bubbles.BubbleBarView
+import com.android.launcher3.taskbar.bubbles.BubbleBarViewController
+import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController
 import com.android.launcher3.taskbar.bubbles.BubbleView
 import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutController
 import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutMessage
@@ -46,8 +51,10 @@
 import com.android.launcher3.taskbar.bubbles.flyout.FlyoutCallbacks
 import com.android.launcher3.taskbar.bubbles.flyout.FlyoutScheduler
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
+import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState
 import com.android.wm.shell.shared.animation.PhysicsAnimator
 import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
+import com.android.wm.shell.shared.bubbles.BubbleBarLocation
 import com.android.wm.shell.shared.bubbles.BubbleInfo
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Semaphore
@@ -56,12 +63,6 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.kotlin.any
-import org.mockito.kotlin.atLeastOnce
-import org.mockito.kotlin.eq
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.verify
-import org.mockito.kotlin.whenever
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
@@ -77,7 +78,7 @@
     private lateinit var bubble: BubbleBarBubble
     private lateinit var bubbleBarView: BubbleBarView
     private lateinit var flyoutContainer: FrameLayout
-    private lateinit var bubbleStashController: BubbleStashController
+    private lateinit var bubbleStashController: FakeBubbleStashController
     private lateinit var flyoutController: BubbleBarFlyoutController
     private val emptyRunnable = Runnable {}
 
@@ -88,6 +89,7 @@
     fun setUp() {
         animatorScheduler = TestBubbleBarViewAnimatorScheduler()
         bubbleBarParentViewController = TestBubbleBarParentViewHeightUpdateNotifier()
+        bubbleStashController = FakeBubbleStashController()
         PhysicsAnimatorTestUtils.prepareForTest()
         setupFlyoutController()
     }
@@ -95,11 +97,10 @@
     @Test
     fun animateBubbleInForStashed() {
         setUpBubbleBar()
-        setUpBubbleStashController()
 
         val handle = View(context)
         val handleAnimator = PhysicsAnimator.getInstance(handle)
-        whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+        bubbleStashController.handleAnimator = handleAnimator
 
         val animator =
             BubbleBarViewAnimator(
@@ -146,17 +147,16 @@
         assertThat(handle.translationY).isEqualTo(0)
         assertThat(bubbleBarView.alpha).isEqualTo(0)
         assertThat(animator.isAnimating).isFalse()
-        verify(bubbleStashController).stashBubbleBarImmediate()
+        assertThat(bubbleStashController.isStashed).isTrue()
     }
 
     @Test
     fun animateBubbleInForStashed_tapAnimatingBubble() {
         setUpBubbleBar()
-        setUpBubbleStashController()
 
         val handle = View(context)
         val handleAnimator = PhysicsAnimator.getInstance(handle)
-        whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+        bubbleStashController.handleAnimator = handleAnimator
 
         val animator =
             BubbleBarViewAnimator(
@@ -186,7 +186,7 @@
         assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
         assertThat(animator.isAnimating).isTrue()
 
-        verify(bubbleStashController, atLeastOnce()).updateTaskbarTouchRegion()
+        assertThat(bubbleStashController.taskbarTouchRegionUpdated).isTrue()
         assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1)
         waitForFlyoutToShow()
 
@@ -210,11 +210,10 @@
     @Test
     fun animateBubbleInForStashed_touchTaskbarArea_whileShowing() {
         setUpBubbleBar()
-        setUpBubbleStashController()
 
         val handle = View(context)
         val handleAnimator = PhysicsAnimator.getInstance(handle)
-        whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+        bubbleStashController.handleAnimator = handleAnimator
 
         val animator =
             BubbleBarViewAnimator(
@@ -244,12 +243,19 @@
             animator.onStashStateChangingWhileAnimating()
         }
 
+        // wait for the animation to cancel
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+            handleAnimator,
+            DynamicAnimation.TRANSLATION_Y,
+        )
+
         // verify that the hide animation was canceled
         assertThat(animatorScheduler.delayedBlock).isNull()
         assertThat(animator.isAnimating).isFalse()
+        assertThat(bubbleStashController.animationInterrupted).isTrue()
         assertThat(bubbleBarView.scaleX).isEqualTo(1)
         assertThat(bubbleBarView.scaleY).isEqualTo(1)
-        verify(bubbleStashController).onNewBubbleAnimationInterrupted(any(), any())
+        assertThat(bubbleStashController.isStashed).isTrue()
 
         // PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait
         // again
@@ -260,11 +266,10 @@
     @Test
     fun animateBubbleInForStashed_touchTaskbarArea_whileHiding() {
         setUpBubbleBar()
-        setUpBubbleStashController()
 
         val handle = View(context)
         val handleAnimator = PhysicsAnimator.getInstance(handle)
-        whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+        bubbleStashController.handleAnimator = handleAnimator
 
         val animator =
             BubbleBarViewAnimator(
@@ -302,7 +307,7 @@
         }
         assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2)
         assertThat(animator.isAnimating).isFalse()
-        verify(bubbleStashController).onNewBubbleAnimationInterrupted(any(), any())
+        assertThat(bubbleStashController.animationInterrupted).isTrue()
 
         // PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait
         // again
@@ -313,11 +318,10 @@
     @Test
     fun animateBubbleInForStashed_showAnimationCanceled() {
         setUpBubbleBar()
-        setUpBubbleStashController()
 
         val handle = View(context)
         val handleAnimator = PhysicsAnimator.getInstance(handle)
-        whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+        bubbleStashController.handleAnimator = handleAnimator
 
         val animator =
             BubbleBarViewAnimator(
@@ -351,11 +355,10 @@
     @Test
     fun animateBubbleInForStashed_autoExpanding() {
         setUpBubbleBar()
-        setUpBubbleStashController()
 
         val handle = View(context)
         val handleAnimator = PhysicsAnimator.getInstance(handle)
-        whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+        bubbleStashController.handleAnimator = handleAnimator
 
         var notifiedExpanded = false
         val onExpanded = Runnable { notifiedExpanded = true }
@@ -391,18 +394,17 @@
         // verify there is no hide animation
         assertThat(animatorScheduler.delayedBlock).isNull()
 
-        verify(bubbleStashController).showBubbleBarImmediate()
+        assertThat(bubbleStashController.isStashed).isFalse()
         assertThat(notifiedExpanded).isTrue()
     }
 
     @Test
     fun animateBubbleInForStashed_expandedWhileAnimatingIn() {
         setUpBubbleBar()
-        setUpBubbleStashController()
 
         val handle = View(context)
         val handleAnimator = PhysicsAnimator.getInstance(handle)
-        whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+        bubbleStashController.handleAnimator = handleAnimator
 
         var notifiedExpanded = false
         val onExpanded = Runnable { notifiedExpanded = true }
@@ -451,11 +453,10 @@
     @Test
     fun animateBubbleInForStashed_expandedWhileFullyIn() {
         setUpBubbleBar()
-        setUpBubbleStashController()
 
         val handle = View(context)
         val handleAnimator = PhysicsAnimator.getInstance(handle)
-        whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+        bubbleStashController.handleAnimator = handleAnimator
 
         var notifiedExpanded = false
         val onExpanded = Runnable { notifiedExpanded = true }
@@ -507,13 +508,11 @@
     @Test
     fun animateToInitialState_inApp() {
         setUpBubbleBar()
-        setUpBubbleStashController()
-        whenever(bubbleStashController.bubbleBarTranslationY)
-            .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
+        bubbleStashController.launcherState = BubbleLauncherState.IN_APP
 
         val handle = View(context)
         val handleAnimator = PhysicsAnimator.getInstance(handle)
-        whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+        bubbleStashController.handleAnimator = handleAnimator
 
         val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
 
@@ -562,19 +561,78 @@
         assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE)
         assertThat(notifiedBubbleBarVisible).isTrue()
 
-        verify(bubbleStashController).stashBubbleBarImmediate()
+        assertThat(bubbleStashController.isStashed).isTrue()
+    }
+
+    @Test
+    fun animateToInitialState_whileDragging_inApp() {
+        setUpBubbleBar()
+        bubbleStashController.launcherState = BubbleLauncherState.IN_APP
+
+        val handle = View(context)
+        val handleAnimator = PhysicsAnimator.getInstance(handle)
+        bubbleStashController.handleAnimator = handleAnimator
+
+        val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
+
+        var notifiedBubbleBarVisible = false
+        val onBubbleBarVisible = Runnable { notifiedBubbleBarVisible = true }
+        val animator =
+            BubbleBarViewAnimator(
+                bubbleBarView,
+                bubbleStashController,
+                flyoutController,
+                bubbleBarParentViewController,
+                onExpanded = emptyRunnable,
+                onBubbleBarVisible = onBubbleBarVisible,
+                animatorScheduler,
+            )
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            bubbleBarView.visibility = INVISIBLE
+            animator.animateToInitialState(
+                bubble,
+                isInApp = true,
+                isExpanding = false,
+                isDragging = true,
+            )
+        }
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+        barAnimator.assertIsNotRunning()
+        assertThat(animator.isAnimating).isTrue()
+        assertThat(bubbleBarView.alpha).isEqualTo(1)
+        assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
+        assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1)
+        waitForFlyoutToShow()
+
+        assertThat(animatorScheduler.delayedBlock).isNotNull()
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(animatorScheduler.delayedBlock!!)
+
+        waitForFlyoutToHide()
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {}
+        PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Y)
+
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync()
+        assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2)
+        assertThat(animator.isAnimating).isFalse()
+        assertThat(bubbleBarView.alpha).isEqualTo(1)
+        assertThat(handle.translationY).isEqualTo(0)
+        assertThat(bubbleBarView.visibility).isEqualTo(VISIBLE)
+        assertThat(notifiedBubbleBarVisible).isTrue()
     }
 
     @Test
     fun animateToInitialState_inApp_autoExpanding() {
         setUpBubbleBar()
-        setUpBubbleStashController()
-        whenever(bubbleStashController.bubbleBarTranslationY)
-            .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
+        bubbleStashController.launcherState = BubbleLauncherState.IN_APP
 
         val handle = View(context)
         val handleAnimator = PhysicsAnimator.getInstance(handle)
-        whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+        bubbleStashController.handleAnimator = handleAnimator
 
         val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
 
@@ -604,16 +662,14 @@
         assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_TASKBAR)
 
         assertThat(animatorScheduler.delayedBlock).isNull()
-        verify(bubbleStashController).showBubbleBarImmediate()
+        assertThat(bubbleStashController.isStashed).isFalse()
         assertThat(notifiedExpanded).isTrue()
     }
 
     @Test
     fun animateToInitialState_inHome() {
         setUpBubbleBar()
-        setUpBubbleStashController()
-        whenever(bubbleStashController.bubbleBarTranslationY)
-            .thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+        bubbleStashController.launcherState = BubbleLauncherState.HOME
 
         val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
 
@@ -652,15 +708,13 @@
         assertThat(bubbleBarView.alpha).isEqualTo(1)
         assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)
 
-        verify(bubbleStashController).showBubbleBarImmediate()
+        assertThat(bubbleStashController.isStashed).isFalse()
     }
 
     @Test
     fun animateToInitialState_expandedWhileAnimatingIn() {
         setUpBubbleBar()
-        setUpBubbleStashController()
-        whenever(bubbleStashController.bubbleBarTranslationY)
-            .thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+        bubbleStashController.launcherState = BubbleLauncherState.HOME
 
         var notifiedExpanded = false
         val onExpanded = Runnable { notifiedExpanded = true }
@@ -702,16 +756,14 @@
 
         verifyBubbleBarIsExpandedWithTranslation(BAR_TRANSLATION_Y_FOR_HOTSEAT)
         assertThat(animator.isAnimating).isFalse()
-        verify(bubbleStashController).showBubbleBarImmediate()
+        assertThat(bubbleStashController.isStashed).isFalse()
         assertThat(notifiedExpanded).isTrue()
     }
 
     @Test
     fun animateToInitialState_expandedWhileFullyIn() {
         setUpBubbleBar()
-        setUpBubbleStashController()
-        whenever(bubbleStashController.bubbleBarTranslationY)
-            .thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+        bubbleStashController.launcherState = BubbleLauncherState.HOME
 
         var notifiedExpanded = false
         val onExpanded = Runnable { notifiedExpanded = true }
@@ -758,9 +810,7 @@
     @Test
     fun animateBubbleBarForCollapsed() {
         setUpBubbleBar()
-        setUpBubbleStashController()
-        whenever(bubbleStashController.bubbleBarTranslationY)
-            .thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+        bubbleStashController.launcherState = BubbleLauncherState.HOME
 
         val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
 
@@ -804,16 +854,13 @@
         assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(2)
         // the bubble bar translation y should be back to its initial value
         assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)
-
-        verify(bubbleStashController).showBubbleBarImmediate()
+        assertThat(bubbleStashController.isStashed).isFalse()
     }
 
     @Test
     fun animateBubbleBarForCollapsed_autoExpanding() {
         setUpBubbleBar()
-        setUpBubbleStashController()
-        whenever(bubbleStashController.bubbleBarTranslationY)
-            .thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+        bubbleStashController.launcherState = BubbleLauncherState.HOME
 
         val semaphore = Semaphore(0)
         var notifiedExpanded = false
@@ -860,16 +907,14 @@
 
         assertThat(animator.isAnimating).isFalse()
         assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)
-        verify(bubbleStashController).showBubbleBarImmediate()
+        assertThat(bubbleStashController.isStashed).isFalse()
         assertThat(notifiedExpanded).isTrue()
     }
 
     @Test
     fun animateBubbleBarForCollapsed_expandingWhileAnimatingIn() {
         setUpBubbleBar()
-        setUpBubbleStashController()
-        whenever(bubbleStashController.bubbleBarTranslationY)
-            .thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+        bubbleStashController.launcherState = BubbleLauncherState.HOME
 
         val semaphore = Semaphore(0)
         var notifiedExpanded = false
@@ -928,16 +973,14 @@
         assertThat(animator.isAnimating).isFalse()
         assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)
         assertThat(bubbleBarView.isExpanded).isTrue()
-        verify(bubbleStashController).showBubbleBarImmediate()
+        assertThat(bubbleStashController.isStashed).isFalse()
         assertThat(notifiedExpanded).isTrue()
     }
 
     @Test
     fun animateBubbleBarForCollapsed_expandingWhileFullyIn() {
         setUpBubbleBar()
-        setUpBubbleStashController()
-        whenever(bubbleStashController.bubbleBarTranslationY)
-            .thenReturn(BAR_TRANSLATION_Y_FOR_HOTSEAT)
+        bubbleStashController.launcherState = BubbleLauncherState.HOME
 
         val barAnimator = PhysicsAnimator.getInstance(bubbleBarView)
 
@@ -991,18 +1034,17 @@
         assertThat(animator.isAnimating).isFalse()
         assertThat(bubbleBarView.translationY).isEqualTo(BAR_TRANSLATION_Y_FOR_HOTSEAT)
         assertThat(bubbleBarView.isExpanded).isTrue()
-        verify(bubbleStashController).showBubbleBarImmediate()
+        assertThat(bubbleStashController.isStashed).isFalse()
         assertThat(notifiedExpanded).isTrue()
     }
 
     @Test
     fun interruptAnimation_whileAnimatingIn() {
         setUpBubbleBar()
-        setUpBubbleStashController()
 
         val handle = View(context)
         val handleAnimator = PhysicsAnimator.getInstance(handle)
-        whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+        bubbleStashController.handleAnimator = handleAnimator
 
         val animator =
             BubbleBarViewAnimator(
@@ -1063,17 +1105,16 @@
         assertThat(handle.translationY).isEqualTo(0)
         assertThat(bubbleBarView.alpha).isEqualTo(0)
         assertThat(animator.isAnimating).isFalse()
-        verify(bubbleStashController).stashBubbleBarImmediate()
+        assertThat(bubbleStashController.isStashed).isTrue()
     }
 
     @Test
     fun interruptAnimation_whileIn() {
         setUpBubbleBar()
-        setUpBubbleStashController()
 
         val handle = View(context)
         val handleAnimator = PhysicsAnimator.getInstance(handle)
-        whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+        bubbleStashController.handleAnimator = handleAnimator
 
         val animator =
             BubbleBarViewAnimator(
@@ -1140,17 +1181,16 @@
         assertThat(handle.translationY).isEqualTo(0)
         assertThat(bubbleBarView.alpha).isEqualTo(0)
         assertThat(animator.isAnimating).isFalse()
-        verify(bubbleStashController).stashBubbleBarImmediate()
+        assertThat(bubbleStashController.isStashed).isTrue()
     }
 
     @Test
     fun interruptAnimation_whileAnimatingOut_whileCollapsingFlyout() {
         setUpBubbleBar()
-        setUpBubbleStashController()
 
         val handle = View(context)
         val handleAnimator = PhysicsAnimator.getInstance(handle)
-        whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+        bubbleStashController.handleAnimator = handleAnimator
 
         val animator =
             BubbleBarViewAnimator(
@@ -1228,17 +1268,16 @@
         assertThat(handle.translationY).isEqualTo(0)
         assertThat(bubbleBarView.alpha).isEqualTo(0)
         assertThat(animator.isAnimating).isFalse()
-        verify(bubbleStashController).stashBubbleBarImmediate()
+        assertThat(bubbleStashController.isStashed).isTrue()
     }
 
     @Test
     fun interruptAnimation_whileAnimatingOut_barToHandle() {
         setUpBubbleBar()
-        setUpBubbleStashController()
 
         val handle = View(context)
         val handleAnimator = PhysicsAnimator.getInstance(handle)
-        whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+        bubbleStashController.handleAnimator = handleAnimator
 
         val animator =
             BubbleBarViewAnimator(
@@ -1295,7 +1334,7 @@
             animator.animateBubbleInForStashed(updatedBubble, isExpanding = false)
         }
 
-        // since animation was interrupted there shouldn`t be additional calls to adjust window
+        // since animation was interrupted there shouldn't be additional calls to adjust window
         assertThat(bubbleBarParentViewController.timesInvoked).isEqualTo(1)
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync {}
@@ -1343,17 +1382,16 @@
         assertThat(handle.translationY).isEqualTo(0)
         assertThat(bubbleBarView.alpha).isEqualTo(0)
         assertThat(animator.isAnimating).isFalse()
-        verify(bubbleStashController).stashBubbleBarImmediate()
+        assertThat(bubbleStashController.isStashed).isTrue()
     }
 
     @Test
     fun interruptForIme() {
         setUpBubbleBar()
-        setUpBubbleStashController()
 
         val handle = View(context)
         val handleAnimator = PhysicsAnimator.getInstance(handle)
-        whenever(bubbleStashController.getStashedHandlePhysicsAnimator()).thenReturn(handleAnimator)
+        bubbleStashController.handleAnimator = handleAnimator
 
         val animator =
             BubbleBarViewAnimator(
@@ -1384,7 +1422,8 @@
         // verify that the hide animation was canceled
         assertThat(animatorScheduler.delayedBlock).isNull()
         assertThat(animator.isAnimating).isFalse()
-        verify(bubbleStashController).onNewBubbleAnimationInterrupted(eq(true), any())
+        assertThat(bubbleStashController.animationInterrupted).isTrue()
+        assertThat(bubbleStashController.isStashed).isTrue()
 
         // PhysicsAnimatorTestUtils posts the cancellation to the main thread so we need to wait
         // again
@@ -1437,17 +1476,6 @@
         InstrumentationRegistry.getInstrumentation().waitForIdleSync()
     }
 
-    private fun setUpBubbleStashController() {
-        bubbleStashController = mock<BubbleStashController>()
-        whenever(bubbleStashController.isStashed).thenReturn(true)
-        whenever(bubbleStashController.getDiffBetweenHandleAndBarCenters())
-            .thenReturn(DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS)
-        whenever(bubbleStashController.getStashedHandleTranslationForNewBubbleAnimation())
-            .thenReturn(HANDLE_TRANSLATION)
-        whenever(bubbleStashController.bubbleBarTranslationYForTaskbar)
-            .thenReturn(BAR_TRANSLATION_Y_FOR_TASKBAR)
-    }
-
     private fun setupFlyoutController() {
         flyoutContainer = FrameLayout(context)
         val flyoutPositioner =
@@ -1554,6 +1582,91 @@
             timesInvoked++
         }
     }
+
+    private class FakeBubbleStashController : BubbleStashController {
+
+        var handleAnimator: PhysicsAnimator<View>? = null
+        var taskbarTouchRegionUpdated = false
+            private set
+
+        var animationInterrupted = false
+            private set
+
+        private var _isStashed = true
+
+        override var launcherState = BubbleLauncherState.HOME
+        override val isStashed: Boolean
+            get() = _isStashed
+
+        override var bubbleBarVerticalCenterForHome = 0
+        override var isSysuiLocked = false
+        override val isTransientTaskBar = true
+        override val hasHandleView = true
+        override val bubbleBarTranslationYForTaskbar = BAR_TRANSLATION_Y_FOR_TASKBAR
+        override val bubbleBarTranslationYForHotseat = BAR_TRANSLATION_Y_FOR_HOTSEAT
+        override var inAppDisplayOverrideProgress = 0f
+
+        override fun init(
+            taskbarInsetsController: TaskbarInsetsController,
+            bubbleBarViewController: BubbleBarViewController,
+            bubbleStashedHandleViewController: BubbleStashedHandleViewController?,
+            controllersAfterInitAction: BubbleStashController.ControllersAfterInitAction,
+        ) {}
+
+        override fun showBubbleBarImmediate() {
+            _isStashed = false
+        }
+
+        override fun showBubbleBarImmediate(bubbleBarTranslationY: Float) {
+            _isStashed = false
+        }
+
+        override fun stashBubbleBarImmediate() {
+            _isStashed = true
+        }
+
+        override fun getTouchableHeight() = 100
+
+        override fun isBubbleBarVisible() = true
+
+        override fun onNewBubbleAnimationInterrupted(
+            isStashed: Boolean,
+            bubbleBarTranslationY: Float,
+        ) {
+            _isStashed = isStashed
+            animationInterrupted = true
+        }
+
+        override fun isEventOverBubbleBarViews(ev: MotionEvent) = false
+
+        override fun setBubbleBarLocation(bubbleBarLocation: BubbleBarLocation) {}
+
+        override fun stashBubbleBar() {
+            _isStashed = true
+        }
+
+        override fun showBubbleBar(expandBubbles: Boolean, bubbleBarGesture: Boolean) {
+            _isStashed = false
+        }
+
+        override fun getDiffBetweenHandleAndBarCenters() = DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS
+
+        override fun getStashedHandleTranslationForNewBubbleAnimation() = HANDLE_TRANSLATION
+
+        override fun getStashedHandlePhysicsAnimator(): PhysicsAnimator<View>? {
+            return handleAnimator
+        }
+
+        override fun updateTaskbarTouchRegion() {
+            taskbarTouchRegionUpdated = true
+        }
+
+        override fun setHandleTranslationY(translationY: Float) {}
+
+        override fun getHandleTranslationY() = 0f
+
+        override fun getHandleBounds(bounds: Rect) {}
+    }
 }
 
 private const val DIFF_BETWEEN_HANDLE_AND_BAR_CENTERS = -20f
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
index f642345..b24926b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashControllerTest.kt
@@ -507,6 +507,45 @@
         assertThat(height).isEqualTo(BUBBLE_BAR_HEIGHT)
     }
 
+    @Test
+    fun getHandleViewAlpha_stashedHasBubbles_alphaPropertyReturned() {
+        // Given BubbleBar is stashed and has bubbles
+        whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
+        mTransientBubbleStashController.isStashed = true
+
+        // When handle view alpha property
+        val alphaProperty = mTransientBubbleStashController.getHandleViewAlpha()
+
+        // Then the stash handle alpha property should not be null
+        assertThat(alphaProperty).isNotNull()
+    }
+
+    @Test
+    fun getHandleViewAlpha_stashedHasNoBubblesBar_alphaPropertyIsNull() {
+        // Given BubbleBar is stashed and has no bubbles
+        whenever(bubbleBarViewController.hasBubbles()).thenReturn(false)
+        mTransientBubbleStashController.isStashed = true
+
+        // When handle view alpha property
+        val alphaProperty = mTransientBubbleStashController.getHandleViewAlpha()
+
+        // Then the stash handle alpha property should be null
+        assertThat(alphaProperty).isNull()
+    }
+
+    @Test
+    fun getHandleViewAlpha_unstashedHasBubbles_alphaPropertyIsNull() {
+        // Given BubbleBar is not stashed and has bubbles
+        whenever(bubbleBarViewController.hasBubbles()).thenReturn(true)
+        mTransientBubbleStashController.isStashed = false
+
+        // When handle view alpha property
+        val alphaProperty = mTransientBubbleStashController.getHandleViewAlpha()
+
+        // Then the stash handle alpha property should be null
+        assertThat(alphaProperty).isNull()
+    }
+
     private fun advanceTimeBy(advanceMs: Long) {
         // Advance animator for on-device tests
         getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(advanceMs) }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt
new file mode 100644
index 0000000..5f7b360
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelHelper.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar.rules
+
+import com.android.quickstep.RecentsModel
+import com.android.quickstep.RecentsModel.RecentTasksChangedListener
+import com.android.quickstep.TaskIconCache
+import com.android.quickstep.TaskThumbnailCache
+import com.android.quickstep.util.GroupTask
+import java.util.function.Consumer
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+/** Helper class to mock the [RecentsModel] object in test */
+class MockedRecentsModelHelper {
+    private val mockIconCache: TaskIconCache = mock()
+    private val mockThumbnailCache: TaskThumbnailCache = mock()
+
+    var taskListId = 0
+    var recentTasksChangedListener: RecentTasksChangedListener? = null
+    var taskRequests: MutableList<(List<GroupTask>) -> Unit> = mutableListOf()
+
+    val mockRecentsModel: RecentsModel = mock {
+        on { iconCache } doReturn mockIconCache
+
+        on { thumbnailCache } doReturn mockThumbnailCache
+
+        on { unregisterRecentTasksChangedListener() } doAnswer { recentTasksChangedListener = null }
+
+        on { registerRecentTasksChangedListener(any<RecentTasksChangedListener>()) } doAnswer
+            {
+                recentTasksChangedListener = it.getArgument<RecentTasksChangedListener>(0)
+            }
+
+        on { getTasks(anyOrNull(), anyOrNull()) } doAnswer
+            {
+                val request = it.getArgument<Consumer<List<GroupTask>>?>(0)
+                if (request != null) {
+                    taskRequests.add { response -> request.accept(response) }
+                }
+                taskListId
+            }
+
+        on { getTasks(anyOrNull()) } doAnswer
+            {
+                val request = it.getArgument<Consumer<List<GroupTask>>?>(0)
+                if (request != null) {
+                    taskRequests.add { response -> request.accept(response) }
+                }
+                taskListId
+            }
+
+        on { isTaskListValid(any()) } doAnswer { taskListId == it.getArgument(0) }
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt
index ed1443d..359b876 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/MockedRecentsModelTestRule.kt
@@ -16,64 +16,17 @@
 
 package com.android.launcher3.taskbar.rules
 
-import com.android.quickstep.RecentsModel
-import com.android.quickstep.RecentsModel.RecentTasksChangedListener
-import com.android.quickstep.TaskIconCache
 import com.android.quickstep.util.GroupTask
-import java.util.function.Consumer
 import org.junit.rules.TestRule
 import org.junit.runner.Description
 import org.junit.runners.model.Statement
-import org.mockito.kotlin.any
-import org.mockito.kotlin.anyOrNull
-import org.mockito.kotlin.doAnswer
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.mock
 
-class MockedRecentsModelTestRule(private val context: TaskbarWindowSandboxContext) : TestRule {
-
-    private val mockIconCache: TaskIconCache = mock()
-
-    private val mockRecentsModel: RecentsModel = mock {
-        on { iconCache } doReturn mockIconCache
-
-        on { unregisterRecentTasksChangedListener() } doAnswer { recentTasksChangedListener = null }
-
-        on { registerRecentTasksChangedListener(any<RecentTasksChangedListener>()) } doAnswer
-            {
-                recentTasksChangedListener = it.getArgument<RecentTasksChangedListener>(0)
-            }
-
-        on { getTasks(anyOrNull(), anyOrNull()) } doAnswer
-            {
-                val request = it.getArgument<Consumer<List<GroupTask>>?>(0)
-                if (request != null) {
-                    taskRequests.add { response -> request.accept(response) }
-                }
-                taskListId
-            }
-
-        on { getTasks(anyOrNull()) } doAnswer
-            {
-                val request = it.getArgument<Consumer<List<GroupTask>>?>(0)
-                if (request != null) {
-                    taskRequests.add { response -> request.accept(response) }
-                }
-                taskListId
-            }
-
-        on { isTaskListValid(any()) } doAnswer { taskListId == it.getArgument(0) }
-    }
-
+class MockedRecentsModelTestRule(private val modelHelper: MockedRecentsModelHelper) : TestRule {
     private var recentTasks: List<GroupTask> = emptyList()
-    private var taskListId = 0
-    private var recentTasksChangedListener: RecentTasksChangedListener? = null
-    private var taskRequests: MutableList<(List<GroupTask>) -> Unit> = mutableListOf()
 
     override fun apply(base: Statement?, description: Description?): Statement {
         return object : Statement() {
             override fun evaluate() {
-                context.putObject(RecentsModel.INSTANCE, mockRecentsModel)
                 base?.evaluate()
             }
         }
@@ -82,15 +35,15 @@
     // NOTE: For the update to take effect, `resolvePendingTaskRequests()` needs to be called, so
     // calbacks to any pending `RecentsModel.getTasks()` get called with the updated task list.
     fun updateRecentTasks(tasks: List<GroupTask>) {
-        ++taskListId
+        ++modelHelper.taskListId
         recentTasks = tasks
-        recentTasksChangedListener?.onRecentTasksChanged()
+        modelHelper.recentTasksChangedListener?.onRecentTasksChanged()
     }
 
     fun resolvePendingTaskRequests() {
         val requests = mutableListOf<(List<GroupTask>) -> Unit>()
-        requests.addAll(taskRequests)
-        taskRequests.clear()
+        requests.addAll(modelHelper.taskRequests)
+        modelHelper.taskRequests.clear()
 
         requests.forEach { it(recentTasks) }
     }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
index 74b154a..f225807 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarModeRule.kt
@@ -20,7 +20,6 @@
 import com.android.launcher3.taskbar.rules.TaskbarModeRule.Mode
 import com.android.launcher3.taskbar.rules.TaskbarModeRule.TaskbarMode
 import com.android.launcher3.util.DisplayController
-import com.android.launcher3.util.MainThreadInitializedObject
 import com.android.launcher3.util.NavigationMode
 import org.junit.rules.TestRule
 import org.junit.runner.Description
@@ -31,8 +30,8 @@
 /**
  * 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.
+ * [context] should match the test's target context, so that Dagger singleton 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.
@@ -61,11 +60,10 @@
                 val mode = taskbarMode.mode
 
                 getInstrumentation().runOnMainSync {
-                    context.putObject(
-                        DisplayController.INSTANCE,
-                        object : DisplayController(context) {
-                            override fun getInfo(): Info {
-                                return spy(super.getInfo()) {
+                    DisplayController.INSTANCE[context].let {
+                        if (it is DisplayControllerSpy) {
+                            it.infoModifier = { info ->
+                                spy(info) {
                                     on { isTransientTaskbar } doReturn (mode == Mode.TRANSIENT)
                                     on { isPinnedTaskbar } doReturn (mode == Mode.PINNED)
                                     on { navigationMode } doReturn
@@ -76,8 +74,8 @@
                                         }
                                 }
                             }
-                        },
-                    )
+                        }
+                    }
                 }
 
                 base.evaluate()
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
index cd4e78b..2dacf69 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarUnitTestRule.kt
@@ -24,17 +24,16 @@
 import android.provider.Settings.Secure.getUriFor
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.launcher3.LauncherAppState
-import com.android.launcher3.statehandlers.DesktopVisibilityController
 import com.android.launcher3.taskbar.TaskbarActivityContext
 import com.android.launcher3.taskbar.TaskbarControllers
 import com.android.launcher3.taskbar.TaskbarManager
 import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarNavButtonCallbacks
-import com.android.launcher3.taskbar.TaskbarViewController
 import com.android.launcher3.taskbar.bubbles.BubbleControllers
 import com.android.launcher3.taskbar.rules.TaskbarUnitTestRule.InjectController
 import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
 import com.android.launcher3.util.TestUtil
 import com.android.quickstep.AllAppsActionManager
+import com.android.quickstep.fallback.window.RecentsDisplayModel
 import java.lang.reflect.Field
 import java.lang.reflect.ParameterizedType
 import java.util.Locale
@@ -73,6 +72,7 @@
 class TaskbarUnitTestRule(
     private val testInstance: Any,
     private val context: TaskbarWindowSandboxContext,
+    private val controllerInjectionCallback: () -> Unit = {},
 ) : TestRule {
 
     private val instrumentation = InstrumentationRegistry.getInstrumentation()
@@ -111,11 +111,14 @@
                                     PendingIntent(IIntentSender.Default())
                                 },
                                 object : TaskbarNavButtonCallbacks {},
-                                DesktopVisibilityController(context),
+                                RecentsDisplayModel.INSTANCE.get(context),
                             ) {
-                            override fun recreateTaskbar() {
-                                super.recreateTaskbar()
-                                if (currentActivityContext != null) injectControllers()
+                            override fun recreateTaskbars() {
+                                super.recreateTaskbars()
+                                if (currentActivityContext != null) {
+                                    injectControllers()
+                                    controllerInjectionCallback.invoke()
+                                }
                             }
                         }
                     }
@@ -124,28 +127,26 @@
                     // Needs to be set on window context instead of sandbox context, because it does
                     // does not propagate between them. However, this change will impact created
                     // TaskbarActivityContext instances, since they wrap the window context.
-                    taskbarManager.windowContext.resources.configuration.setLayoutDirection(
+                    // TODO: iterate through all window contexts and do this.
+                    taskbarManager.primaryWindowContext.resources.configuration.setLayoutDirection(
                         RTL_LOCALE
                     )
                 }
 
                 try {
-                    TaskbarViewController.enableModelLoadingForTests(false)
-
                     // Required to complete initialization.
                     instrumentation.runOnMainSync { taskbarManager.onUserUnlocked() }
 
                     base.evaluate()
                 } finally {
                     instrumentation.runOnMainSync { taskbarManager.destroy() }
-                    TaskbarViewController.enableModelLoadingForTests(true)
                 }
             }
         }
     }
 
     /** Simulates Taskbar recreation lifecycle. */
-    fun recreateTaskbar() = instrumentation.runOnMainSync { taskbarManager.recreateTaskbar() }
+    fun recreateTaskbar() = instrumentation.runOnMainSync { taskbarManager.recreateTaskbars() }
 
     private fun injectControllers() {
         val bubbleControllerTypes =
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
index 8c51216..d96e06e 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/taskbar/rules/TaskbarWindowSandboxContext.kt
@@ -22,26 +22,32 @@
 import android.hardware.display.VirtualDisplay
 import android.view.Display.DEFAULT_DISPLAY
 import androidx.test.core.app.ApplicationProvider
-import com.android.launcher3.FakeLauncherPrefs
 import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.dagger.ApplicationContext
 import com.android.launcher3.dagger.LauncherAppComponent
 import com.android.launcher3.dagger.LauncherAppSingleton
-import com.android.launcher3.util.MainThreadInitializedObject.ObjectSandbox
+import com.android.launcher3.statehandlers.DesktopVisibilityController
+import com.android.launcher3.util.AllModulesMinusWMProxy
+import com.android.launcher3.util.DaggerSingletonTracker
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.FakePrefsModule
 import com.android.launcher3.util.SandboxApplication
 import com.android.launcher3.util.SettingsCache
 import com.android.launcher3.util.SettingsCacheSandbox
+import com.android.launcher3.util.window.WindowManagerProxy
 import com.android.quickstep.SystemUiProxy
+import dagger.Binds
 import dagger.BindsInstance
 import dagger.Component
+import dagger.Module
+import dagger.Provides
+import javax.inject.Inject
 import org.junit.rules.ExternalResource
 import org.junit.rules.RuleChain
 import org.junit.rules.TestRule
 import org.junit.runner.Description
 import org.junit.runners.model.Statement
-
-/** Include additional bindings when building a [TaskbarSandboxComponent]. */
-typealias TaskbarComponentBinder =
-    TaskbarWindowSandboxContext.(TaskbarSandboxComponent.Builder) -> Unit
+import org.mockito.kotlin.spy
 
 /**
  * [SandboxApplication] for running Taskbar tests.
@@ -53,8 +59,8 @@
 private constructor(
     private val base: SandboxApplication,
     val virtualDisplay: VirtualDisplay,
-    private val componentBinder: TaskbarComponentBinder?,
-) : ContextWrapper(base), ObjectSandbox by base, TestRule {
+    private val params: SandboxParams,
+) : ContextWrapper(base), TestRule {
 
     val settingsCacheSandbox = SettingsCacheSandbox()
 
@@ -68,13 +74,10 @@
             override fun before() {
                 val context = this@TaskbarWindowSandboxContext
                 val builder =
-                    DaggerTaskbarSandboxComponent.builder()
-                        .bindSystemUiProxy(SystemUiProxy(context))
+                    params.builderBase
+                        .bindSystemUiProxy(params.systemUiProxyProvider.invoke(context))
                         .bindSettingsCache(settingsCacheSandbox.cache)
-                componentBinder?.invoke(context, builder)
                 base.initDaggerComponent(builder)
-
-                putObject(LauncherPrefs.INSTANCE, FakeLauncherPrefs(context))
             }
         }
 
@@ -89,10 +92,9 @@
         private const val VIRTUAL_DISPLAY_NAME = "TaskbarSandboxDisplay"
 
         /** Creates a [SandboxApplication] for Taskbar tests. */
-        fun create(componentBinder: TaskbarComponentBinder? = null): TaskbarWindowSandboxContext {
+        fun create(params: SandboxParams = SandboxParams()): TaskbarWindowSandboxContext {
             val base = ApplicationProvider.getApplicationContext<Context>()
             val displayManager = checkNotNull(base.getSystemService(DisplayManager::class.java))
-
             // Create virtual display to avoid clashing with Taskbar on default display.
             val virtualDisplay =
                 base.resources.displayMetrics.let {
@@ -109,15 +111,60 @@
             return TaskbarWindowSandboxContext(
                 SandboxApplication(base.createDisplayContext(virtualDisplay.display)),
                 virtualDisplay,
-                componentBinder,
+                params,
             )
         }
     }
 }
 
+/** A wrapper over display controller which allows modifying the underlying info */
 @LauncherAppSingleton
-@Component
+class DisplayControllerSpy
+@Inject
+constructor(
+    @ApplicationContext context: Context,
+    wmProxy: WindowManagerProxy,
+    prefs: LauncherPrefs,
+    lifecycle: DaggerSingletonTracker,
+) : DisplayController(context, wmProxy, prefs, lifecycle) {
+
+    var infoModifier: ((Info) -> Info)? = null
+
+    override fun getInfo(): Info = infoModifier?.invoke(super.getInfo()) ?: super.getInfo()
+}
+
+@Module
+abstract class DisplayControllerModule {
+    @Binds abstract fun bindDisplayController(controller: DisplayControllerSpy): DisplayController
+}
+
+@Module
+object DesktopVisibilityControllerModule {
+    @JvmStatic
+    @Provides
+    @LauncherAppSingleton
+    fun provideDesktopVisibilityController(
+        @ApplicationContext context: Context,
+        systemUiProxy: SystemUiProxy,
+        lifecycleTracker: DaggerSingletonTracker,
+    ): DesktopVisibilityController {
+        return spy(DesktopVisibilityController(context, systemUiProxy, lifecycleTracker))
+    }
+}
+
+@LauncherAppSingleton
+@Component(
+    modules =
+        [
+            AllModulesMinusWMProxy::class,
+            FakePrefsModule::class,
+            DisplayControllerModule::class,
+            TaskbarSandboxModule::class,
+            DesktopVisibilityControllerModule::class,
+        ]
+)
 interface TaskbarSandboxComponent : LauncherAppComponent {
+
     @Component.Builder
     interface Builder : LauncherAppComponent.Builder {
         @BindsInstance fun bindSystemUiProxy(proxy: SystemUiProxy): Builder
@@ -127,3 +174,9 @@
         override fun build(): TaskbarSandboxComponent
     }
 }
+
+/** Include additional bindings when building a [TaskbarSandboxComponent]. */
+data class SandboxParams(
+    val systemUiProxyProvider: (Context) -> SystemUiProxy = { SystemUiProxy(it) },
+    val builderBase: TaskbarSandboxComponent.Builder = DaggerTaskbarSandboxComponent.builder(),
+)
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt
index dcd5352..52238c8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/util/SettingsCacheSandbox.kt
@@ -17,19 +17,22 @@
 package com.android.launcher3.util
 
 import android.net.Uri
+import com.android.launcher3.util.SettingsCache.OnChangeListener
 import org.mockito.kotlin.any
 import org.mockito.kotlin.doAnswer
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
 
-/**
- * Provides a sandboxed [SettingsCache] for testing.
- *
- * Note that listeners registered to [cache] will never be invoked.
- */
+/** Provides [SettingsCache] sandboxed from system settings for testing. */
 class SettingsCacheSandbox {
     private val values = mutableMapOf<Uri, Int>()
+    private val listeners = mutableMapOf<Uri, MutableSet<OnChangeListener>>()
 
-    /** Fake cache that delegates [SettingsCache.getValue] to [values]. */
+    /**
+     * Fake cache that delegates:
+     * - [SettingsCache.getValue] to [values]
+     * - [SettingsCache.mListenerMap] to [listeners].
+     */
     val cache =
         mock<SettingsCache> {
             on { getValue(any<Uri>()) } doAnswer { mock.getValue(it.getArgument(0), 1) }
@@ -37,11 +40,22 @@
                 {
                     values.getOrDefault(it.getArgument(0), it.getArgument(1)) == 1
                 }
+
+            doAnswer {
+                    listeners.getOrPut(it.getArgument(0)) { mutableSetOf() }.add(it.getArgument(1))
+                }
+                .whenever(mock)
+                .register(any(), any())
+            doAnswer { listeners[it.getArgument(0)]?.remove(it.getArgument(1)) }
+                .whenever(mock)
+                .unregister(any(), any())
         }
 
     operator fun get(key: Uri): Int? = values[key]
 
     operator fun set(key: Uri, value: Int) {
+        if (value == values[key]) return
         values[key] = value
+        listeners[key]?.forEach { it.onSettingsChanged(value == 1) }
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetCategoryFilterTest.kt b/quickstep/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetCategoryFilterTest.kt
new file mode 100644
index 0000000..9b0a95a
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetCategoryFilterTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.widget.picker
+
+import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
+import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
+import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_NOT_KEYGUARD
+import android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class WidgetCategoryFilterTest {
+
+    @Test
+    fun filterValueZero_everythingMatches() {
+        val noFilter = WidgetCategoryFilter(categoryMask = 0)
+
+        noFilter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN)
+        noFilter.assertMatches(WIDGET_CATEGORY_KEYGUARD)
+        noFilter.assertMatches(WIDGET_CATEGORY_NOT_KEYGUARD)
+        noFilter.assertMatches(WIDGET_CATEGORY_SEARCHBOX)
+        noFilter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD)
+        noFilter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_NOT_KEYGUARD)
+        noFilter.assertMatches(
+            WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_NOT_KEYGUARD
+        )
+        noFilter.assertMatches(
+            WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_NOT_KEYGUARD
+        )
+        noFilter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_KEYGUARD)
+    }
+
+    @Test
+    fun includeHomeScreen_matchesOnlyIfHomeScreenExists() {
+        val filter = WidgetCategoryFilter(WIDGET_CATEGORY_HOME_SCREEN)
+
+        filter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN)
+        filter.assertMatches(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+        filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_HOME_SCREEN)
+        filter.assertMatches(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_KEYGUARD)
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD)
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX)
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_SEARCHBOX)
+    }
+
+    @Test
+    fun includeHomeScreenOrKeyguard_matchesIfEitherHomeScreenOrKeyguardExists() {
+        val filter = WidgetCategoryFilter(WIDGET_CATEGORY_HOME_SCREEN or WIDGET_CATEGORY_KEYGUARD)
+
+        filter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN)
+        filter.assertMatches(WIDGET_CATEGORY_KEYGUARD)
+        filter.assertMatches(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+        filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_HOME_SCREEN)
+        filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_KEYGUARD)
+        filter.assertMatches(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+        filter.assertMatches(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD)
+
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD)
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX)
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_NOT_KEYGUARD)
+    }
+
+    @Test
+    fun excludeNotKeyguard_doesNotMatchIfNotKeyguardExists() {
+        val filter = WidgetCategoryFilter(WIDGET_CATEGORY_NOT_KEYGUARD.inv())
+
+        filter.assertMatches(WIDGET_CATEGORY_HOME_SCREEN)
+        filter.assertMatches(WIDGET_CATEGORY_KEYGUARD)
+        filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX)
+        filter.assertMatches(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+        filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_HOME_SCREEN)
+        filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_KEYGUARD)
+
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD)
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD)
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_NOT_KEYGUARD)
+        filter.assertDoesNotMatch(
+            WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN
+        )
+    }
+
+    @Test
+    fun multipleExclusions_doesNotMatchIfExcludedCategoriesExist() {
+        val filter =
+            WidgetCategoryFilter(
+                WIDGET_CATEGORY_HOME_SCREEN.inv() and WIDGET_CATEGORY_NOT_KEYGUARD.inv()
+            )
+
+        filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX)
+        filter.assertMatches(WIDGET_CATEGORY_KEYGUARD)
+        filter.assertMatches(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_KEYGUARD)
+
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_HOME_SCREEN)
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_HOME_SCREEN)
+
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD)
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN)
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD)
+        filter.assertDoesNotMatch(WIDGET_CATEGORY_SEARCHBOX or WIDGET_CATEGORY_NOT_KEYGUARD)
+        filter.assertDoesNotMatch(
+            WIDGET_CATEGORY_NOT_KEYGUARD or WIDGET_CATEGORY_KEYGUARD or WIDGET_CATEGORY_HOME_SCREEN
+        )
+    }
+
+    private fun WidgetCategoryFilter.assertMatches(category: Int) {
+        assertThat(matches(category)).isTrue()
+    }
+
+    private fun WidgetCategoryFilter.assertDoesNotMatch(category: Int) {
+        assertThat(matches(category)).isFalse()
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
index 970bdec..6fbbd59 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
@@ -18,6 +18,8 @@
 
 import static com.android.quickstep.AbsSwipeUpHandler.STATE_HANDLER_INVALIDATED;
 import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
+import static com.android.wm.shell.shared.split.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
 
 import static junit.framework.TestCase.assertFalse;
 import static junit.framework.TestCase.assertTrue;
@@ -30,7 +32,9 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.animation.ValueAnimator;
@@ -50,7 +54,6 @@
 import android.view.ViewTreeObserver;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.launcher3.DeviceProfile;
@@ -58,13 +61,18 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulContainer;
+import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.SystemUiController;
-import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.util.ContextInitListener;
+import com.android.quickstep.util.MotionPauseDetector;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.RecentsViewContainer;
 import com.android.systemui.shared.Flags;
 import com.android.systemui.shared.system.InputConsumerController;
+import com.android.wm.shell.shared.split.SplitBounds;
+
+import com.google.android.msdl.data.model.MSDLToken;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -84,8 +92,9 @@
         SWIPE_HANDLER extends AbsSwipeUpHandler<RECENTS_CONTAINER, RECENTS_VIEW, STATE_TYPE>,
         CONTAINER_INTERFACE extends BaseContainerInterface<STATE_TYPE, RECENTS_CONTAINER>> {
 
-    protected final Context mContext =
-            InstrumentationRegistry.getInstrumentation().getTargetContext();
+    protected final LauncherModelHelper mLauncherModelHelper = new LauncherModelHelper();
+    protected final LauncherModelHelper.SandboxModelContext mContext =
+            mLauncherModelHelper.sandboxContext;
     protected final InputConsumerController mInputConsumerController =
             InputConsumerController.getRecentsAnimationInputConsumer();
     protected final ActivityManager.RunningTaskInfo mRunningTaskInfo =
@@ -112,7 +121,6 @@
 
     protected RecentsAnimationTargets mRecentsAnimationTargets;
     protected TaskAnimationManager mTaskAnimationManager;
-    protected RecentsAnimationDeviceState mRecentsAnimationDeviceState;
 
     @Mock protected CONTAINER_INTERFACE mActivityInterface;
     @Mock protected ContextInitListener<?> mContextInitListener;
@@ -123,6 +131,7 @@
     @Mock protected LauncherRootView mRootView;
     @Mock protected SystemUiController mSystemUiController;
     @Mock protected GestureState mGestureState;
+    @Mock protected MSDLPlayerWrapper mMSDLPlayerWrapper;
 
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@@ -134,6 +143,12 @@
     public void setUpAnimationTargets() {
         Bundle extras = new Bundle();
         extras.putBoolean(KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION, true);
+        extras.putParcelable(KEY_EXTRA_SPLIT_BOUNDS, new SplitBounds(
+                /* leftTopBounds = */ new Rect(),
+                /* rightBottomBounds = */ new Rect(),
+                /* leftTopTaskId = */ -1,
+                /* rightBottomTaskId = */ -1,
+                /* snapPosition = */ SNAP_TO_2_50_50));
         mRecentsAnimationTargets = new RecentsAnimationTargets(
                 new RemoteAnimationTarget[] {mRemoteAnimationTarget},
                 new RemoteAnimationTarget[] {mRemoteAnimationTarget},
@@ -173,7 +188,8 @@
 
     @Before
     public void setUpRecentsContainer() {
-        mTaskAnimationManager = new TaskAnimationManager(mContext, getRecentsWindowManager());
+        mTaskAnimationManager = new TaskAnimationManager(mContext,
+                RecentsAnimationDeviceState.INSTANCE.get(mContext));
         RecentsViewContainer recentsContainer = getRecentsContainer();
         RECENTS_VIEW recentsView = getRecentsView();
 
@@ -191,12 +207,6 @@
         }).when(recentsContainer).runOnBindToTouchInteractionService(any());
     }
 
-    @Before
-    public void setUpRecentsAnimationDeviceState() {
-        runOnMainSync(() ->
-                mRecentsAnimationDeviceState = new RecentsAnimationDeviceState(mContext, true));
-    }
-
     @Test
     public void testInitWhenReady_registersActivityInitListener() {
         String reasonString = "because i said so";
@@ -304,6 +314,17 @@
         });
     }
 
+    @Test
+    @EnableFlags(com.android.launcher3.Flags.FLAG_MSDL_FEEDBACK)
+    public void onMotionPauseDetected_playsSwipeThresholdToken() {
+        SWIPE_HANDLER handler = createSwipeHandler();
+        MotionPauseDetector.OnMotionPauseListener listener = handler.getMotionPauseListener();
+        listener.onMotionPauseDetected();
+
+        verify(mMSDLPlayerWrapper, times(1)).playToken(eq(MSDLToken.SWIPE_THRESHOLD_INDICATOR));
+        verifyNoMoreInteractions(mMSDLPlayerWrapper);
+    }
+
     /**
      * Verifies that RecentsAnimationController#finish() is called, and captures and runs any
      * callback that was passed to it. This ensures that STATE_CURRENT_TASK_FINISHED is correctly
@@ -343,7 +364,7 @@
         float xVelocityPxPerMs = isQuickSwitch ? 100 : 0;
         float yVelocityPxPerMs = isQuickSwitch ? 0 : -100;
         swipeHandler.onGestureEnded(
-                yVelocityPxPerMs, new PointF(xVelocityPxPerMs, yVelocityPxPerMs));
+                yVelocityPxPerMs, new PointF(xVelocityPxPerMs, yVelocityPxPerMs), isQuickSwitch);
         swipeHandler.onCalculateEndTarget();
         runOnMainSync(swipeHandler::onSettledOnEndTarget);
 
@@ -352,7 +373,7 @@
 
     private void onRecentsAnimationStart(SWIPE_HANDLER absSwipeUpHandler) {
         runOnMainSync(() -> absSwipeUpHandler.onRecentsAnimationStart(
-                mRecentsAnimationController, mRecentsAnimationTargets));
+                mRecentsAnimationController, mRecentsAnimationTargets, /* transitionInfo= */null));
     }
 
     protected static void runOnMainSync(Runnable runnable) {
@@ -364,11 +385,6 @@
         return createSwipeHandler(SystemClock.uptimeMillis(), false);
     }
 
-    @Nullable
-    protected RecentsWindowManager getRecentsWindowManager() {
-        return null;
-    }
-
     @NonNull
     protected abstract SWIPE_HANDLER createSwipeHandler(
             long touchTimeMs, boolean continuingLastGesture);
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt
index 73b35e8..a1bd107 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AllAppsActionManagerTest.kt
@@ -18,32 +18,59 @@
 
 import android.app.PendingIntent
 import android.content.IIntentSender
+import android.provider.Settings
+import android.provider.Settings.Secure.USER_SETUP_COMPLETE
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.AllModulesForTest
 import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import com.android.launcher3.util.SandboxApplication
+import com.android.launcher3.util.SettingsCache
+import com.android.launcher3.util.SettingsCacheSandbox
 import com.android.launcher3.util.TestUtil
 import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
 import java.util.concurrent.Semaphore
 import java.util.concurrent.TimeUnit.SECONDS
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
 private const val TIMEOUT = 5L
+private val USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(USER_SETUP_COMPLETE)
 
 @RunWith(AndroidJUnit4::class)
 class AllAppsActionManagerTest {
     private val callbackSemaphore = Semaphore(0)
     private val bgExecutor = UI_HELPER_EXECUTOR
 
-    private val allAppsActionManager =
-        AllAppsActionManager(
-            InstrumentationRegistry.getInstrumentation().targetContext,
-            bgExecutor,
-        ) {
-            callbackSemaphore.release()
-            PendingIntent(IIntentSender.Default())
+    @get:Rule val context = SandboxApplication()
+
+    private val settingsCacheSandbox =
+        SettingsCacheSandbox().also { it[USER_SETUP_COMPLETE_URI] = 1 }
+
+    private val allAppsActionManager by
+        lazy(LazyThreadSafetyMode.NONE) {
+            AllAppsActionManager(context, bgExecutor) {
+                callbackSemaphore.release()
+                PendingIntent(IIntentSender.Default())
+            }
         }
 
+    @Before
+    fun initDaggerComponent() {
+        context.initDaggerComponent(
+            DaggerAllAppsActionManagerTestComponent.builder()
+                .bindSettingsCache(settingsCacheSandbox.cache)
+        )
+    }
+
+    @After fun destroyManager() = allAppsActionManager.onDestroy()
+
     @Test
     fun taskbarPresent_actionRegistered() {
         allAppsActionManager.isTaskbarPresent = true
@@ -88,4 +115,50 @@
         assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
         assertThat(allAppsActionManager.isActionRegistered).isTrue()
     }
+
+    @Test
+    fun taskbarPresent_userSetupIncomplete_actionUnregistered() {
+        settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 0
+        allAppsActionManager.isTaskbarPresent = true
+        assertThat(allAppsActionManager.isActionRegistered).isFalse()
+    }
+
+    @Test
+    fun taskbarPresent_setupUiVisible_actionUnregistered() {
+        allAppsActionManager.isSetupUiVisible = true
+        allAppsActionManager.isTaskbarPresent = true
+        assertThat(allAppsActionManager.isActionRegistered).isFalse()
+    }
+
+    @Test
+    fun taskbarPresent_userSetupCompleted_actionRegistered() {
+        settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 0
+        allAppsActionManager.isTaskbarPresent = true
+
+        settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 1
+        assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
+        assertThat(allAppsActionManager.isActionRegistered).isTrue()
+    }
+
+    @Test
+    fun taskbarPresent_setupUiDismissed_actionRegistered() {
+        allAppsActionManager.isSetupUiVisible = true
+        allAppsActionManager.isTaskbarPresent = true
+
+        allAppsActionManager.isSetupUiVisible = false
+        assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
+        assertThat(allAppsActionManager.isActionRegistered).isTrue()
+    }
+}
+
+@LauncherAppSingleton
+@Component(modules = [AllModulesForTest::class])
+interface AllAppsActionManagerTestComponent : LauncherAppComponent {
+
+    @Component.Builder
+    interface Builder : LauncherAppComponent.Builder {
+        @BindsInstance fun bindSettingsCache(settingsCache: SettingsCache): Builder
+
+        override fun build(): AllAppsActionManagerTestComponent
+    }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/DisplayModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/DisplayModelTest.kt
new file mode 100644
index 0000000..fa7907f
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/DisplayModelTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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 android.content.Context
+import android.view.Display
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import java.io.PrintWriter
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertNull
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DisplayModelTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    class TestableResource : DisplayModel.DisplayResource() {
+        var isCleanupCalled = false
+
+        override fun cleanup() {
+            isCleanupCalled = true
+        }
+
+        override fun dump(prefix: String, writer: PrintWriter) {
+            // No-Op
+        }
+    }
+
+    private val testableDisplayModel =
+        object : DisplayModel<TestableResource>(context) {
+            override fun createDisplayResource(display: Display): TestableResource {
+                return TestableResource()
+            }
+        }
+
+    @Test
+    fun testCreate() {
+        testableDisplayModel.storeDisplayResource(Display.DEFAULT_DISPLAY)
+        val resource = testableDisplayModel.getDisplayResource(Display.DEFAULT_DISPLAY)
+        assertNotNull(resource)
+    }
+
+    @Test
+    fun testCleanAndDelete() {
+        testableDisplayModel.storeDisplayResource(Display.DEFAULT_DISPLAY)
+        val resource = testableDisplayModel.getDisplayResource(Display.DEFAULT_DISPLAY)!!
+        assertNotNull(resource)
+        testableDisplayModel.deleteDisplayResource(Display.DEFAULT_DISPLAY)
+        assert(resource.isCleanupCalled)
+        assertNull(testableDisplayModel.getDisplayResource(Display.DEFAULT_DISPLAY))
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
index 88197e5..3489519 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/FallbackSwipeHandlerTestCase.java
@@ -44,12 +44,12 @@
             long touchTimeMs, boolean continuingLastGesture) {
         return new FallbackSwipeHandler(
                 mContext,
-                mRecentsAnimationDeviceState,
                 mTaskAnimationManager,
                 mGestureState,
                 touchTimeMs,
                 continuingLastGesture,
-                mInputConsumerController);
+                mInputConsumerController,
+                mMSDLPlayerWrapper);
     }
 
     @NonNull
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
index 36c2f23..5661dcf 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2Test.kt
@@ -17,13 +17,20 @@
 package com.android.quickstep
 
 import android.graphics.PointF
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManagerGlobal
+import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+import android.view.DisplayInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.launcher3.R
 import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppModule
 import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.util.LauncherModelHelper
-import com.android.quickstep.dagger.QuickStepModule
+import com.android.launcher3.util.MSDLPlayerWrapper
 import com.android.systemui.contextualeducation.GestureType
 import com.android.systemui.shared.system.InputConsumerController
 import dagger.BindsInstance
@@ -51,6 +58,8 @@
 
     @Mock private lateinit var systemUiProxy: SystemUiProxy
 
+    @Mock private lateinit var msdlPlayerWrapper: MSDLPlayerWrapper
+
     private lateinit var underTest: LauncherSwipeHandlerV2
 
     @get:Rule val mockitoRule = MockitoJUnit.rule()
@@ -61,24 +70,45 @@
     private val flingSpeed =
         -(sandboxContext.resources.getDimension(R.dimen.quickstep_fling_threshold_speed) + 1)
 
+    private val displayManager: DisplayManager =
+        sandboxContext.spyService(DisplayManager::class.java)
+
     @Before
     fun setup() {
+        val display =
+            Display(
+                DisplayManagerGlobal.getInstance(),
+                DEFAULT_DISPLAY,
+                DisplayInfo(),
+                DEFAULT_DISPLAY_ADJUSTMENTS,
+            )
+        whenever(displayManager.getDisplay(eq(DEFAULT_DISPLAY))).thenReturn(display)
+        whenever(displayManager.displays).thenReturn(arrayOf(display))
+
         sandboxContext.initDaggerComponent(
-            DaggerTestComponent.builder().bindSystemUiProxy(systemUiProxy)
+            DaggerTestComponent.builder()
+                .bindSystemUiProxy(systemUiProxy)
+                .bindRotationHelper(mock(RotationTouchHelper::class.java))
+                .bindRecentsState(mock(RecentsAnimationDeviceState::class.java))
         )
-        val deviceState = mock(RecentsAnimationDeviceState::class.java)
-        whenever(deviceState.rotationTouchHelper).thenReturn(mock(RotationTouchHelper::class.java))
-        gestureState = spy(GestureState(OverviewComponentObserver.INSTANCE.get(sandboxContext), 0))
+        gestureState =
+            spy(
+                GestureState(
+                    OverviewComponentObserver.INSTANCE.get(sandboxContext),
+                    DEFAULT_DISPLAY,
+                    0,
+                )
+            )
 
         underTest =
             LauncherSwipeHandlerV2(
                 sandboxContext,
-                deviceState,
                 taskAnimationManager,
                 gestureState,
                 0,
                 false,
                 inputConsumerController,
+                msdlPlayerWrapper,
             )
         underTest.onGestureStarted(/* isLikelyToStartNewTask= */ false)
     }
@@ -86,32 +116,30 @@
     @Test
     fun goHomeFromAppByTrackpad_updateEduStats() {
         gestureState.setTrackpadGestureType(GestureState.TrackpadGestureType.THREE_FINGER)
-        underTest.onGestureEnded(flingSpeed, PointF())
+        underTest.onGestureEnded(flingSpeed, PointF(), /* horizontalTouchSlopPassed= */ false)
         verify(systemUiProxy)
-            .updateContextualEduStats(
-                /* isTrackpadGesture= */ eq(true),
-                eq(GestureType.HOME.toString()),
-            )
+            .updateContextualEduStats(/* isTrackpadGesture= */ eq(true), eq(GestureType.HOME))
     }
 
     @Test
     fun goHomeFromAppByTouch_updateEduStats() {
-        underTest.onGestureEnded(flingSpeed, PointF())
+        underTest.onGestureEnded(flingSpeed, PointF(), /* horizontalTouchSlopPassed= */ false)
         verify(systemUiProxy)
-            .updateContextualEduStats(
-                /* isTrackpadGesture= */ eq(false),
-                eq(GestureType.HOME.toString()),
-            )
+            .updateContextualEduStats(/* isTrackpadGesture= */ eq(false), eq(GestureType.HOME))
     }
 }
 
 @LauncherAppSingleton
-@Component(modules = [QuickStepModule::class])
+@Component(modules = [LauncherAppModule::class])
 interface TestComponent : LauncherAppComponent {
     @Component.Builder
     interface Builder : LauncherAppComponent.Builder {
         @BindsInstance fun bindSystemUiProxy(proxy: SystemUiProxy): Builder
 
+        @BindsInstance fun bindRotationHelper(helper: RotationTouchHelper): Builder
+
+        @BindsInstance fun bindRecentsState(state: RecentsAnimationDeviceState): Builder
+
         override fun build(): TestComponent
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
index ec1dc8b..66c4ab5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/LauncherSwipeHandlerV2TestCase.java
@@ -33,11 +33,13 @@
 import com.android.quickstep.views.RecentsView;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
 @SmallTest
 @RunWith(LauncherMultivalentJUnit.class)
+@Ignore
 public class LauncherSwipeHandlerV2TestCase extends AbsSwipeUpHandlerTestCase<
         LauncherState,
         QuickstepLauncher,
@@ -73,12 +75,12 @@
             long touchTimeMs, boolean continuingLastGesture) {
         return new LauncherSwipeHandlerV2(
                 mContext,
-                mRecentsAnimationDeviceState,
                 mTaskAnimationManager,
                 mGestureState,
                 touchTimeMs,
                 continuingLastGesture,
-                mInputConsumerController);
+                mInputConsumerController,
+                mMSDLPlayerWrapper);
     }
 
     @NonNull
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt
index 0ae710f..56c01f9 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/OverviewCommandHelperTest.kt
@@ -68,7 +68,10 @@
                     touchInteractionService = mock(),
                     overviewComponentObserver = mock(),
                     taskAnimationManager = mock(),
-                    dispatcherProvider = TestDispatcherProvider(dispatcher)
+                    dispatcherProvider = TestDispatcherProvider(dispatcher),
+                    recentsDisplayModel = mock(),
+                    focusState = mock(),
+                    taskbarManager = mock(),
                 )
             )
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
new file mode 100644
index 0000000..9722e9d
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentTasksListTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.launcher3.Flags.FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS;
+import static com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.TestCase.assertNull;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
+import android.app.KeyguardManager;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.launcher3.statehandlers.DesktopVisibilityController;
+import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.LooperExecutor;
+import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.views.TaskViewType;
+import com.android.systemui.shared.recents.model.Task;
+import com.android.wm.shell.shared.GroupedTaskInfo;
+import com.android.wm.shell.shared.split.SplitBounds;
+import com.android.wm.shell.shared.split.SplitScreenConstants;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecentTasksListTest {
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private SystemUiProxy mSystemUiProxy;
+    @Mock
+    private TopTaskTracker mTopTaskTracker;
+
+    // Class under test
+    private RecentTasksList mRecentTasksList;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class);
+        KeyguardManager mockKeyguardManager = mock(KeyguardManager.class);
+
+        // Set desktop mode supported
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getBoolean(R.bool.config_isDesktopModeSupported)).thenReturn(true);
+        when(mResources.getBoolean(R.bool.config_canInternalDisplayHostDesktops))
+                .thenReturn(true);
+
+        mRecentTasksList = new RecentTasksList(mContext, mockMainThreadExecutor,
+                mockKeyguardManager, mSystemUiProxy, mTopTaskTracker,
+                mock(DesktopVisibilityController.class),
+                mock(DaggerSingletonTracker.class));
+    }
+
+    @Test
+    public void onRecentTasksChanged_doesNotFetchTasks() throws Exception {
+        mRecentTasksList.onRecentTasksChanged();
+        verify(mSystemUiProxy, times(0))
+                .getRecentTasks(anyInt(), anyInt());
+    }
+
+    @Test
+    public void loadTasksInBackground_onlyKeys_noValidTaskDescription() throws Exception  {
+        GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks(
+                new RecentTaskInfo(), new RecentTaskInfo(), new SplitBounds(
+                        /* leftTopBounds = */ new Rect(),
+                        /* rightBottomBounds = */ new Rect(),
+                        /* leftTopTaskId = */ -1,
+                        /* rightBottomTaskId = */ -1,
+                        /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50));
+        when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
+                .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
+
+        List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1,
+                true);
+
+        assertEquals(1, taskList.size());
+        taskList.get(0).getTasks().forEach(t -> assertNull(t.taskDescription.getLabel()));
+    }
+
+    @Test
+    public void loadTasksInBackground_GetRecentTasksException() throws Exception  {
+        when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
+                .thenThrow(new SystemUiProxy.GetRecentTasksException("task load failed"));
+
+        RecentTasksList.TaskLoadResult taskList = mRecentTasksList.loadTasksInBackground(
+                Integer.MAX_VALUE, -1, false);
+
+        assertThat(taskList.mRequestId).isEqualTo(-1);
+        assertThat(taskList).isEmpty();
+    }
+
+    @Test
+    public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() throws Exception  {
+        String taskDescription = "Wheeee!";
+        RecentTaskInfo task1 = new RecentTaskInfo();
+        task1.taskDescription = new ActivityManager.TaskDescription(taskDescription);
+        RecentTaskInfo task2 = new RecentTaskInfo();
+        task2.taskDescription = new ActivityManager.TaskDescription();
+        GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks(task1, task2,
+                new SplitBounds(
+                        /* leftTopBounds = */ new Rect(),
+                        /* rightBottomBounds = */ new Rect(),
+                        /* leftTopTaskId = */ -1,
+                        /* rightBottomTaskId = */ -1,
+                        /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50));
+        when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
+                .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
+
+        List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1,
+                false);
+
+        assertEquals(1, taskList.size());
+        var tasks = taskList.get(0).getTasks();
+        assertEquals(2, tasks.size());
+        assertEquals(taskDescription, tasks.get(0).taskDescription.getLabel());
+        assertNull(tasks.get(1).taskDescription.getLabel());
+    }
+
+    @Test
+    @DisableFlags(FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS)
+    public void loadTasksInBackground_freeformTask_createsDesktopTask() throws Exception  {
+        List<TaskInfo> tasks = Arrays.asList(
+                createRecentTaskInfo(1 /* taskId */, DEFAULT_DISPLAY),
+                createRecentTaskInfo(4 /* taskId */, DEFAULT_DISPLAY),
+                createRecentTaskInfo(5 /* taskId */, 1 /* displayId */),
+                createRecentTaskInfo(6 /* taskId */, 1 /* displayId */));
+        GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forDeskTasks(
+                0 /* deskId */, tasks, Collections.emptySet() /* minimizedTaskIds */);
+        when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
+                .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
+
+        List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(
+                Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */);
+
+        assertEquals(1, taskList.size());
+        assertEquals(TaskViewType.DESKTOP, taskList.get(0).taskViewType);
+        List<Task> actualFreeformTasks = taskList.get(0).getTasks();
+        assertEquals(4, actualFreeformTasks.size());
+        assertEquals(1, actualFreeformTasks.get(0).key.id);
+        assertFalse(actualFreeformTasks.get(0).isMinimized);
+        assertEquals(4, actualFreeformTasks.get(1).key.id);
+        assertFalse(actualFreeformTasks.get(1).isMinimized);
+        assertEquals(5, actualFreeformTasks.get(2).key.id);
+        assertFalse(actualFreeformTasks.get(2).isMinimized);
+        assertEquals(6, actualFreeformTasks.get(3).key.id);
+        assertFalse(actualFreeformTasks.get(3).isMinimized);
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_SEPARATE_EXTERNAL_DISPLAY_TASKS)
+    @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+    public void loadTasksInBackground_freeformTask_createsDesktopTaskPerDisplay() throws Exception {
+        List<TaskInfo> tasks = Arrays.asList(
+                createRecentTaskInfo(1 /* taskId */, DEFAULT_DISPLAY),
+                createRecentTaskInfo(4 /* taskId */, DEFAULT_DISPLAY),
+                createRecentTaskInfo(5 /* taskId */, 1 /* displayId */),
+                createRecentTaskInfo(6 /* taskId */, 1 /* displayId */));
+        GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forDeskTasks(
+                0 /* deskId */, tasks, Collections.emptySet() /* minimizedTaskIds */);
+        when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
+                .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
+
+        List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(
+                Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */);
+
+        assertEquals(2, taskList.size());
+        assertEquals(TaskViewType.DESKTOP, taskList.get(0).taskViewType);
+        List<Task> actualFreeformTasksDefaultDisplay = taskList.get(0).getTasks();
+        assertEquals(2, actualFreeformTasksDefaultDisplay.size());
+        assertEquals(1, actualFreeformTasksDefaultDisplay.get(0).key.id);
+        assertFalse(actualFreeformTasksDefaultDisplay.get(0).isMinimized);
+        assertEquals(4, actualFreeformTasksDefaultDisplay.get(1).key.id);
+        assertFalse(actualFreeformTasksDefaultDisplay.get(1).isMinimized);
+
+        List<Task> actualFreeformTasksExternalDisplay = taskList.get(1).getTasks();
+        assertEquals(2, actualFreeformTasksExternalDisplay.size());
+        assertEquals(5, actualFreeformTasksExternalDisplay.get(0).key.id);
+        assertFalse(actualFreeformTasksExternalDisplay.get(0).isMinimized);
+        assertEquals(6, actualFreeformTasksExternalDisplay.get(1).key.id);
+        assertFalse(actualFreeformTasksExternalDisplay.get(1).isMinimized);
+    }
+
+    @Test
+    public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_createDesktopTask()
+            throws Exception {
+        List<TaskInfo> tasks = Arrays.asList(
+                createRecentTaskInfo(1 /* taskId */, DEFAULT_DISPLAY),
+                createRecentTaskInfo(4 /* taskId */, DEFAULT_DISPLAY),
+                createRecentTaskInfo(5 /* taskId */, DEFAULT_DISPLAY));
+        Set<Integer> minimizedTaskIds =
+                Arrays.stream(new Integer[]{1, 4, 5}).collect(Collectors.toSet());
+        GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forDeskTasks(
+                0 /* deskId */, tasks, minimizedTaskIds);
+        when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
+                .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
+
+        List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(
+                Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */);
+
+        assertEquals(1, taskList.size());
+        assertEquals(TaskViewType.DESKTOP, taskList.get(0).taskViewType);
+        List<Task> actualFreeformTasks = taskList.get(0).getTasks();
+        assertEquals(3, actualFreeformTasks.size());
+        assertEquals(1, actualFreeformTasks.get(0).key.id);
+        assertTrue(actualFreeformTasks.get(0).isMinimized);
+        assertEquals(4, actualFreeformTasks.get(1).key.id);
+        assertTrue(actualFreeformTasks.get(1).isMinimized);
+        assertEquals(5, actualFreeformTasks.get(2).key.id);
+        assertTrue(actualFreeformTasks.get(2).isMinimized);
+    }
+
+    private TaskInfo createRecentTaskInfo(int taskId, int displayId) {
+        RecentTaskInfo recentTaskInfo = new RecentTaskInfo();
+        recentTaskInfo.taskId = taskId;
+        recentTaskInfo.displayId = displayId;
+        return recentTaskInfo;
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
new file mode 100644
index 0000000..a7370b0
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
@@ -0,0 +1,260 @@
+package com.android.quickstep
+
+import android.view.Display
+import androidx.test.annotation.UiThreadTest
+import androidx.test.filters.SmallTest
+import com.android.launcher3.dagger.LauncherComponentProvider
+import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
+import com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE
+import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
+import com.android.launcher3.util.DisplayController.Info
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import com.android.launcher3.util.LauncherMultivalentJUnit
+import com.android.launcher3.util.NavigationMode
+import com.android.launcher3.util.SandboxApplication
+import com.android.quickstep.util.GestureExclusionManager
+import com.android.systemui.shared.system.QuickStepContract
+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.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.verifyNoMoreInteractions
+import org.mockito.kotlin.whenever
+
+/** Unit test for [RecentsAnimationDeviceState]. */
+@SmallTest
+@UiThreadTest
+@RunWith(LauncherMultivalentJUnit::class)
+class RecentsAnimationDeviceStateTest {
+
+    @get:Rule val context = SandboxApplication()
+
+    @Mock private lateinit var exclusionManager: GestureExclusionManager
+    @Mock private lateinit var info: Info
+
+    private lateinit var underTest: RecentsAnimationDeviceState
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        val component = LauncherComponentProvider.get(context)
+        underTest =
+            RecentsAnimationDeviceState(
+                context,
+                exclusionManager,
+                component.displayController,
+                component.contextualSearchStateManager,
+                component.rotationTouchHelper,
+                component.settingsCache,
+                component.daggerSingletonTracker,
+            )
+    }
+
+    @After
+    fun tearDown() {
+        UI_HELPER_EXECUTOR.submit {}.get()
+        MAIN_EXECUTOR.submit {}.get()
+    }
+
+    @Test
+    fun registerExclusionListener_success() {
+        underTest.registerExclusionListener()
+
+        verify(exclusionManager).addListener(underTest)
+    }
+
+    @Test
+    fun registerExclusionListener_again_fail() {
+        underTest.registerExclusionListener()
+        reset(exclusionManager)
+
+        underTest.registerExclusionListener()
+
+        verifyNoMoreInteractions(exclusionManager)
+    }
+
+    @Test
+    fun unregisterExclusionListener_success() {
+        underTest.registerExclusionListener()
+        reset(exclusionManager)
+
+        underTest.unregisterExclusionListener()
+
+        verify(exclusionManager).removeListener(underTest)
+    }
+
+    @Test
+    fun unregisterExclusionListener_again_fail() {
+        underTest.registerExclusionListener()
+        underTest.unregisterExclusionListener()
+        reset(exclusionManager)
+
+        underTest.unregisterExclusionListener()
+
+        verifyNoMoreInteractions(exclusionManager)
+    }
+
+    @Test
+    fun onDisplayInfoChanged_noButton_registerExclusionListener() {
+        doReturn(NavigationMode.NO_BUTTON).whenever(info).getNavigationMode()
+
+        underTest.onDisplayInfoChanged(context, info, CHANGE_ROTATION or CHANGE_NAVIGATION_MODE)
+
+        verify(exclusionManager).addListener(underTest)
+    }
+
+    @Test
+    fun onDisplayInfoChanged_twoButton_unregisterExclusionListener() {
+        underTest.registerExclusionListener()
+        whenever(info.getNavigationMode()).thenReturn(NavigationMode.TWO_BUTTONS)
+        reset(exclusionManager)
+
+        underTest.onDisplayInfoChanged(context, info, CHANGE_ROTATION or CHANGE_NAVIGATION_MODE)
+
+        verify(exclusionManager).removeListener(underTest)
+    }
+
+    @Test
+    fun onDisplayInfoChanged_changeDensity_noOp() {
+        underTest.registerExclusionListener()
+        whenever(info.getNavigationMode()).thenReturn(NavigationMode.NO_BUTTON)
+        reset(exclusionManager)
+
+        underTest.onDisplayInfoChanged(context, info, CHANGE_DENSITY)
+
+        verifyNoMoreInteractions(exclusionManager)
+    }
+
+    @Test
+    fun trackpadGesturesNotAllowedForSelectedStates() {
+        val disablingStates =
+            GESTURE_DISABLING_SYSUI_STATES + SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED
+
+        allSysUiStates().forEach { state ->
+            val canStartGesture = !disablingStates.contains(state)
+            underTest.setSysUIStateFlagsForDisplay(state, Display.DEFAULT_DISPLAY)
+            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.setSysUIStateFlagsForDisplay(state, Display.DEFAULT_DISPLAY)
+            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.setSysUIStateFlagsForDisplay(state, Display.DEFAULT_DISPLAY)
+            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.setSysUIStateFlagsForDisplay(state, Display.DEFAULT_DISPLAY)
+            assertThat(underTest.canStartSystemGesture()).isEqualTo(gestureAllowed)
+        }
+    }
+
+    @Test
+    fun getSystemUiStateFlags_defaultAwake() {
+        val NOT_EXISTENT_DISPLAY = 2
+        assertThat(underTest.getSystemUiStateFlags(NOT_EXISTENT_DISPLAY))
+            .isEqualTo(QuickStepContract.SYSUI_STATE_AWAKE)
+    }
+
+    @Test
+    fun clearSysUIStateFlagsForDisplay_displayNotReturnedAnymore() {
+        underTest.setSysUIStateFlagsForDisplay(1, /* displayId= */ 1)
+
+        assertThat(underTest.displaysWithSysUIState).contains(1)
+        assertThat(underTest.getSystemUiStateFlags(1)).isEqualTo(1)
+
+        underTest.clearSysUIStateFlagsForDisplay(1)
+
+        assertThat(underTest.displaysWithSysUIState).doesNotContain(1)
+        assertThat(underTest.getSystemUiStateFlags(1))
+            .isEqualTo(QuickStepContract.SYSUI_STATE_AWAKE)
+    }
+
+    @Test
+    fun setSysUIStateFlagsForDisplay_setsCorrectly() {
+        underTest.setSysUIStateFlagsForDisplay(1, /* displayId= */ 1)
+        underTest.setSysUIStateFlagsForDisplay(2, /* displayId= */ 2)
+
+        assertThat(underTest.getSystemUiStateFlags(1)).isEqualTo(1)
+        assertThat(underTest.getSystemUiStateFlags(2)).isEqualTo(2)
+        assertThat(underTest.displaysWithSysUIState).containsAtLeast(1, 2)
+    }
+
+    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/RecentsModelTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
similarity index 75%
rename from quickstep/tests/src/com/android/quickstep/RecentsModelTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
index 648fa93..2eb2e4c 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentsModelTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsModelTest.java
@@ -32,22 +32,30 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Rect;
 import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.launcher3.Flags;
 import com.android.launcher3.R;
+import com.android.launcher3.graphics.ThemeManager;
 import com.android.launcher3.icons.IconProvider;
+import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.LockedUserState;
+import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SplitTask;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
+import com.android.wm.shell.shared.split.SplitScreenConstants;
 
-import org.junit.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.Mock;
 import org.mockito.MockitoAnnotations;
@@ -56,6 +64,7 @@
 import java.util.function.Consumer;
 
 @SmallTest
+@RunWith(AndroidJUnit4.class)
 public class RecentsModelTest {
     @Mock
     private Context mContext;
@@ -67,7 +76,13 @@
     private RecentTasksList mTasksList;
 
     @Mock
-    private TaskThumbnailCache.HighResLoadingState mHighResLoadingState;
+    private HighResLoadingState mHighResLoadingState;
+
+    @Mock
+    private LockedUserState mLockedUserState;
+
+    @Mock
+    private ThemeManager mThemeManager;
 
     private RecentsModel mRecentsModel;
 
@@ -94,7 +109,8 @@
         when(mThumbnailCache.isPreloadingEnabled()).thenReturn(true);
 
         mRecentsModel = new RecentsModel(mContext, mTasksList, mock(TaskIconCache.class),
-                mThumbnailCache, mock(IconProvider.class), mock(TaskStackChangeListeners.class));
+                mThumbnailCache, mock(IconProvider.class), mock(TaskStackChangeListeners.class),
+                mLockedUserState, () -> mThemeManager, mock(DaggerSingletonTracker.class));
 
         mResource = mock(Resources.class);
         when(mResource.getInteger((R.integer.recentsThumbnailCacheSize))).thenReturn(3);
@@ -111,10 +127,12 @@
                 .updateThumbnailInCache(taskArgs.capture(), /* lowResolution= */ eq(false));
 
         GroupTask expectedGroupTask = mTaskResult.get(0);
-        assertThat(taskArgs.getAllValues().get(0)).isEqualTo(
-                expectedGroupTask.task1);
-        assertThat(taskArgs.getAllValues().get(1)).isEqualTo(
-                expectedGroupTask.task2);
+        var taskArgsValues = taskArgs.getAllValues();
+        var expectedTasks = expectedGroupTask.getTasks();
+        assertThat(taskArgsValues.size()).isEqualTo(expectedTasks.size());
+        for (int i = 0; i < expectedTasks.size(); ++i) {
+            assertThat(taskArgsValues.get(i)).isEqualTo(expectedTasks.get(i));
+        }
     }
 
     @Test
@@ -157,6 +175,17 @@
                 .updateThumbnailInCache(any(), anyBoolean());
     }
 
+    @Test
+    public void themeCallbackAttachedOnUnlock() {
+        verify(mThemeManager, never()).addChangeListener(any());
+
+        ArgumentCaptor<Runnable> callbackCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(mLockedUserState).runOnUserUnlocked(callbackCaptor.capture());
+
+        callbackCaptor.getAllValues().forEach(Runnable::run);
+        verify(mThemeManager, times(1)).addChangeListener(any());
+    }
+
     private RecentTasksList.TaskLoadResult getTaskResult() {
         RecentTasksList.TaskLoadResult allTasks = new RecentTasksList.TaskLoadResult(0, false, 1);
         ActivityManager.RecentTaskInfo taskInfo1 = new ActivityManager.RecentTaskInfo();
@@ -167,7 +196,13 @@
         Task.TaskKey taskKey2 = new Task.TaskKey(taskInfo2);
         Task task2 = Task.from(taskKey2, taskInfo2, false);
 
-        allTasks.add(new GroupTask(task1, task2, null));
+        allTasks.add(
+                new SplitTask(task1, task2, new SplitConfigurationOptions.SplitBounds(
+                        /* leftTopBounds = */ new Rect(),
+                        /* rightBottomBounds = */ new Rect(),
+                        /* leftTopTaskId = */ -1,
+                        /* rightBottomTaskId = */ -1,
+                        /* snapPosition = */ SplitScreenConstants.SNAP_TO_2_50_50)));
         return allTasks;
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
index 1bdf273..c1be1ce 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/RecentsWindowSwipeHandlerTestCase.java
@@ -17,16 +17,23 @@
 package com.android.quickstep;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.util.AllModulesForTest;
 import com.android.launcher3.util.LauncherMultivalentJUnit;
 import com.android.quickstep.fallback.FallbackRecentsView;
 import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
 import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.fallback.window.RecentsWindowSwipeHandler;
 import com.android.quickstep.views.RecentsViewContainer;
 
+import dagger.BindsInstance;
+import dagger.Component;
+
+import org.junit.Before;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
@@ -39,8 +46,15 @@
         RecentsWindowSwipeHandler,
         FallbackWindowInterface> {
 
-    @Mock private RecentsWindowManager mRecentsWindowManager;
+    @Mock private RecentsDisplayModel mRecentsDisplayModel;
     @Mock private FallbackRecentsView<RecentsWindowManager> mRecentsView;
+    @Mock private RecentsWindowManager mRecentsWindowManager;
+
+    @Before
+    public void setRecentsDisplayModel() {
+        mContext.initDaggerComponent(DaggerRecentsWindowSwipeHandlerTestCase_TestComponent.builder()
+                .bindRecentsDisplayModel(mRecentsDisplayModel));
+    }
 
     @NonNull
     @Override
@@ -48,19 +62,12 @@
             boolean continuingLastGesture) {
         return new RecentsWindowSwipeHandler(
                 mContext,
-                mRecentsAnimationDeviceState,
                 mTaskAnimationManager,
                 mGestureState,
                 touchTimeMs,
                 continuingLastGesture,
                 mInputConsumerController,
-                mRecentsWindowManager);
-    }
-
-    @Nullable
-    @Override
-    protected RecentsWindowManager getRecentsWindowManager() {
-        return mRecentsWindowManager;
+                mMSDLPlayerWrapper);
     }
 
     @NonNull
@@ -74,4 +81,14 @@
     protected FallbackRecentsView<RecentsWindowManager> getRecentsView() {
         return mRecentsView;
     }
+
+    @LauncherAppSingleton
+    @Component(modules = {AllModulesForTest.class})
+    interface TestComponent extends LauncherAppComponent {
+        @Component.Builder
+        interface Builder extends LauncherAppComponent.Builder {
+            @BindsInstance Builder bindRecentsDisplayModel(RecentsDisplayModel model);
+            @Override LauncherAppComponent build();
+        }
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
similarity index 79%
rename from quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
index 633a575..6e9885a 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskAnimationManagerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskAnimationManagerTest.java
@@ -27,24 +27,23 @@
 import android.content.Context;
 import android.content.Intent;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-
-import com.android.quickstep.fallback.window.RecentsWindowManager;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
+@RunWith(AndroidJUnit4.class)
 public class TaskAnimationManagerTest {
 
-    @Mock
-    private Context mContext;
-
-    @Mock
-    private RecentsWindowManager mRecentsWindowManager;
+    protected final Context mContext =
+            InstrumentationRegistry.getInstrumentation().getTargetContext();
 
     @Mock
     private SystemUiProxy mSystemUiProxy;
@@ -54,7 +53,8 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mTaskAnimationManager = new TaskAnimationManager(mContext, mRecentsWindowManager) {
+        mTaskAnimationManager = new TaskAnimationManager(mContext,
+                RecentsAnimationDeviceState.INSTANCE.get(mContext)) {
             @Override
             SystemUiProxy getSystemUiProxy() {
                 return mSystemUiProxy;
@@ -69,8 +69,8 @@
         final RecentsAnimationCallbacks.RecentsAnimationListener listener =
                 mock(RecentsAnimationCallbacks.RecentsAnimationListener.class);
         doReturn(activityInterface).when(gestureState).getContainerInterface();
-        mTaskAnimationManager.startRecentsAnimation(gestureState, new Intent(), listener);
-
+        runOnMainSync(() ->
+                mTaskAnimationManager.startRecentsAnimation(gestureState, new Intent(), listener));
         final ArgumentCaptor<ActivityOptions> optionsCaptor =
                 ArgumentCaptor.forClass(ActivityOptions.class);
         verify(mSystemUiProxy)
@@ -78,4 +78,8 @@
         assertEquals(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS,
                 optionsCaptor.getValue().getPendingIntentBackgroundActivityStartMode());
     }
+
+    protected static void runOnMainSync(Runnable runnable) {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable);
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskThumbnailCacheTest.java
similarity index 96%
rename from quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/TaskThumbnailCacheTest.java
index 4e04261..3686c16 100644
--- a/quickstep/tests/src/com/android/quickstep/TaskThumbnailCacheTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/TaskThumbnailCacheTest.java
@@ -28,6 +28,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.launcher3.R;
@@ -35,12 +36,14 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.concurrent.Executor;
 
 @SmallTest
+@RunWith(AndroidJUnit4.class)
 public class TaskThumbnailCacheTest {
     @Mock
     private Context mContext;
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
index 98a3607..cfeade8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
@@ -24,8 +24,11 @@
 
 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LONG_PRESS_NAVBAR;
+import static com.android.launcher3.logging.StatsLogManager.LauncherLatencyEvent.LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.quickstep.DeviceConfigWrapper.DEFAULT_LPNH_TIMEOUT_MS;
+import static com.android.quickstep.inputconsumers.NavHandleLongPressInputConsumer.MIN_TIME_TO_LOG_ABANDON_MS;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -33,9 +36,11 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.os.SystemClock;
@@ -45,8 +50,12 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.util.AllModulesForTest;
 import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.SandboxContext;
 import com.android.quickstep.DeviceConfigWrapper;
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
@@ -56,6 +65,9 @@
 import com.android.quickstep.util.TestExtensions;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
+import dagger.BindsInstance;
+import dagger.Component;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -77,6 +89,7 @@
     private NavHandleLongPressInputConsumer mUnderTest;
     private SandboxContext mContext;
     private float mScreenWidth;
+    private long mDownTimeMs;
     @Mock InputConsumer mDelegate;
     @Mock InputMonitorCompat mInputMonitor;
     @Mock RecentsAnimationDeviceState mDeviceState;
@@ -85,6 +98,9 @@
     @Mock NavHandleLongPressHandler mNavHandleLongPressHandler;
     @Mock TopTaskTracker mTopTaskTracker;
     @Mock TopTaskTracker.CachedTaskInfo mTaskInfo;
+    @Mock StatsLogManager mStatsLogManager;
+    @Mock StatsLogManager.StatsLogger mStatsLogger;
+    @Mock StatsLogManager.StatsLatencyLogger mStatsLatencyLogger;
 
     @Before
     public void setup() {
@@ -94,6 +110,11 @@
         when(mDelegate.allowInterceptByParent()).thenReturn(true);
         mLongPressTriggered.set(false);
         when(mNavHandleLongPressHandler.getLongPressRunnable(any())).thenReturn(mLongPressRunnable);
+        when(mStatsLogger.withPackageName(any())).thenReturn(mStatsLogger);
+        when(mStatsLatencyLogger.withInstanceId(any())).thenReturn(mStatsLatencyLogger);
+        when(mStatsLatencyLogger.withLatency(anyLong())).thenReturn(mStatsLatencyLogger);
+        when(mStatsLogManager.logger()).thenReturn(mStatsLogger);
+        when(mStatsLogManager.latencyLogger()).thenReturn(mStatsLatencyLogger);
         initializeObjectUnderTest();
     }
 
@@ -118,17 +139,23 @@
         assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
         verify(mNavHandleLongPressHandler, never()).onTouchStarted(any());
         verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+        verifyNoMoreInteractions(mStatsLogManager);
+        verifyNoMoreInteractions(mStatsLogger);
+        verifyNoMoreInteractions(mStatsLatencyLogger);
     }
 
     @Test
     public void testDelegateDisallowsTouchInterceptAfterTouchDown() {
+        // Touch down and wait the minimum abandonment time.
         mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
+        sleep(MIN_TIME_TO_LOG_ABANDON_MS);
 
         // Delegate should still get touches unless long press is triggered.
         verify(mDelegate).onMotionEvent(any());
         verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
         verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
 
+        // Child delegate blocks us from intercepting further motion events.
         when(mDelegate.allowInterceptByParent()).thenReturn(false);
         mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_MOVE));
 
@@ -138,46 +165,54 @@
         assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
         verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
         verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+        verifyNoMoreInteractions(mStatsLogger);
+        // Because we handled touch down before the child blocked additional events, log abandon.
+        verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
     }
 
     @Test
     public void testLongPressTriggered() {
         mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
-        SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS);
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        sleep(DEFAULT_LPNH_TIMEOUT_MS);
 
         assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE);
         assertTrue(mLongPressTriggered.get());
         verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
         verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+        verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR);
+        verifyNoMoreInteractions(mStatsLatencyLogger);
+
+        // Ensure abandon latency is still not logged after long press.
+        mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_UP));
+        verifyNoMoreInteractions(mStatsLatencyLogger);
     }
 
     @Test
     public void testLongPressTriggeredWithSlightVerticalMovement() {
         mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
-        mUnderTest.onMotionEvent(generateCenteredMotionEventWithYOffset(ACTION_MOVE,
-                -(TOUCH_SLOP - 1)));
-        SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS);
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        mUnderTest.onMotionEvent(generateCenteredMotionEventWithYOffset(ACTION_MOVE, 1));
+        sleep(DEFAULT_LPNH_TIMEOUT_MS);
 
         assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE);
         assertTrue(mLongPressTriggered.get());
         verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
         verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+        verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR);
+        verifyNoMoreInteractions(mStatsLatencyLogger);
     }
 
     @Test
     public void testLongPressTriggeredWithSlightHorizontalMovement() {
         mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
-        mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE,
-                mScreenWidth / 2f - (TOUCH_SLOP - 1), 0));
-        SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS);
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE, mScreenWidth / 2f + 1, 0));
+        sleep(DEFAULT_LPNH_TIMEOUT_MS);
 
         assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE);
         assertTrue(mLongPressTriggered.get());
         verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
         verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+        verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR);
+        verifyNoMoreInteractions(mStatsLatencyLogger);
     }
 
     @Test
@@ -190,8 +225,7 @@
             mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE,
                     mScreenWidth / 2f - (TOUCH_SLOP - 1), 0));
             // We have entered the second stage, so the normal timeout shouldn't trigger.
-            SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS);
-            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+            sleep(DEFAULT_LPNH_TIMEOUT_MS);
 
             assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
             assertFalse(mLongPressTriggered.get());
@@ -201,14 +235,15 @@
             // After an extended time, the long press should trigger.
             float extendedDurationMultiplier =
                     (DeviceConfigWrapper.get().getTwoStageDurationPercentage() / 100f);
-            SystemClock.sleep((long) (DEFAULT_LPNH_TIMEOUT_MS
+            sleep((long) (DEFAULT_LPNH_TIMEOUT_MS
                     * (extendedDurationMultiplier - 1)));  // -1 because we already waited 1x
-            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
             assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE);
             assertTrue(mLongPressTriggered.get());
             verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
             verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+            verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR);
+            verifyNoMoreInteractions(mStatsLatencyLogger);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -222,13 +257,14 @@
 
             mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
             // We have not entered the second stage, so the normal timeout should trigger.
-            SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS);
-            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+            sleep(DEFAULT_LPNH_TIMEOUT_MS);
 
             assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_ACTIVE);
             assertTrue(mLongPressTriggered.get());
             verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
             verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+            verify(mStatsLogger).log(LAUNCHER_LONG_PRESS_NAVBAR);
+            verifyNoMoreInteractions(mStatsLatencyLogger);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -237,48 +273,59 @@
     @Test
     public void testLongPressAbortedByTouchUp() {
         mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
-        SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS - 10);
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        sleep(MIN_TIME_TO_LOG_ABANDON_MS);
 
         assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
         assertFalse(mLongPressTriggered.get());
 
         mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_UP));
         // Wait past the long press timeout, to be extra sure it wouldn't have triggered.
-        SystemClock.sleep(20);
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        sleep(DEFAULT_LPNH_TIMEOUT_MS);
 
         assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
         assertFalse(mLongPressTriggered.get());
         verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
         verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+        verifyNoMoreInteractions(mStatsLogger);
+        verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
     }
 
     @Test
     public void testLongPressAbortedByTouchCancel() {
         mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
-        SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS - 10);
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        sleep(MIN_TIME_TO_LOG_ABANDON_MS);
 
         assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
         assertFalse(mLongPressTriggered.get());
 
         mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_CANCEL));
         // Wait past the long press timeout, to be extra sure it wouldn't have triggered.
-        SystemClock.sleep(20);
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        sleep(DEFAULT_LPNH_TIMEOUT_MS);
 
         assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
         assertFalse(mLongPressTriggered.get());
         verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
         verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+        verifyNoMoreInteractions(mStatsLogger);
+        verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
+    }
+
+    @Test
+    public void testTouchCancelWithoutTouchDown() {
+        mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_CANCEL));
+
+        assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
+        assertFalse(mLongPressTriggered.get());
+        verify(mNavHandleLongPressHandler, never()).onTouchStarted(any());
+        verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+        verifyNoMoreInteractions(mStatsLogger);
+        verifyNoMoreInteractions(mStatsLatencyLogger);
     }
 
     @Test
     public void testLongPressAbortedByTouchSlopPassedVertically() {
         mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
-        SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS - 10);
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        sleep(MIN_TIME_TO_LOG_ABANDON_MS);
 
         assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
         assertFalse(mLongPressTriggered.get());
@@ -286,20 +333,20 @@
         mUnderTest.onMotionEvent(generateCenteredMotionEventWithYOffset(ACTION_MOVE,
                 -(TOUCH_SLOP + 1)));
         // Wait past the long press timeout, to be extra sure it wouldn't have triggered.
-        SystemClock.sleep(20);
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        sleep(DEFAULT_LPNH_TIMEOUT_MS);
 
         assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
         assertFalse(mLongPressTriggered.get());
         verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
         verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+        verifyNoMoreInteractions(mStatsLogger);
+        verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
     }
 
     @Test
     public void testLongPressAbortedByTouchSlopPassedHorizontally() {
         mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
-        SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS - 10);
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        sleep(MIN_TIME_TO_LOG_ABANDON_MS);
 
         assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
         assertFalse(mLongPressTriggered.get());
@@ -307,13 +354,14 @@
         mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE,
                 mScreenWidth / 2f - (TOUCH_SLOP + 1), 0));
         // Wait past the long press timeout, to be extra sure it wouldn't have triggered.
-        SystemClock.sleep(20);
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        sleep(DEFAULT_LPNH_TIMEOUT_MS);
 
         assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
         assertFalse(mLongPressTriggered.get());
         verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
         verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+        verifyNoMoreInteractions(mStatsLogger);
+        verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
     }
 
     @Test
@@ -327,8 +375,7 @@
             mUnderTest.onMotionEvent(generateCenteredMotionEventWithYOffset(ACTION_MOVE,
                     -(TOUCH_SLOP - 1)));
             // Normal duration shouldn't trigger.
-            SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS);
-            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+            sleep(DEFAULT_LPNH_TIMEOUT_MS);
 
             assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
             assertFalse(mLongPressTriggered.get());
@@ -339,15 +386,16 @@
             // Wait past the extended long press timeout, to be sure it wouldn't have triggered.
             float extendedDurationMultiplier =
                     (DeviceConfigWrapper.get().getTwoStageDurationPercentage() / 100f);
-            SystemClock.sleep((long) (DEFAULT_LPNH_TIMEOUT_MS
+            sleep((long) (DEFAULT_LPNH_TIMEOUT_MS
                     * (extendedDurationMultiplier - 1)));  // -1 because we already waited 1x
-            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
             assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
             assertFalse(mLongPressTriggered.get());
             verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
             // Touch cancelled.
             verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+            verifyNoMoreInteractions(mStatsLogger);
+            verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -364,8 +412,7 @@
             mUnderTest.onMotionEvent(generateMotionEvent(ACTION_MOVE,
                     mScreenWidth / 2f - (TOUCH_SLOP - 1), 0));
             // Normal duration shouldn't trigger.
-            SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS);
-            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+            sleep(DEFAULT_LPNH_TIMEOUT_MS);
 
             assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
             assertFalse(mLongPressTriggered.get());
@@ -376,15 +423,16 @@
             // Wait past the extended long press timeout, to be sure it wouldn't have triggered.
             float extendedDurationMultiplier =
                     (DeviceConfigWrapper.get().getTwoStageDurationPercentage() / 100f);
-            SystemClock.sleep((long) (DEFAULT_LPNH_TIMEOUT_MS
+            sleep((long) (DEFAULT_LPNH_TIMEOUT_MS
                     * (extendedDurationMultiplier - 1)));  // -1 because we already waited 1x
-            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
             assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
             assertFalse(mLongPressTriggered.get());
             verify(mNavHandleLongPressHandler, times(1)).onTouchStarted(any());
             // Touch cancelled.
             verify(mNavHandleLongPressHandler, times(1)).onTouchFinished(any(), any());
+            verifyNoMoreInteractions(mStatsLogger);
+            verify(mStatsLatencyLogger).log(LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -394,14 +442,16 @@
     public void testTouchOutsideNavHandleIgnored() {
         // Touch the far left side of the screen. (y=0 is top of navbar region, picked arbitrarily)
         mUnderTest.onMotionEvent(generateMotionEvent(ACTION_DOWN, 0, 0));
-        SystemClock.sleep(DEFAULT_LPNH_TIMEOUT_MS);
-        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+        sleep(DEFAULT_LPNH_TIMEOUT_MS);
 
         // Should be ignored because the x position was not centered in the navbar region.
         assertThat(mUnderTest.mState).isEqualTo(DelegateInputConsumer.STATE_INACTIVE);
         assertFalse(mLongPressTriggered.get());
         verify(mNavHandleLongPressHandler, never()).onTouchStarted(any());
         verify(mNavHandleLongPressHandler, never()).onTouchFinished(any(), any());
+        verifyNoMoreInteractions(mStatsLogManager);
+        verifyNoMoreInteractions(mStatsLogger);
+        verifyNoMoreInteractions(mStatsLatencyLogger);
     }
 
     @Test
@@ -416,6 +466,20 @@
         mUnderTest.onHoverEvent(generateCenteredMotionEvent(ACTION_HOVER_ENTER));
 
         verify(mDelegate, times(2)).onHoverEvent(any());
+
+        verifyNoMoreInteractions(mStatsLogManager);
+        verifyNoMoreInteractions(mStatsLogger);
+        verifyNoMoreInteractions(mStatsLatencyLogger);
+    }
+
+    @Test
+    public void testNoLogsForShortTouch() {
+        mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_DOWN));
+        sleep(10);
+        mUnderTest.onMotionEvent(generateCenteredMotionEvent(ACTION_UP));
+        verifyNoMoreInteractions(mStatsLogManager);
+        verifyNoMoreInteractions(mStatsLogger);
+        verifyNoMoreInteractions(mStatsLatencyLogger);
     }
 
     private void initializeObjectUnderTest() {
@@ -423,11 +487,21 @@
             mContext.onDestroy();
         }
         mContext = new SandboxContext(getApplicationContext());
-        mContext.putObject(TopTaskTracker.INSTANCE, mTopTaskTracker);
+        mContext.initDaggerComponent(
+                DaggerNavHandleLongPressInputConsumerTest_TopTaskTrackerComponent
+                        .builder()
+                        .bindTopTaskTracker(mTopTaskTracker));
         mScreenWidth = DisplayController.INSTANCE.get(mContext).getInfo().currentSize.x;
         mUnderTest = new NavHandleLongPressInputConsumer(mContext, mDelegate, mInputMonitor,
                 mDeviceState, mNavHandle, mGestureState);
         mUnderTest.setNavHandleLongPressHandler(mNavHandleLongPressHandler);
+        mUnderTest.setStatsLogManager(mStatsLogManager);
+        mDownTimeMs = 0;
+    }
+
+    private static void sleep(long sleepMs) {
+        SystemClock.sleep(sleepMs);
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
 
     /** Generate a motion event centered horizontally in the screen. */
@@ -440,8 +514,12 @@
         return generateMotionEvent(motionAction, mScreenWidth / 2f, y);
     }
 
-    private static MotionEvent generateMotionEvent(int motionAction, float x, float y) {
-        return MotionEvent.obtain(0, 0, motionAction, x, y, 0);
+    private MotionEvent generateMotionEvent(int motionAction, float x, float y) {
+        if (motionAction == ACTION_DOWN) {
+            mDownTimeMs = SystemClock.uptimeMillis();
+        }
+        long eventTime = SystemClock.uptimeMillis();
+        return MotionEvent.obtain(mDownTimeMs, eventTime, motionAction, x, y, 0);
     }
 
     private static AutoCloseable overrideTwoStageFlag(boolean value) {
@@ -450,4 +528,16 @@
                 value,
                 () -> DeviceConfigWrapper.get().getEnableLpnhTwoStages());
     }
+
+    @LauncherAppSingleton
+    @Component(modules = AllModulesForTest.class)
+    public interface TopTaskTrackerComponent extends LauncherAppComponent {
+        @Component.Builder
+        interface Builder extends LauncherAppComponent.Builder {
+            @BindsInstance Builder bindTopTaskTracker(TopTaskTracker topTaskTracker);
+
+            @Override
+            TopTaskTrackerComponent build();
+        }
+    }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
index 7c48ea4..14570b5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/logging/SettingsChangeLoggerTest.kt
@@ -21,8 +21,8 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.launcher3.LauncherPrefs
 import com.android.launcher3.LauncherPrefs.Companion.ALLOW_ROTATION
-import com.android.launcher3.LauncherPrefs.Companion.THEMED_ICONS
 import com.android.launcher3.SessionCommitReceiver.ADD_ICON_PREFERENCE_KEY
+import com.android.launcher3.graphics.ThemeManager
 import com.android.launcher3.logging.InstanceId
 import com.android.launcher3.logging.StatsLogManager
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ADD_NEW_APPS_TO_HOME_SCREEN_ENABLED
@@ -34,6 +34,9 @@
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_NOTIFICATION_DOT_ENABLED
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_THEMED_ICON_DISABLED
 import com.android.launcher3.states.RotationHelper.ALLOW_ROTATION_PREFERENCE_KEY
+import com.android.launcher3.util.DaggerSingletonTracker
+import com.android.launcher3.util.DisplayController
+import com.android.launcher3.util.SettingsCache
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -60,37 +63,57 @@
     @Mock private lateinit var mStatsLogManager: StatsLogManager
 
     @Mock private lateinit var mMockLogger: StatsLogManager.StatsLogger
+    @Mock private lateinit var mTracker: DaggerSingletonTracker
+    private var displayController: DisplayController = DisplayController.INSTANCE.get(mContext)
+    private var settingsCache: SettingsCache = SettingsCache.INSTANCE.get(mContext)
 
     @Captor private lateinit var mEventCaptor: ArgumentCaptor<StatsLogManager.EventEnum>
 
     private var mDefaultThemedIcons = false
     private var mDefaultAllowRotation = false
 
+    private val themeManager: ThemeManager
+        get() = ThemeManager.INSTANCE.get(mContext)
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
         whenever(mStatsLogManager.logger()).doReturn(mMockLogger)
         whenever(mStatsLogManager.logger().withInstanceId(any())).doReturn(mMockLogger)
-        mDefaultThemedIcons = LauncherPrefs.get(mContext).get(THEMED_ICONS)
+        mDefaultThemedIcons = themeManager.isMonoThemeEnabled
         mDefaultAllowRotation = LauncherPrefs.get(mContext).get(ALLOW_ROTATION)
         // To match the default value of THEMED_ICONS
-        LauncherPrefs.get(mContext).put(THEMED_ICONS, false)
+        themeManager.isMonoThemeEnabled = false
         // To match the default value of ALLOW_ROTATION
         LauncherPrefs.get(mContext).put(item = ALLOW_ROTATION, value = false)
 
-        mSystemUnderTest = SettingsChangeLogger(mContext, mStatsLogManager)
+        mSystemUnderTest =
+            SettingsChangeLogger(
+                mContext,
+                mStatsLogManager,
+                mTracker,
+                displayController,
+                settingsCache,
+            )
     }
 
     @After
     fun tearDown() {
-        LauncherPrefs.get(mContext).put(THEMED_ICONS, mDefaultThemedIcons)
+        themeManager.isMonoThemeEnabled = mDefaultThemedIcons
         LauncherPrefs.get(mContext).put(ALLOW_ROTATION, mDefaultAllowRotation)
     }
 
     @Test
     fun loggingPrefs_correctDefaultValue() {
-        val systemUnderTest = SettingsChangeLogger(mContext, mStatsLogManager)
+        val systemUnderTest =
+            SettingsChangeLogger(
+                mContext,
+                mStatsLogManager,
+                mTracker,
+                displayController,
+                settingsCache,
+            )
 
         assertThat(systemUnderTest.loggingPrefs[ALLOW_ROTATION_PREFERENCE_KEY]!!.defaultValue)
             .isFalse()
@@ -117,7 +140,8 @@
         LauncherPrefs.get(mContext).put(item = ALLOW_ROTATION, value = true)
 
         // This a new object so the values of mLoggablePrefs will be different
-        SettingsChangeLogger(mContext, mStatsLogManager).logSnapshot(mInstanceId)
+        SettingsChangeLogger(mContext, mStatsLogManager, mTracker, displayController, settingsCache)
+            .logSnapshot(mInstanceId)
 
         verify(mMockLogger, atLeastOnce()).log(mEventCaptor.capture())
         val capturedEvents = mEventCaptor.allValues
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
index ea52842..0570c26 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/LandscapePagedViewHandlerTest.kt
@@ -62,6 +62,7 @@
             isRTL,
             OVERVIEW_TASK_MARGIN_PX,
             DIVIDER_SIZE_PX,
+            oneIconHiddenDueToSmallWidth = false,
         )
     }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
index 2bc182c..3788688 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/orientation/SeascapePagedViewHandlerTest.kt
@@ -62,6 +62,7 @@
             isRTL,
             OVERVIEW_TASK_MARGIN_PX,
             DIVIDER_SIZE_PX,
+            oneIconHiddenDueToSmallWidth = false,
         )
     }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeHighResLoadingStateNotifier.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeHighResLoadingStateNotifier.kt
index 7d09efd..4adf01e 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeHighResLoadingStateNotifier.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeHighResLoadingStateNotifier.kt
@@ -16,7 +16,7 @@
 
 package com.android.quickstep.recents.data
 
-import com.android.quickstep.TaskThumbnailCache.HighResLoadingState.HighResLoadingStateChangedCallback
+import com.android.quickstep.HighResLoadingState.HighResLoadingStateChangedCallback
 
 class FakeHighResLoadingStateNotifier : HighResLoadingStateNotifier {
     val listeners = mutableListOf<HighResLoadingStateChangedCallback>()
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt
index eaeb513..9e99a0b 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentTasksDataSource.kt
@@ -20,10 +20,12 @@
 import java.util.function.Consumer
 
 class FakeRecentTasksDataSource : RecentTasksDataSource {
-    var taskList: List<GroupTask> = listOf()
+    private var taskList: List<GroupTask> = listOf()
 
     override fun getTasks(callback: Consumer<List<GroupTask>>?): Int {
-        callback?.accept(taskList)
+        // Makes a copy of the GroupTask to create a new GroupTask instance and to simulate
+        // RecentsModel::getTasks behavior.
+        callback?.accept(taskList.map { it.copy() })
         return 0
     }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt
index fc2f029..4e90903 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeRecentsDeviceProfileRepository.kt
@@ -18,9 +18,7 @@
 
 class FakeRecentsDeviceProfileRepository : RecentsDeviceProfileRepository {
     private var recentsDeviceProfile =
-        RecentsDeviceProfile(
-            isLargeScreen = false,
-        )
+        RecentsDeviceProfile(isLargeScreen = false, canEnterDesktopMode = false)
 
     override fun getRecentsDeviceProfile() = recentsDeviceProfile
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
index 5de876a..f6f158f 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskIconDataSource.kt
@@ -17,11 +17,11 @@
 package com.android.quickstep.recents.data
 
 import android.graphics.drawable.Drawable
-import com.android.launcher3.util.CancellableTask
-import com.android.quickstep.TaskIconCache
+import com.android.quickstep.TaskIconCache.TaskCacheEntry
 import com.android.quickstep.task.thumbnail.data.TaskIconDataSource
 import com.android.systemui.shared.recents.model.Task
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.yield
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 
@@ -29,28 +29,30 @@
 
     val taskIdToDrawable: MutableMap<Int, Drawable> =
         (0..10).associateWith { mockCopyableDrawable() }.toMutableMap()
-
-    val taskIdToUpdatingTask: MutableMap<Int, () -> Unit> = mutableMapOf()
-    var shouldLoadSynchronously: Boolean = true
+    private val completionPrevented: MutableSet<Int> = mutableSetOf()
 
     /** Retrieves and sets an icon on [task] from [taskIdToDrawable]. */
-    override fun getIconInBackground(
-        task: Task,
-        callback: TaskIconCache.GetTaskIconCallback
-    ): CancellableTask<*>? {
-        val wrappedCallback = {
-            callback.onTaskIconReceived(
-                taskIdToDrawable.getValue(task.key.id),
-                "content desc ${task.key.id}",
-                "title ${task.key.id}"
-            )
+    override suspend fun getIcon(task: Task): TaskCacheEntry {
+        while (task.key.id in completionPrevented) {
+            yield()
         }
-        if (shouldLoadSynchronously) {
-            wrappedCallback()
-        } else {
-            taskIdToUpdatingTask[task.key.id] = wrappedCallback
-        }
-        return null
+        return TaskCacheEntry(
+            taskIdToDrawable.getValue(task.key.id),
+            "content desc ${task.key.id}",
+            "title ${task.key.id}",
+        )
+    }
+
+    fun preventIconLoad(taskId: Int) {
+        completionPrevented.add(taskId)
+    }
+
+    fun completeLoadingForTask(taskId: Int) {
+        completionPrevented.remove(taskId)
+    }
+
+    fun completeLoading() {
+        completionPrevented.clear()
     }
 
     companion object {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
index d12c0b0..40d5e02 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTaskThumbnailDataSource.kt
@@ -17,37 +17,41 @@
 package com.android.quickstep.recents.data
 
 import android.graphics.Bitmap
-import com.android.launcher3.util.CancellableTask
 import com.android.quickstep.task.thumbnail.data.TaskThumbnailDataSource
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
-import java.util.function.Consumer
+import kotlinx.coroutines.yield
 import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
 
 class FakeTaskThumbnailDataSource : TaskThumbnailDataSource {
 
     val taskIdToBitmap: MutableMap<Int, Bitmap> =
         (0..10).associateWith { mock<Bitmap>() }.toMutableMap()
-    val taskIdToUpdatingTask: MutableMap<Int, () -> Unit> = mutableMapOf()
-    var shouldLoadSynchronously: Boolean = true
+    private val completionPrevented: MutableSet<Int> = mutableSetOf()
+    private val getThumbnailCalls = mutableMapOf<Int, Int>()
+
+    var highResEnabled = true
 
     /** Retrieves and sets a thumbnail on [task] from [taskIdToBitmap]. */
-    override fun getThumbnailInBackground(
-        task: Task,
-        callback: Consumer<ThumbnailData>
-    ): CancellableTask<ThumbnailData>? {
-        val thumbnailData = mock<ThumbnailData>()
-        whenever(thumbnailData.thumbnail).thenReturn(taskIdToBitmap[task.key.id])
-        val wrappedCallback = {
-            task.thumbnail = thumbnailData
-            callback.accept(thumbnailData)
+    override suspend fun getThumbnail(task: Task): ThumbnailData {
+        getThumbnailCalls[task.key.id] = (getThumbnailCalls[task.key.id] ?: 0) + 1
+
+        while (task.key.id in completionPrevented) {
+            yield()
         }
-        if (shouldLoadSynchronously) {
-            wrappedCallback()
-        } else {
-            taskIdToUpdatingTask[task.key.id] = wrappedCallback
-        }
-        return null
+        return ThumbnailData(
+            thumbnail = taskIdToBitmap[task.key.id],
+            reducedResolution = !highResEnabled,
+        )
+    }
+
+    fun getNumberOfGetThumbnailCalls(taskId: Int): Int = getThumbnailCalls[taskId] ?: 0
+
+    fun preventThumbnailLoad(taskId: Int) {
+        completionPrevented.add(taskId)
+    }
+
+    fun completeLoadingForTask(taskId: Int) {
+        completionPrevented.remove(taskId)
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
index d6688d6..35af29f 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/FakeTasksRepository.kt
@@ -49,16 +49,19 @@
     override fun getThumbnailById(taskId: Int): Flow<ThumbnailData?> =
         getTaskDataById(taskId).map { it?.thumbnail }
 
+    override fun getCurrentThumbnailById(taskId: Int): ThumbnailData? =
+        tasks.value.firstOrNull { it.key.id == taskId }?.thumbnail
+
     override fun setVisibleTasks(visibleTaskIdList: Set<Int>) {
         visibleTasks.value = visibleTaskIdList
         tasks.value =
             tasks.value.map {
                 it.apply {
                     thumbnail = thumbnailDataMap[it.key.id]
-                    taskIconDataMap[it.key.id].let { data ->
-                        title = data?.title
-                        titleDescription = data?.titleDescription
-                        icon = data?.icon
+                    taskIconDataMap[it.key.id]?.let { data ->
+                        title = data.title
+                        titleDescription = data.titleDescription
+                        icon = data.icon
                     }
                 }
             }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImplTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImplTest.kt
deleted file mode 100644
index abe4142..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/RecentsDeviceProfileRepositoryImplTest.kt
+++ /dev/null
@@ -1,44 +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.quickstep.recents.data
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.launcher3.FakeInvariantDeviceProfileTest
-import com.android.quickstep.views.RecentsViewContainer
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
-
-/** Test for [RecentsDeviceProfileRepositoryImpl] */
-@RunWith(AndroidJUnit4::class)
-class RecentsDeviceProfileRepositoryImplTest : FakeInvariantDeviceProfileTest() {
-    private val recentsViewContainer = mock<RecentsViewContainer>()
-
-    private val systemUnderTest = RecentsDeviceProfileRepositoryImpl(recentsViewContainer)
-
-    @Test
-    fun deviceProfileMappedCorrectly() {
-        initializeVarsForTablet()
-        val tabletDeviceProfile = newDP()
-        whenever(recentsViewContainer.deviceProfile).thenReturn(tabletDeviceProfile)
-
-        assertThat(systemUnderTest.getRecentsDeviceProfile())
-            .isEqualTo(RecentsDeviceProfile(isLargeScreen = true))
-    }
-}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt
index 41f6bfd..b91f8bd 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TaskVisualsChangedDelegateTest.kt
@@ -19,16 +19,19 @@
 import android.content.ComponentName
 import android.content.Intent
 import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskIconChangedCallback
 import com.android.quickstep.recents.data.TaskVisualsChangedDelegate.TaskThumbnailChangedCallback
 import com.android.systemui.shared.recents.model.Task.TaskKey
 import com.android.systemui.shared.recents.model.ThumbnailData
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.verifyNoMoreInteractions
 
+@RunWith(AndroidJUnit4::class)
 class TaskVisualsChangedDelegateTest {
     private val taskVisualsChangeNotifier = FakeTaskVisualsChangeNotifier()
     private val highResLoadingStateNotifier = FakeHighResLoadingStateNotifier()
@@ -83,21 +86,21 @@
         // Correct match
         systemUnderTest.registerTaskIconChangedCallback(
             createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1),
-            expectedListener
+            expectedListener,
         )
         // 1 out of 2 match
         systemUnderTest.registerTaskIconChangedCallback(
             createTaskKey(id = 2, pkg = PACKAGE_NAME, userId = 1),
-            listener
+            listener,
         )
         systemUnderTest.registerTaskIconChangedCallback(
             createTaskKey(id = 3, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 2),
-            listener
+            listener,
         )
         // 0 out of 2 match
         systemUnderTest.registerTaskIconChangedCallback(
             createTaskKey(id = 4, pkg = PACKAGE_NAME, userId = 2),
-            listener
+            listener,
         )
 
         systemUnderTest.onTaskIconChanged(ALTERNATIVE_PACKAGE_NAME, UserHandle(1))
@@ -112,11 +115,11 @@
         val newListener = mock<TaskIconChangedCallback>()
         systemUnderTest.registerTaskIconChangedCallback(
             createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1),
-            replacedListener
+            replacedListener,
         )
         systemUnderTest.registerTaskIconChangedCallback(
             createTaskKey(id = 1, pkg = ALTERNATIVE_PACKAGE_NAME, userId = 1),
-            newListener
+            newListener,
         )
 
         systemUnderTest.onTaskIconChanged(ALTERNATIVE_PACKAGE_NAME, UserHandle(1))
@@ -132,11 +135,11 @@
         val expectedThumbnailData = ThumbnailData(snapshotId = 12345)
         systemUnderTest.registerTaskThumbnailChangedCallback(
             createTaskKey(id = 1),
-            expectedListener
+            expectedListener,
         )
         systemUnderTest.registerTaskThumbnailChangedCallback(
             createTaskKey(id = 2),
-            additionalListener
+            additionalListener,
         )
 
         systemUnderTest.onTaskThumbnailChanged(1, expectedThumbnailData)
@@ -146,22 +149,41 @@
     }
 
     @Test
-    fun onHighResLoadingStateChanged_notifiesAllListeners() {
+    fun onHighResLoadingStateChanged_toEnabled_notifiesAllListeners() {
         val expectedListener = mock<TaskThumbnailChangedCallback>()
         val additionalListener = mock<TaskThumbnailChangedCallback>()
         systemUnderTest.registerTaskThumbnailChangedCallback(
             createTaskKey(id = 1),
-            expectedListener
+            expectedListener,
         )
         systemUnderTest.registerTaskThumbnailChangedCallback(
             createTaskKey(id = 2),
-            additionalListener
+            additionalListener,
         )
 
         systemUnderTest.onHighResLoadingStateChanged(true)
 
-        verify(expectedListener).onHighResLoadingStateChanged()
-        verify(additionalListener).onHighResLoadingStateChanged()
+        verify(expectedListener).onHighResLoadingStateChanged(true)
+        verify(additionalListener).onHighResLoadingStateChanged(true)
+    }
+
+    @Test
+    fun onHighResLoadingStateChanged_toDisabled_notifiesAllListeners() {
+        val expectedListener = mock<TaskThumbnailChangedCallback>()
+        val additionalListener = mock<TaskThumbnailChangedCallback>()
+        systemUnderTest.registerTaskThumbnailChangedCallback(
+            createTaskKey(id = 1),
+            expectedListener,
+        )
+        systemUnderTest.registerTaskThumbnailChangedCallback(
+            createTaskKey(id = 2),
+            additionalListener,
+        )
+
+        systemUnderTest.onHighResLoadingStateChanged(false)
+
+        verify(expectedListener).onHighResLoadingStateChanged(false)
+        verify(additionalListener).onHighResLoadingStateChanged(false)
     }
 
     @Test
@@ -171,7 +193,7 @@
         val expectedThumbnailData = ThumbnailData(snapshotId = 12345)
         systemUnderTest.registerTaskThumbnailChangedCallback(
             createTaskKey(id = 1),
-            replacedListener1
+            replacedListener1,
         )
         systemUnderTest.registerTaskThumbnailChangedCallback(createTaskKey(id = 1), newListener1)
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
index 357df6e..6790567 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/data/TasksRepositoryTest.kt
@@ -19,13 +19,17 @@
 import android.content.ComponentName
 import android.content.Intent
 import android.graphics.Bitmap
+import android.graphics.Rect
 import android.graphics.drawable.Drawable
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.launcher3.util.TestDispatcherProvider
 import com.android.quickstep.util.DesktopTask
-import com.android.quickstep.util.GroupTask
+import com.android.quickstep.util.SingleTask
+import com.android.quickstep.util.SplitTask
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.first
@@ -37,7 +41,9 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.spy
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 
 @OptIn(ExperimentalCoroutinesApi::class)
@@ -46,9 +52,19 @@
     private val tasks = (0..5).map(::createTaskWithId)
     private val defaultTaskList =
         listOf(
-            GroupTask(tasks[0]),
-            GroupTask(tasks[1], tasks[2], null),
-            DesktopTask(tasks.subList(3, 6)),
+            SingleTask(tasks[0]),
+            SplitTask(
+                tasks[1],
+                tasks[2],
+                SplitConfigurationOptions.SplitBounds(
+                    /* leftTopBounds = */ Rect(),
+                    /* rightBottomBounds = */ Rect(),
+                    /* leftTopTaskId = */ -1,
+                    /* rightBottomTaskId = */ -1,
+                    /* snapPosition = */ SNAP_TO_2_50_50,
+                ),
+            ),
+            DesktopTask(deskId = 0, tasks.subList(3, 6)),
         )
     private val recentsModel = FakeRecentTasksDataSource()
     private val taskThumbnailDataSource = FakeTaskThumbnailDataSource()
@@ -56,7 +72,7 @@
     private val taskVisualsChangeNotifier = FakeTaskVisualsChangeNotifier()
     private val highResLoadingStateNotifier = FakeHighResLoadingStateNotifier()
     private val taskVisualsChangedDelegate =
-        TaskVisualsChangedDelegateImpl(taskVisualsChangeNotifier, highResLoadingStateNotifier)
+        spy(TaskVisualsChangedDelegateImpl(taskVisualsChangeNotifier, highResLoadingStateNotifier))
 
     private val dispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(dispatcher)
@@ -87,6 +103,85 @@
         }
 
     @Test
+    fun getThumbnailByIdReturnsNullWithNoLoadedThumbnails() =
+        testScope.runTest {
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+
+            assertThat(systemUnderTest.getThumbnailById(1).first()).isNull()
+        }
+
+    @Test
+    fun getCurrentThumbnailByIdReturnsNullWithNoLoadedThumbnails() =
+        testScope.runTest {
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+
+            assertThat(systemUnderTest.getCurrentThumbnailById(1)).isNull()
+        }
+
+    @Test
+    fun getThumbnailByIdReturnsThumbnailWithLoadedThumbnails() =
+        testScope.runTest {
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+            val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
+
+            systemUnderTest.setVisibleTasks(setOf(1))
+
+            assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1)
+        }
+
+    @Test
+    fun whenThumbnailIsLoaded_getAllTaskData_usesPreviousLoadedThumbnailAndIcon() =
+        testScope.runTest {
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+            val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
+
+            systemUnderTest.setVisibleTasks(setOf(1))
+            assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1)
+
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+            assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1)
+        }
+
+    @Test
+    fun getAllTaskData_clearsPreviouslyLoadedImagesForRemovedTasks() =
+        testScope.runTest {
+            // Setup data
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+            val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
+
+            // Load images for task 1
+            systemUnderTest.setVisibleTasks(setOf(1))
+            assertThat(systemUnderTest.getThumbnailById(1).first()!!.thumbnail).isEqualTo(bitmap1)
+
+            // Remove task 1 from "all data"
+            recentsModel.seedTasks(
+                defaultTaskList.filterNot { groupTask -> groupTask.tasks.any { it.key.id == 1 } }
+            )
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+
+            // Assert task 1 was fully removed
+            assertThat(systemUnderTest.getThumbnailById(1).first()?.thumbnail).isNull()
+            verify(taskVisualsChangedDelegate).unregisterTaskThumbnailChangedCallback(tasks[1].key)
+        }
+
+    @Test
+    fun getCurrentThumbnailByIdReturnsThumbnailWithLoadedThumbnails() =
+        testScope.runTest {
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+            val bitmap1 = taskThumbnailDataSource.taskIdToBitmap[1]
+
+            systemUnderTest.setVisibleTasks(setOf(1))
+
+            assertThat(systemUnderTest.getCurrentThumbnailById(1)?.thumbnail).isEqualTo(bitmap1)
+        }
+
+    @Test
     fun setVisibleTasksPopulatesThumbnails() =
         testScope.runTest {
             recentsModel.seedTasks(defaultTaskList)
@@ -133,7 +228,7 @@
                 .isEqualTo(bitmap2)
 
             // Prevent new loading of Bitmaps
-            taskThumbnailDataSource.shouldLoadSynchronously = false
+            taskThumbnailDataSource.preventThumbnailLoad(2)
             systemUnderTest.setVisibleTasks(setOf(2, 3))
 
             assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail!!.thumbnail)
@@ -154,7 +249,7 @@
                 .assertHasIconDataFromSource(taskIconDataSource)
 
             // Prevent new loading of Drawables
-            taskThumbnailDataSource.shouldLoadSynchronously = false
+            taskIconDataSource.preventIconLoad(2)
             systemUnderTest.setVisibleTasks(setOf(2, 3))
 
             systemUnderTest
@@ -176,9 +271,6 @@
             assertThat(task2.thumbnail!!.thumbnail).isEqualTo(bitmap2)
             task2.assertHasIconDataFromSource(taskIconDataSource)
 
-            // Prevent new loading of Bitmaps
-            taskThumbnailDataSource.shouldLoadSynchronously = false
-            taskIconDataSource.shouldLoadSynchronously = false
             systemUnderTest.setVisibleTasks(setOf(0, 1))
 
             val task2AfterVisibleTasksChanged = systemUnderTest.getTaskDataById(2).first()!!
@@ -194,21 +286,22 @@
             // Setup fakes
             recentsModel.seedTasks(defaultTaskList)
             val bitmap2 = taskThumbnailDataSource.taskIdToBitmap[2]
-            taskThumbnailDataSource.shouldLoadSynchronously = false
 
             // Setup TasksRepository
             systemUnderTest.getAllTaskData(forceRefresh = true)
-            systemUnderTest.setVisibleTasks(setOf(1, 2))
 
-            // Assert there is no bitmap in first emission
-            assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail).isNull()
+            val task2DataFlow = systemUnderTest.getTaskDataById(2)
+            val task2BitmapValues = mutableListOf<Bitmap?>()
+            testScope.backgroundScope.launch {
+                task2DataFlow.map { it?.thumbnail?.thumbnail }.toList(task2BitmapValues)
+            }
 
-            // Simulate bitmap loading after first emission
-            taskThumbnailDataSource.taskIdToUpdatingTask.getValue(2).invoke()
+            // Check for first emission
+            assertThat(task2BitmapValues.single()).isNull()
 
+            systemUnderTest.setVisibleTasks(setOf(2))
             // Check for second emission
-            assertThat(systemUnderTest.getTaskDataById(2).first()!!.thumbnail!!.thumbnail)
-                .isEqualTo(bitmap2)
+            assertThat(task2BitmapValues).isEqualTo(listOf(null, bitmap2))
         }
 
     @Test
@@ -233,8 +326,9 @@
         }
 
     @Test
-    fun onHighResLoadingStateChanged_setsNewThumbnailDataOnTask() =
+    fun onHighResLoadingStateChanged_highResReplacesLowResThumbnail() =
         testScope.runTest {
+            taskThumbnailDataSource.highResEnabled = false
             recentsModel.seedTasks(defaultTaskList)
             systemUnderTest.getAllTaskData(forceRefresh = true)
 
@@ -244,16 +338,77 @@
             val expectedPreviousBitmap = taskThumbnailDataSource.taskIdToBitmap[1]
             val taskDataFlow = systemUnderTest.getTaskDataById(1)
 
-            val task1ThumbnailValues = mutableListOf<Bitmap?>()
+            val task1ThumbnailValues = mutableListOf<ThumbnailData?>()
             testScope.backgroundScope.launch {
-                taskDataFlow.map { it?.thumbnail?.thumbnail }.toList(task1ThumbnailValues)
+                taskDataFlow.map { it?.thumbnail }.toList(task1ThumbnailValues)
             }
 
             taskThumbnailDataSource.taskIdToBitmap[1] = expectedBitmap
+            taskThumbnailDataSource.highResEnabled = true
             taskVisualsChangedDelegate.onHighResLoadingStateChanged(true)
 
-            assertThat(task1ThumbnailValues.first()).isEqualTo(expectedPreviousBitmap)
-            assertThat(task1ThumbnailValues.last()).isEqualTo(expectedBitmap)
+            val firstThumbnailValue = task1ThumbnailValues.first()!!
+            assertThat(firstThumbnailValue.thumbnail).isEqualTo(expectedPreviousBitmap)
+            assertThat(firstThumbnailValue.reducedResolution).isTrue()
+
+            val lastThumbnailValue = task1ThumbnailValues.last()!!
+            assertThat(lastThumbnailValue.thumbnail).isEqualTo(expectedBitmap)
+            assertThat(lastThumbnailValue.reducedResolution).isFalse()
+        }
+
+    @Test
+    fun onHighResLoadingStateChanged_invisibleTaskIgnored() =
+        testScope.runTest {
+            taskThumbnailDataSource.highResEnabled = false
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+
+            systemUnderTest.setVisibleTasks(setOf(1))
+
+            val invisibleTaskId = 2
+            val taskDataFlow = systemUnderTest.getTaskDataById(invisibleTaskId)
+
+            val task2ThumbnailValues = mutableListOf<ThumbnailData?>()
+            testScope.backgroundScope.launch {
+                taskDataFlow.map { it?.thumbnail }.toList(task2ThumbnailValues)
+            }
+
+            taskThumbnailDataSource.highResEnabled = true
+            taskVisualsChangedDelegate.onHighResLoadingStateChanged(true)
+
+            assertThat(task2ThumbnailValues.filterNotNull()).isEmpty()
+            assertThat(taskThumbnailDataSource.getNumberOfGetThumbnailCalls(2)).isEqualTo(0)
+        }
+
+    @Test
+    fun onHighResLoadingStateChanged_lowResDoesNotReplaceHighResThumbnail() =
+        testScope.runTest {
+            taskThumbnailDataSource.highResEnabled = true
+            recentsModel.seedTasks(defaultTaskList)
+            systemUnderTest.getAllTaskData(forceRefresh = true)
+
+            systemUnderTest.setVisibleTasks(setOf(1))
+
+            val expectedBitmap = mock<Bitmap>()
+            val expectedPreviousBitmap = taskThumbnailDataSource.taskIdToBitmap[1]
+            val taskDataFlow = systemUnderTest.getTaskDataById(1)
+
+            val task1ThumbnailValues = mutableListOf<ThumbnailData?>()
+            testScope.backgroundScope.launch {
+                taskDataFlow.map { it?.thumbnail }.toList(task1ThumbnailValues)
+            }
+
+            taskThumbnailDataSource.taskIdToBitmap[1] = expectedBitmap
+            taskThumbnailDataSource.highResEnabled = false
+            taskVisualsChangedDelegate.onHighResLoadingStateChanged(false)
+
+            val firstThumbnailValue = task1ThumbnailValues.first()!!
+            assertThat(firstThumbnailValue.thumbnail).isEqualTo(expectedPreviousBitmap)
+            assertThat(firstThumbnailValue.reducedResolution).isFalse()
+
+            val lastThumbnailValue = task1ThumbnailValues.last()!!
+            assertThat(lastThumbnailValue.thumbnail).isEqualTo(expectedPreviousBitmap)
+            assertThat(lastThumbnailValue.reducedResolution).isFalse()
         }
 
     @Test
@@ -284,7 +439,6 @@
         testScope.runTest {
             recentsModel.seedTasks(defaultTaskList)
             systemUnderTest.getAllTaskData(forceRefresh = true)
-            taskThumbnailDataSource.shouldLoadSynchronously = false
 
             val taskDataFlow = systemUnderTest.getTaskDataById(1)
             val task1IconValues = mutableListOf<Drawable?>()
@@ -293,14 +447,10 @@
             }
 
             systemUnderTest.setVisibleTasks(setOf(1))
-            val task1UpdatingTaskOld = taskThumbnailDataSource.taskIdToUpdatingTask[1]
-            println(task1UpdatingTaskOld)
+            assertThat(taskThumbnailDataSource.getNumberOfGetThumbnailCalls(1)).isEqualTo(1)
 
             systemUnderTest.setVisibleTasks(setOf(1, 2))
-            val task1UpdatingTaskNew = taskThumbnailDataSource.taskIdToUpdatingTask[1]
-            println(task1UpdatingTaskNew)
-
-            assertThat(task1UpdatingTaskNew).isEqualTo(task1UpdatingTaskOld)
+            assertThat(taskThumbnailDataSource.getNumberOfGetThumbnailCalls(1)).isEqualTo(1)
         }
 
     private fun createTaskWithId(taskId: Int) =
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetSysUiStatusNavFlagsUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetSysUiStatusNavFlagsUseCaseTest.kt
new file mode 100644
index 0000000..d384256
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetSysUiStatusNavFlagsUseCaseTest.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.domain.usecase
+
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+import com.android.launcher3.util.SystemUiController.FLAG_DARK_NAV
+import com.android.launcher3.util.SystemUiController.FLAG_DARK_STATUS
+import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV
+import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class GetSysUiStatusNavFlagsUseCaseTest {
+    private val sut: GetSysUiStatusNavFlagsUseCase = GetSysUiStatusNavFlagsUseCase()
+
+    @Test
+    fun onLightStatusBarAppearance_returns_LightTheme() {
+        val thumbnailData = ThumbnailData(appearance = APPEARANCE_LIGHT_STATUS_BARS)
+        val flag = sut.invoke(thumbnailData) // 6
+        flag.assertContainsFlag(FLAG_LIGHT_STATUS)
+        flag.assertContainsFlag(FLAG_DARK_NAV)
+        flag.assertDoesNotContainsFlag(FLAG_DARK_STATUS)
+        flag.assertDoesNotContainsFlag(FLAG_LIGHT_NAV)
+    }
+
+    @Test
+    fun onLightNavBarsAppearance_returns_LightTheme() {
+        val thumbnailData = ThumbnailData(appearance = APPEARANCE_LIGHT_NAVIGATION_BARS)
+        val flag = sut.invoke(thumbnailData)
+        flag.assertContainsFlag(FLAG_DARK_STATUS)
+        flag.assertContainsFlag(FLAG_LIGHT_NAV)
+        flag.assertDoesNotContainsFlag(FLAG_LIGHT_STATUS)
+        flag.assertDoesNotContainsFlag(FLAG_DARK_NAV)
+    }
+
+    @Test
+    fun onLightStatusBarAndNavBarAppearance_returns_LightTheme() {
+        val thumbnailData =
+            ThumbnailData(
+                appearance = APPEARANCE_LIGHT_STATUS_BARS or APPEARANCE_LIGHT_NAVIGATION_BARS
+            )
+        val flag = sut.invoke(thumbnailData)
+        flag.assertContainsFlag(FLAG_LIGHT_NAV)
+        flag.assertContainsFlag(FLAG_LIGHT_STATUS)
+        flag.assertDoesNotContainsFlag(FLAG_DARK_STATUS)
+        flag.assertDoesNotContainsFlag(FLAG_DARK_NAV)
+    }
+
+    @Test
+    fun onLightAppearance_returns_LightTheme() {
+        val thumbnailData =
+            ThumbnailData(
+                appearance =
+                    APPEARANCE_LIGHT_CAPTION_BARS or
+                        APPEARANCE_LIGHT_STATUS_BARS or
+                        APPEARANCE_LIGHT_NAVIGATION_BARS
+            )
+        val flag = sut.invoke(thumbnailData)
+        flag.assertContainsFlag(FLAG_LIGHT_NAV)
+        flag.assertContainsFlag(FLAG_LIGHT_STATUS)
+        flag.assertDoesNotContainsFlag(FLAG_DARK_STATUS)
+        flag.assertDoesNotContainsFlag(FLAG_DARK_NAV)
+    }
+
+    @Test
+    fun onDarkAppearance_returns_DarkTheme() {
+        val thumbnailData = ThumbnailData(appearance = 0)
+        val flag = sut.invoke(thumbnailData)
+        flag.assertContainsFlag(FLAG_DARK_STATUS)
+        flag.assertContainsFlag(FLAG_DARK_NAV)
+        flag.assertDoesNotContainsFlag(FLAG_LIGHT_NAV)
+        flag.assertDoesNotContainsFlag(FLAG_LIGHT_STATUS)
+    }
+
+    @Test
+    fun onUnrelatedDarkAppearance_returns_DarkTheme() {
+        val thumbnailData = ThumbnailData(appearance = 1)
+        val flag = sut.invoke(thumbnailData)
+        flag.assertContainsFlag(FLAG_DARK_STATUS)
+        flag.assertContainsFlag(FLAG_DARK_NAV)
+        flag.assertDoesNotContainsFlag(FLAG_LIGHT_NAV)
+        flag.assertDoesNotContainsFlag(FLAG_LIGHT_STATUS)
+    }
+
+    @Test
+    fun whenThumbnailIsNull_returns_default() {
+        val flag = sut.invoke(null)
+        flag.assertDoesNotContainsFlag(FLAG_DARK_STATUS)
+        flag.assertDoesNotContainsFlag(FLAG_LIGHT_NAV)
+        flag.assertDoesNotContainsFlag(FLAG_LIGHT_STATUS)
+        flag.assertDoesNotContainsFlag(FLAG_DARK_NAV)
+    }
+
+    private fun Int.assertContainsFlag(flag: Int) {
+        assertThat(this and flag).isNotEqualTo(0)
+    }
+
+    private fun Int.assertDoesNotContainsFlag(flag: Int) {
+        assertThat(this and flag).isEqualTo(0)
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCaseTest.kt
new file mode 100644
index 0000000..b036bce
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetTaskUseCaseTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.domain.usecase
+
+import android.content.ComponentName
+import android.content.Intent
+import android.graphics.Color
+import android.graphics.drawable.ShapeDrawable
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.quickstep.recents.data.FakeTasksRepository
+import com.android.quickstep.recents.domain.model.TaskModel
+import com.android.systemui.shared.recents.model.Task
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class GetTaskUseCaseTest {
+    private val unconfinedTestDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(unconfinedTestDispatcher)
+
+    private val tasksRepository = FakeTasksRepository()
+    private val sut = GetTaskUseCase(repository = tasksRepository)
+
+    @Before
+    fun setUp() {
+        tasksRepository.seedTasks(listOf(TASK_1))
+    }
+
+    @Test
+    fun taskNotSeeded_returnsNull() =
+        testScope.runTest {
+            val result = sut.invoke(NOT_FOUND_TASK_ID).firstOrNull()
+            assertThat(result).isNull()
+        }
+
+    @Test
+    fun taskNotVisible_returnsNull() =
+        testScope.runTest {
+            val result = sut.invoke(TASK_1_ID).firstOrNull()
+            assertThat(result).isNull()
+        }
+
+    @Test
+    fun taskVisible_returnsData() =
+        testScope.runTest {
+            tasksRepository.setVisibleTasks(setOf(TASK_1_ID))
+            val expectedResult =
+                TaskModel(
+                    id = TASK_1_ID,
+                    title = "Title $TASK_1_ID",
+                    titleDescription = "Content Description $TASK_1_ID",
+                    icon = TASK_1_ICON,
+                    thumbnail = null,
+                    backgroundColor = Color.BLACK,
+                    isLocked = false,
+                )
+            val result = sut.invoke(TASK_1_ID).firstOrNull()
+            assertThat(result).isEqualTo(expectedResult)
+        }
+
+    private companion object {
+        const val NOT_FOUND_TASK_ID = 404
+        private const val TASK_1_ID = 1
+        private val TASK_1_ICON = ShapeDrawable()
+        private val TASK_1 =
+            Task(
+                    Task.TaskKey(
+                        /* id = */ TASK_1_ID,
+                        /* windowingMode = */ 0,
+                        /* intent = */ Intent(),
+                        /* sourceComponent = */ ComponentName("", ""),
+                        /* userId = */ 0,
+                        /* lastActiveTime = */ 2000,
+                    )
+                )
+                .apply {
+                    title = "Title 1"
+                    titleDescription = "Content Description 1"
+                    colorBackground = Color.BLACK
+                    icon = TASK_1_ICON
+                    thumbnail = null
+                    isLocked = false
+                }
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetThumbnailPositionUseCaseTest.kt
similarity index 61%
rename from quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetThumbnailPositionUseCaseTest.kt
index bd7d970..a253280 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailPositionUseCaseTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/GetThumbnailPositionUseCaseTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2024 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,22 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.quickstep.recents.usecase
+package com.android.quickstep.recents.domain.usecase
 
-import android.content.ComponentName
-import android.content.Intent
 import android.graphics.Bitmap
-import android.graphics.Color
 import android.graphics.Matrix
 import android.graphics.Rect
 import android.view.Surface.ROTATION_90
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.quickstep.recents.data.FakeRecentsDeviceProfileRepository
 import com.android.quickstep.recents.data.FakeRecentsRotationStateRepository
-import com.android.quickstep.recents.data.FakeTasksRepository
-import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
-import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
-import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
 import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
 import com.google.common.truth.Truth.assertThat
@@ -43,55 +36,34 @@
 /** Test for [GetThumbnailPositionUseCase] */
 @RunWith(AndroidJUnit4::class)
 class GetThumbnailPositionUseCaseTest {
-    private val task =
-        Task(Task.TaskKey(TASK_ID, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
-            colorBackground = Color.BLACK
-        }
-    private val thumbnailData =
-        ThumbnailData(
-            thumbnail =
-                mock<Bitmap>().apply {
-                    whenever(width).thenReturn(THUMBNAIL_WIDTH)
-                    whenever(height).thenReturn(THUMBNAIL_HEIGHT)
-                }
-        )
-
     private val deviceProfileRepository = FakeRecentsDeviceProfileRepository()
     private val rotationStateRepository = FakeRecentsRotationStateRepository()
-    private val tasksRepository = FakeTasksRepository()
     private val previewPositionHelper = mock<PreviewPositionHelper>()
 
     private val systemUnderTest =
         GetThumbnailPositionUseCase(
             deviceProfileRepository,
             rotationStateRepository,
-            tasksRepository,
             previewPositionHelper,
         )
 
     @Test
-    fun invisibleTask_returnsIdentityMatrix() = runTest {
-        tasksRepository.seedTasks(listOf(task))
-
-        assertThat(systemUnderTest.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl = true))
-            .isInstanceOf(MissingThumbnail::class.java)
+    fun nullThumbnailData_returnsIdentityMatrix() = runTest {
+        val expectedResult = ThumbnailPosition(Matrix.IDENTITY_MATRIX, false)
+        val result = systemUnderTest.invoke(null, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl = true)
+        assertThat(result).isEqualTo(expectedResult)
     }
 
     @Test
-    fun visibleTaskWithoutThumbnailData_returnsIdentityMatrix() = runTest {
-        tasksRepository.seedTasks(listOf(task))
-        tasksRepository.setVisibleTasks(setOf(TASK_ID))
-
-        assertThat(systemUnderTest.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl = true))
-            .isInstanceOf(MissingThumbnail::class.java)
+    fun withoutThumbnail_returnsIdentityMatrix() = runTest {
+        val expectedResult = ThumbnailPosition(Matrix.IDENTITY_MATRIX, false)
+        val result =
+            systemUnderTest.invoke(ThumbnailData(), CANVAS_WIDTH, CANVAS_HEIGHT, isRtl = true)
+        assertThat(result).isEqualTo(expectedResult)
     }
 
     @Test
     fun visibleTaskWithThumbnailData_returnsTransformedMatrix() = runTest {
-        tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
-        tasksRepository.seedTasks(listOf(task))
-        tasksRepository.setVisibleTasks(setOf(TASK_ID))
-
         val isLargeScreen = true
         deviceProfileRepository.setRecentsDeviceProfile(
             deviceProfileRepository.getRecentsDeviceProfile().copy(isLargeScreen = isLargeScreen)
@@ -108,13 +80,14 @@
         whenever(previewPositionHelper.matrix).thenReturn(MATRIX)
         whenever(previewPositionHelper.isOrientationChanged).thenReturn(isRotated)
 
-        assertThat(systemUnderTest.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
-            .isEqualTo(MatrixScaling(MATRIX, isRotated))
+        val result = systemUnderTest.invoke(THUMBNAIL_DATA, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl)
+        val expectedResult = ThumbnailPosition(MATRIX, isRotated)
+        assertThat(result).isEqualTo(expectedResult)
 
         verify(previewPositionHelper)
             .updateThumbnailMatrix(
                 Rect(0, 0, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT),
-                thumbnailData,
+                THUMBNAIL_DATA,
                 CANVAS_WIDTH,
                 CANVAS_HEIGHT,
                 isLargeScreen,
@@ -123,8 +96,7 @@
             )
     }
 
-    companion object {
-        const val TASK_ID = 2
+    private companion object {
         const val THUMBNAIL_WIDTH = 100
         const val THUMBNAIL_HEIGHT = 200
         const val CANVAS_WIDTH = 300
@@ -133,5 +105,14 @@
             Matrix().apply {
                 setValues(floatArrayOf(2.3f, 4.5f, 2.6f, 7.4f, 3.4f, 2.3f, 2.5f, 6.0f, 3.4f))
             }
+
+        val THUMBNAIL_DATA =
+            ThumbnailData(
+                thumbnail =
+                    mock<Bitmap>().apply {
+                        whenever(width).thenReturn(THUMBNAIL_WIDTH)
+                        whenever(height).thenReturn(THUMBNAIL_HEIGHT)
+                    }
+            )
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCaseTest.kt
new file mode 100644
index 0000000..e8bca93
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/domain/usecase/IsThumbnailValidUseCaseTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.domain.usecase
+
+import android.graphics.Bitmap
+import android.view.Surface
+import android.view.Surface.ROTATION_90
+import com.android.quickstep.recents.data.FakeRecentsRotationStateRepository
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+class IsThumbnailValidUseCaseTest {
+    private val recentsRotationStateRepository = FakeRecentsRotationStateRepository()
+    private val systemUnderTest = IsThumbnailValidUseCase(recentsRotationStateRepository)
+
+    @Test
+    fun withNullThumbnail_returnsInvalid() = runTest {
+        val isThumbnailValid = systemUnderTest(thumbnailData = null, viewWidth = 0, viewHeight = 0)
+        assertThat(isThumbnailValid).isEqualTo(false)
+    }
+
+    @Test
+    fun sameAspectRatio_sameRotation_returnsValid() = runTest {
+        val isThumbnailValid =
+            systemUnderTest.invoke(
+                thumbnailData = createThumbnailData(),
+                viewWidth = THUMBNAIL_WIDTH * 2,
+                viewHeight = THUMBNAIL_HEIGHT * 2,
+            )
+        assertThat(isThumbnailValid).isEqualTo(true)
+    }
+
+    @Test
+    fun differentAspectRatio_sameRotation_returnsInvalid() = runTest {
+        val isThumbnailValid =
+            systemUnderTest.invoke(
+                thumbnailData = createThumbnailData(),
+                viewWidth = THUMBNAIL_WIDTH,
+                viewHeight = THUMBNAIL_HEIGHT * 2,
+            )
+        assertThat(isThumbnailValid).isEqualTo(false)
+    }
+
+    @Test
+    fun sameAspectRatio_differentRotation_returnsInvalid() = runTest {
+        val isThumbnailValid =
+            systemUnderTest.invoke(
+                thumbnailData = createThumbnailData(rotation = ROTATION_90),
+                viewWidth = THUMBNAIL_WIDTH * 2,
+                viewHeight = THUMBNAIL_HEIGHT * 2,
+            )
+        assertThat(isThumbnailValid).isEqualTo(false)
+    }
+
+    @Test
+    fun differentAspectRatio_differentRotation_returnsInvalid() = runTest {
+        val isThumbnailValid =
+            systemUnderTest.invoke(
+                thumbnailData = createThumbnailData(rotation = ROTATION_90),
+                viewWidth = THUMBNAIL_WIDTH,
+                viewHeight = THUMBNAIL_HEIGHT * 2,
+            )
+        assertThat(isThumbnailValid).isEqualTo(false)
+    }
+
+    private fun createThumbnailData(
+        rotation: Int = Surface.ROTATION_0,
+        width: Int = THUMBNAIL_WIDTH,
+        height: Int = THUMBNAIL_HEIGHT,
+    ): ThumbnailData {
+        val bitmap = mock<Bitmap>()
+        whenever(bitmap.width).thenReturn(width)
+        whenever(bitmap.height).thenReturn(height)
+        return ThumbnailData(thumbnail = bitmap, rotation = rotation)
+    }
+
+    companion object {
+        const val THUMBNAIL_WIDTH = 100
+        const val THUMBNAIL_HEIGHT = 200
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt
new file mode 100644
index 0000000..b49923f
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.ui.mapper
+
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.drawable.ShapeDrawable
+import android.platform.test.annotations.EnableFlags
+import android.view.Surface
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.Flags
+import com.android.quickstep.recents.ui.viewmodel.TaskData
+import com.android.quickstep.task.thumbnail.TaskHeaderUiState
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TaskUiStateMapperTest {
+
+    /** TaskHeaderUiState */
+    @Test
+    fun taskData_isNull_returns_HideHeader() {
+        val result =
+            TaskUiStateMapper.toTaskHeaderState(
+                taskData = null,
+                hasHeader = false,
+                clickCloseListener = null,
+            )
+        assertThat(result).isEqualTo(TaskHeaderUiState.HideHeader)
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+    @Test
+    fun taskData_hasHeader_and_taskData_returnsShowHeader() {
+        val inputs =
+            listOf(
+                TASK_DATA,
+                TASK_DATA.copy(thumbnailData = null),
+                TASK_DATA.copy(isLocked = true),
+                TASK_DATA.copy(title = null),
+            )
+        val closeCallback = View.OnClickListener {}
+        val expected =
+            TaskHeaderUiState.ShowHeader(
+                header =
+                    TaskHeaderUiState.ThumbnailHeader(
+                        TASK_ICON,
+                        TASK_TITLE_DESCRIPTION,
+                        closeCallback,
+                    )
+            )
+        inputs.forEach { taskData ->
+            val result =
+                TaskUiStateMapper.toTaskHeaderState(
+                    taskData = taskData,
+                    hasHeader = true,
+                    clickCloseListener = closeCallback,
+                )
+            assertThat(result).isEqualTo(expected)
+        }
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+    @Test
+    fun taskData_hasHeader_emptyTaskData_returns_HideHeader() {
+        val inputs =
+            listOf(
+                TASK_DATA.copy(icon = null),
+                TASK_DATA.copy(titleDescription = null),
+                TASK_DATA.copy(icon = null, titleDescription = null),
+            )
+
+        inputs.forEach { taskData ->
+            val result =
+                TaskUiStateMapper.toTaskHeaderState(
+                    taskData = taskData,
+                    hasHeader = true,
+                    clickCloseListener = {},
+                )
+            assertThat(result).isEqualTo(TaskHeaderUiState.HideHeader)
+        }
+    }
+
+    /** TaskThumbnailUiState */
+    @Test
+    fun taskData_isNull_returns_Uninitialized() {
+        val result = TaskUiStateMapper.toTaskThumbnailUiState(taskData = null, isLiveTile = false)
+        assertThat(result).isEqualTo(TaskThumbnailUiState.Uninitialized)
+    }
+
+    @Test
+    fun taskData_isLiveTile_returns_LiveTile() {
+        val inputs =
+            listOf(TASK_DATA, TASK_DATA.copy(thumbnailData = null), TASK_DATA.copy(isLocked = true))
+        inputs.forEach { input ->
+            val result =
+                TaskUiStateMapper.toTaskThumbnailUiState(taskData = input, isLiveTile = true)
+            assertThat(result).isEqualTo(LiveTile)
+        }
+    }
+
+    @Test
+    fun taskData_isStaticTile_returns_SnapshotSplash() {
+        val result =
+            TaskUiStateMapper.toTaskThumbnailUiState(taskData = TASK_DATA, isLiveTile = false)
+
+        val expected =
+            TaskThumbnailUiState.SnapshotSplash(
+                snapshot =
+                    Snapshot(
+                        backgroundColor = TASK_BACKGROUND_COLOR,
+                        bitmap = TASK_THUMBNAIL,
+                        thumbnailRotation = Surface.ROTATION_0,
+                    ),
+                splash = TASK_ICON,
+            )
+
+        assertThat(result).isEqualTo(expected)
+    }
+
+    @Test
+    fun taskData_thumbnailIsNull_returns_BackgroundOnly() {
+        val result =
+            TaskUiStateMapper.toTaskThumbnailUiState(
+                taskData = TASK_DATA.copy(thumbnailData = null),
+                isLiveTile = false,
+            )
+
+        val expected = TaskThumbnailUiState.BackgroundOnly(TASK_BACKGROUND_COLOR)
+        assertThat(result).isEqualTo(expected)
+    }
+
+    @Test
+    fun taskData_isLocked_returns_BackgroundOnly() {
+        val result =
+            TaskUiStateMapper.toTaskThumbnailUiState(
+                taskData = TASK_DATA.copy(isLocked = true),
+                isLiveTile = false,
+            )
+
+        val expected = TaskThumbnailUiState.BackgroundOnly(TASK_BACKGROUND_COLOR)
+        assertThat(result).isEqualTo(expected)
+    }
+
+    private companion object {
+        const val TASK_TITLE_DESCRIPTION = "Title Description 1"
+        var TASK_ID = 1
+        val TASK_ICON = ShapeDrawable()
+        val TASK_THUMBNAIL = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+        val TASK_THUMBNAIL_DATA =
+            ThumbnailData(thumbnail = TASK_THUMBNAIL, rotation = Surface.ROTATION_0)
+        val TASK_BACKGROUND_COLOR = Color.rgb(1, 2, 3)
+        val TASK_DATA =
+            TaskData.Data(
+                TASK_ID,
+                title = "Task 1",
+                titleDescription = TASK_TITLE_DESCRIPTION,
+                icon = TASK_ICON,
+                thumbnailData = TASK_THUMBNAIL_DATA,
+                backgroundColor = TASK_BACKGROUND_COLOR,
+                isLocked = false,
+            )
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt
new file mode 100644
index 0000000..18b9fe9
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/viewmodel/TaskViewModelTest.kt
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.recents.ui.viewmodel
+
+import android.graphics.Color
+import android.graphics.drawable.ShapeDrawable
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_CAPTION_BARS
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_NAV
+import com.android.launcher3.util.SystemUiController.FLAG_LIGHT_STATUS
+import com.android.launcher3.util.TestDispatcherProvider
+import com.android.quickstep.recents.data.FakeRecentsRotationStateRepository
+import com.android.quickstep.recents.domain.model.TaskModel
+import com.android.quickstep.recents.domain.usecase.GetSysUiStatusNavFlagsUseCase
+import com.android.quickstep.recents.domain.usecase.GetTaskUseCase
+import com.android.quickstep.recents.domain.usecase.GetThumbnailPositionUseCase
+import com.android.quickstep.recents.domain.usecase.IsThumbnailValidUseCase
+import com.android.quickstep.recents.viewmodel.RecentsViewData
+import com.android.quickstep.views.TaskViewType
+import com.android.systemui.shared.recents.model.ThumbnailData
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class TaskViewModelTest {
+    private val unconfinedTestDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(unconfinedTestDispatcher)
+
+    private val recentsViewData = RecentsViewData()
+    private val getTaskUseCase = mock<GetTaskUseCase>()
+    private val getThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
+    private val isThumbnailValidUseCase =
+        spy(IsThumbnailValidUseCase(FakeRecentsRotationStateRepository()))
+    private lateinit var sut: TaskViewModel
+
+    @Before
+    fun setUp() {
+        sut = createTaskViewModel(TaskViewType.SINGLE)
+        whenever(getTaskUseCase.invoke(TASK_MODEL_1.id)).thenReturn(flow { emit(TASK_MODEL_1) })
+        whenever(getTaskUseCase.invoke(TASK_MODEL_2.id)).thenReturn(flow { emit(TASK_MODEL_2) })
+        whenever(getTaskUseCase.invoke(TASK_MODEL_3.id)).thenReturn(flow { emit(TASK_MODEL_3) })
+        whenever(getTaskUseCase.invoke(INVALID_TASK_ID)).thenReturn(flow { emit(null) })
+        recentsViewData.runningTaskIds.value = emptySet()
+    }
+
+    @Test
+    fun singleTaskRetrieved_when_validTaskId() =
+        testScope.runTest {
+            sut.bind(TASK_MODEL_1.id)
+            val expectedResult =
+                TaskTileUiState(
+                    tasks = listOf(TASK_MODEL_1.toUiState()),
+                    isLiveTile = false,
+                    hasHeader = false,
+                    sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
+                    taskOverlayEnabled = false,
+                )
+            assertThat(sut.state.first()).isEqualTo(expectedResult)
+        }
+
+    @Test
+    fun hasHeader_when_taskViewTypeIsDesktop() =
+        testScope.runTest {
+            val expectedResults =
+                mapOf(
+                    TaskViewType.SINGLE to false,
+                    TaskViewType.GROUPED to false,
+                    TaskViewType.DESKTOP to true,
+                )
+
+            expectedResults.forEach { (type, expectedResult) ->
+                sut =
+                    TaskViewModel(
+                        taskViewType = type,
+                        recentsViewData = recentsViewData,
+                        getTaskUseCase = getTaskUseCase,
+                        getSysUiStatusNavFlagsUseCase = GetSysUiStatusNavFlagsUseCase(),
+                        isThumbnailValidUseCase = isThumbnailValidUseCase,
+                        getThumbnailPositionUseCase = getThumbnailPositionUseCase,
+                        dispatcherProvider = TestDispatcherProvider(unconfinedTestDispatcher),
+                    )
+                sut.bind(TASK_MODEL_1.id)
+                assertThat(sut.state.first().hasHeader).isEqualTo(expectedResult)
+            }
+        }
+
+    @Test
+    fun multipleTasksRetrieved_when_validTaskIds() =
+        testScope.runTest {
+            sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id, INVALID_TASK_ID)
+            val expectedResult =
+                TaskTileUiState(
+                    tasks =
+                        listOf(
+                            TASK_MODEL_1.toUiState(),
+                            TASK_MODEL_2.toUiState(),
+                            TASK_MODEL_3.toUiState(),
+                            TaskData.NoData(INVALID_TASK_ID),
+                        ),
+                    isLiveTile = false,
+                    hasHeader = false,
+                    sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
+                    taskOverlayEnabled = false,
+                )
+            assertThat(sut.state.first()).isEqualTo(expectedResult)
+        }
+
+    @Test
+    fun isLiveTile_when_runningTasksMatchTasks() =
+        testScope.runTest {
+            recentsViewData.runningTaskShowScreenshot.value = false
+            recentsViewData.runningTaskIds.value =
+                setOf(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+            sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+            val expectedResult =
+                TaskTileUiState(
+                    tasks =
+                        listOf(
+                            TASK_MODEL_1.toUiState(),
+                            TASK_MODEL_2.toUiState(),
+                            TASK_MODEL_3.toUiState(),
+                        ),
+                    isLiveTile = true,
+                    hasHeader = false,
+                    sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
+                    taskOverlayEnabled = false,
+                )
+            assertThat(sut.state.first()).isEqualTo(expectedResult)
+        }
+
+    @Test
+    fun isNotLiveTile_when_runningTaskShowScreenshotIsTrue() =
+        testScope.runTest {
+            recentsViewData.runningTaskShowScreenshot.value = true
+            recentsViewData.runningTaskIds.value =
+                setOf(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+            sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+            val expectedResult =
+                TaskTileUiState(
+                    tasks =
+                        listOf(
+                            TASK_MODEL_1.toUiState(),
+                            TASK_MODEL_2.toUiState(),
+                            TASK_MODEL_3.toUiState(),
+                        ),
+                    isLiveTile = false,
+                    hasHeader = false,
+                    sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
+                    taskOverlayEnabled = false,
+                )
+            assertThat(sut.state.first()).isEqualTo(expectedResult)
+        }
+
+    @Test
+    fun isNotLiveTile_when_runningTasksMatchPartialTasks_lessRunningTasks() =
+        testScope.runTest {
+            recentsViewData.runningTaskShowScreenshot.value = false
+            recentsViewData.runningTaskIds.value = setOf(TASK_MODEL_1.id, TASK_MODEL_2.id)
+            sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+            val expectedResult =
+                TaskTileUiState(
+                    tasks =
+                        listOf(
+                            TASK_MODEL_1.toUiState(),
+                            TASK_MODEL_2.toUiState(),
+                            TASK_MODEL_3.toUiState(),
+                        ),
+                    isLiveTile = false,
+                    hasHeader = false,
+                    sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
+                    taskOverlayEnabled = false,
+                )
+            assertThat(sut.state.first()).isEqualTo(expectedResult)
+        }
+
+    @Test
+    fun isNotLiveTile_when_runningTasksMatchPartialTasks_moreRunningTasks() =
+        testScope.runTest {
+            recentsViewData.runningTaskShowScreenshot.value = false
+            recentsViewData.runningTaskIds.value =
+                setOf(TASK_MODEL_1.id, TASK_MODEL_2.id, TASK_MODEL_3.id)
+            sut.bind(TASK_MODEL_1.id, TASK_MODEL_2.id)
+            val expectedResult =
+                TaskTileUiState(
+                    tasks = listOf(TASK_MODEL_1.toUiState(), TASK_MODEL_2.toUiState()),
+                    isLiveTile = false,
+                    hasHeader = false,
+                    sysUiStatusNavFlags = FLAGS_APPEARANCE_LIGHT_THEME,
+                    taskOverlayEnabled = false,
+                )
+            assertThat(sut.state.first()).isEqualTo(expectedResult)
+        }
+
+    @Test
+    fun noDataAvailable_when_InvalidTaskId() =
+        testScope.runTest {
+            sut.bind(INVALID_TASK_ID)
+            val expectedResult =
+                TaskTileUiState(
+                    listOf(TaskData.NoData(INVALID_TASK_ID)),
+                    isLiveTile = false,
+                    hasHeader = false,
+                    sysUiStatusNavFlags = FLAGS_APPEARANCE_DEFAULT,
+                    taskOverlayEnabled = false,
+                )
+            assertThat(sut.state.first()).isEqualTo(expectedResult)
+        }
+
+    @Test
+    fun taskOverlayEnabled_when_OverlayIsEnabledForVisibleSingleTask() =
+        testScope.runTest {
+            sut.bind(TASK_MODEL_1.id)
+            recentsViewData.overlayEnabled.value = true
+            recentsViewData.settledFullyVisibleTaskIds.value = setOf(1)
+
+            assertThat(sut.state.first().taskOverlayEnabled).isTrue()
+        }
+
+    @Test
+    fun taskOverlayDisabled_when_usingGroupedTask() =
+        testScope.runTest {
+            sut = createTaskViewModel(TaskViewType.GROUPED)
+            sut.bind(TASK_MODEL_1.id)
+            recentsViewData.overlayEnabled.value = true
+            recentsViewData.settledFullyVisibleTaskIds.value = setOf(1)
+
+            assertThat(sut.state.first().taskOverlayEnabled).isFalse()
+        }
+
+    @Test
+    fun taskOverlayDisabled_when_usingDesktopTask() =
+        testScope.runTest {
+            sut = createTaskViewModel(TaskViewType.DESKTOP)
+            sut.bind(TASK_MODEL_1.id)
+            recentsViewData.overlayEnabled.value = true
+            recentsViewData.settledFullyVisibleTaskIds.value = setOf(1)
+
+            assertThat(sut.state.first().taskOverlayEnabled).isFalse()
+        }
+
+    @Test
+    fun taskOverlayDisabled_when_OverlayIsEnabledForInvisibleTask() =
+        testScope.runTest {
+            sut.bind(TASK_MODEL_1.id)
+            recentsViewData.overlayEnabled.value = true
+            recentsViewData.settledFullyVisibleTaskIds.value = setOf(2)
+
+            assertThat(sut.state.first().taskOverlayEnabled).isFalse()
+        }
+
+    @Test
+    fun taskOverlayDisabled_when_OverlayIsDisabledForVisibleTask() =
+        testScope.runTest {
+            sut.bind(TASK_MODEL_1.id)
+            recentsViewData.overlayEnabled.value = false
+            recentsViewData.settledFullyVisibleTaskIds.value = setOf(1)
+
+            assertThat(sut.state.first().taskOverlayEnabled).isFalse()
+        }
+
+    @Test
+    fun shouldShowSplash_calls_useCase() {
+        sut.isThumbnailValid(null, 0, 0)
+        verify(isThumbnailValidUseCase).invoke(anyOrNull(), anyInt(), anyInt())
+    }
+
+    private fun TaskModel.toUiState() =
+        TaskData.Data(
+            taskId = id,
+            title = title,
+            titleDescription = titleDescription,
+            icon = icon!!,
+            thumbnailData = thumbnail,
+            backgroundColor = backgroundColor,
+            isLocked = isLocked,
+        )
+
+    private fun createTaskViewModel(taskViewType: TaskViewType) =
+        TaskViewModel(
+            taskViewType = taskViewType,
+            recentsViewData = recentsViewData,
+            getTaskUseCase = getTaskUseCase,
+            getSysUiStatusNavFlagsUseCase = GetSysUiStatusNavFlagsUseCase(),
+            isThumbnailValidUseCase = isThumbnailValidUseCase,
+            getThumbnailPositionUseCase = getThumbnailPositionUseCase,
+            dispatcherProvider = TestDispatcherProvider(unconfinedTestDispatcher),
+        )
+
+    private companion object {
+        const val INVALID_TASK_ID = -1
+        const val FLAGS_APPEARANCE_LIGHT_THEME = FLAG_LIGHT_STATUS or FLAG_LIGHT_NAV
+        const val FLAGS_APPEARANCE_DEFAULT = 0
+        const val APPEARANCE_LIGHT_THEME =
+            APPEARANCE_LIGHT_CAPTION_BARS or
+                APPEARANCE_LIGHT_STATUS_BARS or
+                APPEARANCE_LIGHT_NAVIGATION_BARS
+
+        val TASK_MODEL_1 =
+            TaskModel(
+                1,
+                "Title 1",
+                "Content Description 1",
+                ShapeDrawable(),
+                ThumbnailData(appearance = APPEARANCE_LIGHT_THEME),
+                Color.BLACK,
+                false,
+            )
+        val TASK_MODEL_2 =
+            TaskModel(
+                2,
+                "Title 2",
+                "Content Description 2",
+                ShapeDrawable(),
+                ThumbnailData(appearance = APPEARANCE_LIGHT_THEME),
+                Color.RED,
+                true,
+            )
+        val TASK_MODEL_3 =
+            TaskModel(
+                3,
+                "Title 3",
+                "Content Description 3",
+                ShapeDrawable(),
+                ThumbnailData(appearance = APPEARANCE_LIGHT_THEME),
+                Color.BLUE,
+                false,
+            )
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt
deleted file mode 100644
index 73aa460..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/GetThumbnailUseCaseTest.kt
+++ /dev/null
@@ -1,84 +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.quickstep.recents.usecase
-
-import android.content.ComponentName
-import android.content.Intent
-import android.graphics.Bitmap
-import android.graphics.Color
-import com.android.quickstep.recents.data.FakeTasksRepository
-import com.android.quickstep.task.viewmodel.TaskOverlayViewModelTest
-import com.android.systemui.shared.recents.model.Task
-import com.android.systemui.shared.recents.model.ThumbnailData
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
-
-/** Test for [GetThumbnailUseCase] */
-class GetThumbnailUseCaseTest {
-    private val task =
-        Task(Task.TaskKey(TASK_ID, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
-            colorBackground = Color.BLACK
-        }
-    private val thumbnailData =
-        ThumbnailData(
-            thumbnail =
-                mock<Bitmap>().apply {
-                    whenever(width).thenReturn(THUMBNAIL_WIDTH)
-                    whenever(height).thenReturn(THUMBNAIL_HEIGHT)
-                }
-        )
-
-    private val tasksRepository = FakeTasksRepository()
-    private val systemUnderTest = GetThumbnailUseCase(tasksRepository)
-
-    @Test
-    fun taskNotSeeded_returnsNull() {
-        assertThat(systemUnderTest.run(TASK_ID)).isNull()
-    }
-
-    @Test
-    fun taskNotLoaded_returnsNull() {
-        tasksRepository.seedTasks(listOf(task))
-
-        assertThat(systemUnderTest.run(TASK_ID)).isNull()
-    }
-
-    @Test
-    fun taskNotVisible_returnsNull() {
-        tasksRepository.seedTasks(listOf(task))
-        tasksRepository.seedThumbnailData(mapOf(TaskOverlayViewModelTest.TASK_ID to thumbnailData))
-
-        assertThat(systemUnderTest.run(TASK_ID)).isNull()
-    }
-
-    @Test
-    fun taskVisible_returnsThumbnail() {
-        tasksRepository.seedTasks(listOf(task))
-        tasksRepository.seedThumbnailData(mapOf(TaskOverlayViewModelTest.TASK_ID to thumbnailData))
-        tasksRepository.setVisibleTasks(setOf(TaskOverlayViewModelTest.TASK_ID))
-
-        assertThat(systemUnderTest.run(TASK_ID)).isEqualTo(thumbnailData.thumbnail)
-    }
-
-    companion object {
-        const val TASK_ID = 0
-        const val THUMBNAIL_WIDTH = 100
-        const val THUMBNAIL_HEIGHT = 200
-    }
-}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCaseTest.kt
deleted file mode 100644
index 92f2efd..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/usecase/SysUiStatusNavFlagsUseCaseTest.kt
+++ /dev/null
@@ -1,110 +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.quickstep.recents.usecase
-
-import android.content.ComponentName
-import android.content.Intent
-import android.graphics.Bitmap
-import android.graphics.Color
-import com.android.quickstep.recents.data.FakeTasksRepository
-import com.android.systemui.shared.recents.model.Task
-import com.android.systemui.shared.recents.model.ThumbnailData
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
-
-/** Test for [SysUiStatusNavFlagsUseCase] */
-class SysUiStatusNavFlagsUseCaseTest {
-    private lateinit var tasksRepository: FakeTasksRepository
-    private lateinit var sysUiStatusNavFlagsUseCase: SysUiStatusNavFlagsUseCase
-
-    @Before
-    fun setup() {
-        tasksRepository = FakeTasksRepository()
-        sysUiStatusNavFlagsUseCase = SysUiStatusNavFlagsUseCase(tasksRepository)
-        initTaskRepository()
-    }
-
-    @Test
-    fun onLightAppearanceReturnExpectedFlags() {
-        assertThat(sysUiStatusNavFlagsUseCase.getSysUiStatusNavFlags(FIRST_TASK_ID))
-            .isEqualTo(FLAGS_APPEARANCE_LIGHT_THEME)
-    }
-
-    @Test
-    fun onDarkAppearanceReturnExpectedFlags() {
-        assertThat(sysUiStatusNavFlagsUseCase.getSysUiStatusNavFlags(SECOND_TASK_ID))
-            .isEqualTo(FLAGS_APPEARANCE_DARK_THEME)
-    }
-
-    @Test
-    fun whenThumbnailIsNullReturnDefault() {
-        assertThat(sysUiStatusNavFlagsUseCase.getSysUiStatusNavFlags(UNKNOWN_TASK_ID))
-            .isEqualTo(FLAGS_DEFAULT)
-    }
-
-    private fun initTaskRepository() {
-        val firstTask =
-            Task(Task.TaskKey(FIRST_TASK_ID, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
-                colorBackground = Color.BLACK
-            }
-        val firstThumbnailData =
-            ThumbnailData(
-                thumbnail =
-                    mock<Bitmap>().apply {
-                        whenever(width).thenReturn(THUMBNAIL_WIDTH)
-                        whenever(height).thenReturn(THUMBNAIL_HEIGHT)
-                    },
-                appearance = APPEARANCE_LIGHT_THEME,
-            )
-
-        val secondTask =
-            Task(Task.TaskKey(SECOND_TASK_ID, 0, Intent(), ComponentName("", ""), 0, 2005)).apply {
-                colorBackground = Color.BLACK
-            }
-        val secondThumbnailData =
-            ThumbnailData(
-                thumbnail =
-                    mock<Bitmap>().apply {
-                        whenever(width).thenReturn(THUMBNAIL_WIDTH)
-                        whenever(height).thenReturn(THUMBNAIL_HEIGHT)
-                    },
-                appearance = APPEARANCE_DARK_THEME,
-            )
-
-        tasksRepository.seedTasks(listOf(firstTask, secondTask))
-        tasksRepository.seedThumbnailData(
-            mapOf(FIRST_TASK_ID to firstThumbnailData, SECOND_TASK_ID to secondThumbnailData)
-        )
-        tasksRepository.setVisibleTasks(setOf(FIRST_TASK_ID, SECOND_TASK_ID))
-    }
-
-    companion object {
-        const val FIRST_TASK_ID = 0
-        const val SECOND_TASK_ID = 100
-        const val UNKNOWN_TASK_ID = 404
-        const val THUMBNAIL_WIDTH = 100
-        const val THUMBNAIL_HEIGHT = 200
-        const val APPEARANCE_LIGHT_THEME = 24
-        const val FLAGS_APPEARANCE_LIGHT_THEME = 5
-        const val APPEARANCE_DARK_THEME = 0
-        const val FLAGS_APPEARANCE_DARK_THEME = 10
-        const val FLAGS_DEFAULT = 0
-    }
-}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/window/RecentsDisplayModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/window/RecentsDisplayModelTest.kt
new file mode 100644
index 0000000..0119679
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/window/RecentsDisplayModelTest.kt
@@ -0,0 +1,118 @@
+/*
+ * 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.recents.window
+
+import android.graphics.Point
+import android.hardware.display.DisplayManager
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.Display
+import android.view.DisplayAdjustments
+import android.view.Surface
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.Flags.FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW
+import com.android.launcher3.Flags.FLAG_ENABLE_LAUNCHER_OVERVIEW_IN_WINDOW
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.window.CachedDisplayInfo
+import com.android.quickstep.fallback.window.RecentsDisplayModel
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(FLAG_ENABLE_LAUNCHER_OVERVIEW_IN_WINDOW, FLAG_ENABLE_FALLBACK_OVERVIEW_IN_WINDOW)
+class RecentsDisplayModelTest {
+    @get:Rule val setFlagsRule = SetFlagsRule()
+
+    // initiate dagger components for injection
+    private val launcherModelHelper = LauncherModelHelper()
+    private val context = spy(launcherModelHelper.sandboxContext)
+    private val displayManager: DisplayManager = context.spyService(DisplayManager::class.java)
+    private val display: Display = mock()
+
+    private lateinit var recentsDisplayModel: RecentsDisplayModel
+
+    private val width = 2208
+    private val height = 1840
+
+    @Before
+    fun setup() {
+        // Mock display
+        val displayInfo = CachedDisplayInfo(Point(width, height), Surface.ROTATION_0)
+        whenever(display.rotation).thenReturn(displayInfo.rotation)
+        whenever(display.displayAdjustments).thenReturn(DisplayAdjustments())
+        whenever(context.display).thenReturn(display)
+
+        // Mock displayManager
+        whenever(displayManager.getDisplay(anyInt())).thenReturn(display)
+
+        runOnMainSync { recentsDisplayModel = RecentsDisplayModel.INSTANCE.get(context) }
+    }
+
+    @Test
+    fun testEnsureSingleton() {
+        val recentsDisplayModel2 = RecentsDisplayModel.INSTANCE.get(context)
+        assert(recentsDisplayModel == recentsDisplayModel2)
+    }
+
+    @Test
+    fun testDefaultDisplayCreation() {
+        Assert.assertNotNull(recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY))
+        Assert.assertNotNull(
+            recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY)
+        )
+    }
+
+    @Test
+    fun testCreateSeparateInstances() {
+        val displayId = Display.DEFAULT_DISPLAY + 1
+        runOnMainSync { recentsDisplayModel.storeDisplayResource(displayId) }
+
+        val defaultManager = recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY)
+        val secondaryManager = recentsDisplayModel.getRecentsWindowManager(displayId)
+        Assert.assertNotSame(defaultManager, secondaryManager)
+
+        val defaultInterface =
+            recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY)
+        val secondInterface = recentsDisplayModel.getFallbackWindowInterface(displayId)
+        Assert.assertNotSame(defaultInterface, secondInterface)
+    }
+
+    @Test
+    fun testDestroy() {
+        Assert.assertNotNull(recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY))
+        Assert.assertNotNull(
+            recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY)
+        )
+        recentsDisplayModel.destroy()
+        Assert.assertNull(recentsDisplayModel.getRecentsWindowManager(Display.DEFAULT_DISPLAY))
+        Assert.assertNull(recentsDisplayModel.getFallbackWindowInterface(Display.DEFAULT_DISPLAY))
+    }
+
+    private fun runOnMainSync(f: Runnable) {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync { f.run() }
+    }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCaseTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCaseTest.kt
deleted file mode 100644
index 0767fb9..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/SplashAlphaUseCaseTest.kt
+++ /dev/null
@@ -1,149 +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.quickstep.task.thumbnail
-
-import android.content.ComponentName
-import android.content.Intent
-import android.graphics.Bitmap
-import android.graphics.Color
-import android.graphics.drawable.Drawable
-import android.view.Surface
-import android.view.Surface.ROTATION_90
-import com.android.quickstep.recents.data.FakeRecentsRotationStateRepository
-import com.android.quickstep.recents.data.FakeTasksRepository
-import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.viewmodel.TaskContainerData
-import com.android.systemui.shared.recents.model.Task
-import com.android.systemui.shared.recents.model.ThumbnailData
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
-
-class SplashAlphaUseCaseTest {
-    private val recentsViewData = RecentsViewData()
-    private val taskContainerData = TaskContainerData()
-    private val taskThumbnailViewData = TaskThumbnailViewData()
-    private val recentTasksRepository = FakeTasksRepository()
-    private val recentsRotationStateRepository = FakeRecentsRotationStateRepository()
-    private val systemUnderTest =
-        SplashAlphaUseCase(
-            recentsViewData,
-            taskContainerData,
-            taskThumbnailViewData,
-            recentTasksRepository,
-            recentsRotationStateRepository,
-        )
-
-    @Test
-    fun execute_withNullThumbnail_showsSplash() = runTest {
-        assertThat(systemUnderTest.execute(0).first()).isEqualTo(SPLASH_HIDDEN)
-    }
-
-    @Test
-    fun execute_withTaskSpecificSplashAlpha_showsSplash() = runTest {
-        setupTask(2)
-        taskContainerData.thumbnailSplashProgress.value = 0.7f
-
-        assertThat(systemUnderTest.execute(2).first()).isEqualTo(0.7f)
-    }
-
-    @Test
-    fun execute_withNoGlobalSplashEnabled_doesntShowSplash() = runTest {
-        setupTask(2)
-
-        assertThat(systemUnderTest.execute(2).first()).isEqualTo(SPLASH_HIDDEN)
-    }
-
-    @Test
-    fun execute_withSameAspectRatioAndRotation_withGlobalSplashEnabled_doesntShowSplash() =
-        runTest {
-            setupTask(2)
-            recentsViewData.thumbnailSplashProgress.value = 0.5f
-            taskThumbnailViewData.width.value = THUMBNAIL_WIDTH * 2
-            taskThumbnailViewData.height.value = THUMBNAIL_HEIGHT * 2
-
-            assertThat(systemUnderTest.execute(2).first()).isEqualTo(SPLASH_HIDDEN)
-        }
-
-    @Test
-    fun execute_withDifferentAspectRatioAndSameRotation_showsSplash() = runTest {
-        setupTask(2)
-        recentsViewData.thumbnailSplashProgress.value = 0.5f
-        taskThumbnailViewData.width.value = THUMBNAIL_WIDTH
-        taskThumbnailViewData.height.value = THUMBNAIL_HEIGHT * 2
-
-        assertThat(systemUnderTest.execute(2).first()).isEqualTo(0.5f)
-    }
-
-    @Test
-    fun execute_withSameAspectRatioAndDifferentRotation_showsSplash() = runTest {
-        setupTask(2, createThumbnailData(rotation = ROTATION_90))
-        recentsViewData.thumbnailSplashProgress.value = 0.5f
-        taskThumbnailViewData.width.value = THUMBNAIL_WIDTH * 2
-        taskThumbnailViewData.height.value = THUMBNAIL_HEIGHT * 2
-
-        assertThat(systemUnderTest.execute(2).first()).isEqualTo(0.5f)
-    }
-
-    @Test
-    fun execute_withDifferentAspectRatioAndRotation_showsSplash() = runTest {
-        setupTask(2, createThumbnailData(rotation = ROTATION_90))
-        recentsViewData.thumbnailSplashProgress.value = 0.5f
-        taskThumbnailViewData.width.value = THUMBNAIL_WIDTH
-        taskThumbnailViewData.height.value = THUMBNAIL_HEIGHT * 2
-
-        assertThat(systemUnderTest.execute(2).first()).isEqualTo(0.5f)
-    }
-
-    private val tasks = (0..5).map(::createTaskWithId)
-
-    private fun setupTask(taskId: Int, thumbnailData: ThumbnailData = createThumbnailData()) {
-        recentTasksRepository.seedThumbnailData(mapOf(taskId to thumbnailData))
-        val expectedIconData = mock<Drawable>()
-        recentTasksRepository.seedIconData(taskId, "Task $taskId", "", expectedIconData)
-        recentTasksRepository.seedTasks(tasks)
-        recentTasksRepository.setVisibleTasks(setOf(taskId))
-    }
-
-    private fun createThumbnailData(
-        rotation: Int = Surface.ROTATION_0,
-        width: Int = THUMBNAIL_WIDTH,
-        height: Int = THUMBNAIL_HEIGHT,
-    ): ThumbnailData {
-        val bitmap = mock<Bitmap>()
-        whenever(bitmap.width).thenReturn(width)
-        whenever(bitmap.height).thenReturn(height)
-
-        return ThumbnailData(thumbnail = bitmap, rotation = rotation)
-    }
-
-    private fun createTaskWithId(taskId: Int) =
-        Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
-            colorBackground = Color.argb(taskId, taskId, taskId, taskId)
-        }
-
-    companion object {
-        const val THUMBNAIL_WIDTH = 100
-        const val THUMBNAIL_HEIGHT = 200
-
-        const val SPLASH_HIDDEN = 0f
-        const val SPLASH_SHOWN = 1f
-    }
-}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
deleted file mode 100644
index a777bd4..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewModelImplTest.kt
+++ /dev/null
@@ -1,294 +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.quickstep.task.thumbnail
-
-import android.content.ComponentName
-import android.content.Intent
-import android.graphics.Bitmap
-import android.graphics.Color
-import android.graphics.Matrix
-import android.graphics.drawable.Drawable
-import android.view.Surface
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.launcher3.util.TestDispatcherProvider
-import com.android.quickstep.recents.data.FakeTasksRepository
-import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
-import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
-import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
-import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Uninitialized
-import com.android.quickstep.task.viewmodel.TaskContainerData
-import com.android.quickstep.task.viewmodel.TaskThumbnailViewModelImpl
-import com.android.systemui.shared.recents.model.Task
-import com.android.systemui.shared.recents.model.ThumbnailData
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
-
-/** Test for [TaskThumbnailView] */
-@RunWith(AndroidJUnit4::class)
-class TaskThumbnailViewModelImplTest {
-    private val dispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(dispatcher)
-
-    private val recentsViewData = RecentsViewData()
-    private val taskContainerData = TaskContainerData()
-    private val dispatcherProvider = TestDispatcherProvider(dispatcher)
-    private val tasksRepository = FakeTasksRepository()
-    private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
-    private val splashAlphaUseCase: SplashAlphaUseCase = mock()
-
-    private val systemUnderTest by lazy {
-        TaskThumbnailViewModelImpl(
-            recentsViewData,
-            taskContainerData,
-            dispatcherProvider,
-            tasksRepository,
-            mGetThumbnailPositionUseCase,
-            splashAlphaUseCase,
-        )
-    }
-
-    private val tasks = (0..5).map(::createTaskWithId)
-
-    @Test
-    fun initialStateIsUninitialized() =
-        testScope.runTest { assertThat(systemUnderTest.uiState.first()).isEqualTo(Uninitialized) }
-
-    @Test
-    fun bindRunningTask_thenStateIs_LiveTile() =
-        testScope.runTest {
-            val taskId = 1
-            tasksRepository.seedTasks(tasks)
-            tasksRepository.setVisibleTasks(setOf(taskId))
-            recentsViewData.runningTaskIds.value = setOf(taskId)
-            systemUnderTest.bind(taskId)
-
-            assertThat(systemUnderTest.uiState.first()).isEqualTo(LiveTile)
-        }
-
-    @Test
-    fun bindRunningTaskShouldShowScreenshot_thenStateIs_SnapshotSplash() =
-        testScope.runTest {
-            val taskId = 1
-            val expectedThumbnailData = createThumbnailData()
-            tasksRepository.seedThumbnailData(mapOf(taskId to expectedThumbnailData))
-            val expectedIconData = mock<Drawable>()
-            tasksRepository.seedIconData(taskId, "Task $taskId", "", expectedIconData)
-            tasksRepository.seedTasks(tasks)
-            tasksRepository.setVisibleTasks(setOf(taskId))
-            recentsViewData.runningTaskIds.value = setOf(taskId)
-            recentsViewData.runningTaskShowScreenshot.value = true
-            systemUnderTest.bind(taskId)
-
-            assertThat(systemUnderTest.uiState.first())
-                .isEqualTo(
-                    SnapshotSplash(
-                        Snapshot(
-                            backgroundColor = Color.rgb(1, 1, 1),
-                            bitmap = expectedThumbnailData.thumbnail!!,
-                            thumbnailRotation = Surface.ROTATION_0,
-                        ),
-                        expectedIconData,
-                    )
-                )
-        }
-
-    @Test
-    fun bindRunningTaskThenStoppedTaskWithoutThumbnail_thenStateChangesToBackgroundOnly() =
-        testScope.runTest {
-            val runningTaskId = 1
-            val stoppedTaskId = 2
-            tasksRepository.seedTasks(tasks)
-            tasksRepository.setVisibleTasks(setOf(runningTaskId, stoppedTaskId))
-            recentsViewData.runningTaskIds.value = setOf(runningTaskId)
-            systemUnderTest.bind(runningTaskId)
-            assertThat(systemUnderTest.uiState.first()).isEqualTo(LiveTile)
-
-            systemUnderTest.bind(stoppedTaskId)
-            assertThat(systemUnderTest.uiState.first())
-                .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2)))
-        }
-
-    @Test
-    fun bindStoppedTaskWithoutThumbnail_thenStateIs_BackgroundOnly_withAlphaRemoved() =
-        testScope.runTest {
-            val stoppedTaskId = 2
-            tasksRepository.seedTasks(tasks)
-            tasksRepository.setVisibleTasks(setOf(stoppedTaskId))
-
-            systemUnderTest.bind(stoppedTaskId)
-            assertThat(systemUnderTest.uiState.first())
-                .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2)))
-        }
-
-    @Test
-    fun bindLockedTaskWithThumbnail_thenStateIs_BackgroundOnly() =
-        testScope.runTest {
-            val taskId = 2
-            tasksRepository.seedThumbnailData(mapOf(taskId to createThumbnailData()))
-            tasks[taskId].isLocked = true
-            tasksRepository.seedTasks(tasks)
-            tasksRepository.setVisibleTasks(setOf(taskId))
-
-            systemUnderTest.bind(taskId)
-            assertThat(systemUnderTest.uiState.first())
-                .isEqualTo(BackgroundOnly(backgroundColor = Color.rgb(2, 2, 2)))
-        }
-
-    @Test
-    fun bindStoppedTaskWithThumbnail_thenStateIs_SnapshotSplash_withAlphaRemoved() =
-        testScope.runTest {
-            val taskId = 2
-            val expectedThumbnailData = createThumbnailData(rotation = Surface.ROTATION_270)
-            tasksRepository.seedThumbnailData(mapOf(taskId to expectedThumbnailData))
-            val expectedIconData = mock<Drawable>()
-            tasksRepository.seedIconData(taskId, "Task $taskId", "", expectedIconData)
-            tasksRepository.seedTasks(tasks)
-            tasksRepository.setVisibleTasks(setOf(taskId))
-
-            systemUnderTest.bind(taskId)
-            assertThat(systemUnderTest.uiState.first())
-                .isEqualTo(
-                    SnapshotSplash(
-                        Snapshot(
-                            backgroundColor = Color.rgb(2, 2, 2),
-                            bitmap = expectedThumbnailData.thumbnail!!,
-                            thumbnailRotation = Surface.ROTATION_270,
-                        ),
-                        expectedIconData,
-                    )
-                )
-        }
-
-    @Test
-    fun bindNonVisibleStoppedTask_whenMadeVisible_thenStateIsSnapshotSplash() =
-        testScope.runTest {
-            val taskId = 2
-            val expectedThumbnailData = createThumbnailData()
-            tasksRepository.seedThumbnailData(mapOf(taskId to expectedThumbnailData))
-            val expectedIconData = mock<Drawable>()
-            tasksRepository.seedIconData(taskId, "Task $taskId", "", expectedIconData)
-            tasksRepository.seedTasks(tasks)
-
-            systemUnderTest.bind(taskId)
-            assertThat(systemUnderTest.uiState.first()).isEqualTo(Uninitialized)
-
-            tasksRepository.setVisibleTasks(setOf(taskId))
-            assertThat(systemUnderTest.uiState.first())
-                .isEqualTo(
-                    SnapshotSplash(
-                        Snapshot(
-                            backgroundColor = Color.rgb(2, 2, 2),
-                            bitmap = expectedThumbnailData.thumbnail!!,
-                            thumbnailRotation = Surface.ROTATION_0,
-                        ),
-                        expectedIconData,
-                    )
-                )
-        }
-
-    @Test
-    fun getSnapshotMatrix_MissingThumbnail() =
-        testScope.runTest {
-            val taskId = 2
-            val isRtl = true
-
-            whenever(mGetThumbnailPositionUseCase.run(taskId, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
-                .thenReturn(MissingThumbnail)
-
-            systemUnderTest.bind(taskId)
-            assertThat(
-                    systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl)
-                )
-                .isEqualTo(Matrix.IDENTITY_MATRIX)
-        }
-
-    @Test
-    fun getSnapshotMatrix_MatrixScaling() =
-        testScope.runTest {
-            val taskId = 2
-            val isRtl = true
-
-            whenever(mGetThumbnailPositionUseCase.run(taskId, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
-                .thenReturn(MatrixScaling(MATRIX, isRotated = false))
-
-            systemUnderTest.bind(taskId)
-            assertThat(
-                    systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl)
-                )
-                .isEqualTo(MATRIX)
-        }
-
-    @Test
-    fun getForegroundScrimDimProgress_returnsForegroundMaxScrim() =
-        testScope.runTest {
-            recentsViewData.tintAmount.value = 0.32f
-            taskContainerData.taskMenuOpenProgress.value = 0f
-            assertThat(systemUnderTest.dimProgress.first()).isEqualTo(0.32f)
-        }
-
-    @Test
-    fun getTaskMenuScrimDimProgress_returnsTaskMenuScrim() =
-        testScope.runTest {
-            recentsViewData.tintAmount.value = 0f
-            taskContainerData.taskMenuOpenProgress.value = 1f
-            assertThat(systemUnderTest.dimProgress.first()).isEqualTo(0.4f)
-        }
-
-    @Test
-    fun getForegroundScrimDimProgress_returnsNoScrim() =
-        testScope.runTest {
-            recentsViewData.tintAmount.value = 0f
-            taskContainerData.taskMenuOpenProgress.value = 0f
-            assertThat(systemUnderTest.dimProgress.first()).isEqualTo(0f)
-        }
-
-    private fun createTaskWithId(taskId: Int) =
-        Task(Task.TaskKey(taskId, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
-            colorBackground = Color.argb(taskId, taskId, taskId, taskId)
-        }
-
-    private fun createThumbnailData(rotation: Int = Surface.ROTATION_0): ThumbnailData {
-        val bitmap = mock<Bitmap>()
-        whenever(bitmap.width).thenReturn(THUMBNAIL_WIDTH)
-        whenever(bitmap.height).thenReturn(THUMBNAIL_HEIGHT)
-
-        return ThumbnailData(thumbnail = bitmap, rotation = rotation)
-    }
-
-    companion object {
-        const val THUMBNAIL_WIDTH = 100
-        const val THUMBNAIL_HEIGHT = 200
-        const val CANVAS_WIDTH = 300
-        const val CANVAS_HEIGHT = 600
-        val MATRIX =
-            Matrix().apply {
-                setValues(floatArrayOf(2.3f, 4.5f, 2.6f, 7.4f, 3.4f, 2.3f, 2.5f, 6.0f, 3.4f))
-            }
-    }
-}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt
deleted file mode 100644
index 2e91f5c..0000000
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/task/viewmodel/TaskOverlayViewModelTest.kt
+++ /dev/null
@@ -1,171 +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.quickstep.task.viewmodel
-
-import android.content.ComponentName
-import android.content.Intent
-import android.graphics.Bitmap
-import android.graphics.Color
-import android.graphics.Matrix
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.quickstep.recents.data.FakeTasksRepository
-import com.android.quickstep.recents.usecase.GetThumbnailPositionUseCase
-import com.android.quickstep.recents.usecase.ThumbnailPositionState.MatrixScaling
-import com.android.quickstep.recents.usecase.ThumbnailPositionState.MissingThumbnail
-import com.android.quickstep.recents.viewmodel.RecentsViewData
-import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Disabled
-import com.android.quickstep.task.thumbnail.TaskOverlayUiState.Enabled
-import com.android.quickstep.task.viewmodel.TaskOverlayViewModel.ThumbnailPositionState
-import com.android.systemui.shared.recents.model.Task
-import com.android.systemui.shared.recents.model.ThumbnailData
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.whenever
-
-/** Test for [TaskOverlayViewModel] */
-@RunWith(AndroidJUnit4::class)
-class TaskOverlayViewModelTest {
-    private val task =
-        Task(Task.TaskKey(TASK_ID, 0, Intent(), ComponentName("", ""), 0, 2000)).apply {
-            colorBackground = Color.BLACK
-        }
-    private val thumbnailData =
-        ThumbnailData(
-            thumbnail =
-                mock<Bitmap>().apply {
-                    whenever(width).thenReturn(THUMBNAIL_WIDTH)
-                    whenever(height).thenReturn(THUMBNAIL_HEIGHT)
-                }
-        )
-    private val recentsViewData = RecentsViewData()
-    private val tasksRepository = FakeTasksRepository()
-    private val mGetThumbnailPositionUseCase = mock<GetThumbnailPositionUseCase>()
-    private val systemUnderTest =
-        TaskOverlayViewModel(task, recentsViewData, mGetThumbnailPositionUseCase, tasksRepository)
-
-    @Test
-    fun initialStateIsDisabled() = runTest {
-        assertThat(systemUnderTest.overlayState.first()).isEqualTo(Disabled)
-    }
-
-    @Test
-    fun recentsViewOverlayDisabled_Disabled() = runTest {
-        recentsViewData.overlayEnabled.value = false
-        recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
-
-        assertThat(systemUnderTest.overlayState.first()).isEqualTo(Disabled)
-    }
-
-    @Test
-    fun taskNotFullyVisible_Disabled() = runTest {
-        recentsViewData.overlayEnabled.value = true
-        recentsViewData.settledFullyVisibleTaskIds.value = setOf()
-
-        assertThat(systemUnderTest.overlayState.first()).isEqualTo(Disabled)
-    }
-
-    @Test
-    fun noThumbnail_Enabled() = runTest {
-        recentsViewData.overlayEnabled.value = true
-        recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
-        task.isLocked = false
-
-        assertThat(systemUnderTest.overlayState.first())
-            .isEqualTo(Enabled(isRealSnapshot = false, thumbnail = null))
-    }
-
-    @Test
-    fun withThumbnail_RealSnapshot_NotLocked_Enabled() = runTest {
-        recentsViewData.overlayEnabled.value = true
-        recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
-        tasksRepository.seedTasks(listOf(task))
-        tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
-        tasksRepository.setVisibleTasks(setOf(TASK_ID))
-        thumbnailData.isRealSnapshot = true
-        task.isLocked = false
-
-        assertThat(systemUnderTest.overlayState.first())
-            .isEqualTo(Enabled(isRealSnapshot = true, thumbnail = thumbnailData.thumbnail))
-    }
-
-    @Test
-    fun withThumbnail_RealSnapshot_Locked_Enabled() = runTest {
-        recentsViewData.overlayEnabled.value = true
-        recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
-        tasksRepository.seedTasks(listOf(task))
-        tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
-        tasksRepository.setVisibleTasks(setOf(TASK_ID))
-        thumbnailData.isRealSnapshot = true
-        task.isLocked = true
-
-        assertThat(systemUnderTest.overlayState.first())
-            .isEqualTo(Enabled(isRealSnapshot = false, thumbnail = thumbnailData.thumbnail))
-    }
-
-    @Test
-    fun withThumbnail_FakeSnapshot_Enabled() = runTest {
-        recentsViewData.overlayEnabled.value = true
-        recentsViewData.settledFullyVisibleTaskIds.value = setOf(TASK_ID)
-        tasksRepository.seedTasks(listOf(task))
-        tasksRepository.seedThumbnailData(mapOf(TASK_ID to thumbnailData))
-        tasksRepository.setVisibleTasks(setOf(TASK_ID))
-        thumbnailData.isRealSnapshot = false
-        task.isLocked = false
-
-        assertThat(systemUnderTest.overlayState.first())
-            .isEqualTo(Enabled(isRealSnapshot = false, thumbnail = thumbnailData.thumbnail))
-    }
-
-    @Test
-    fun getThumbnailMatrix_MissingThumbnail() = runTest {
-        val isRtl = true
-
-        whenever(mGetThumbnailPositionUseCase.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
-            .thenReturn(MissingThumbnail)
-
-        assertThat(systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
-            .isEqualTo(ThumbnailPositionState(Matrix.IDENTITY_MATRIX, isRotated = false))
-    }
-
-    @Test
-    fun getThumbnailMatrix_MatrixScaling() = runTest {
-        val isRtl = true
-        val isRotated = true
-
-        whenever(mGetThumbnailPositionUseCase.run(TASK_ID, CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
-            .thenReturn(MatrixScaling(MATRIX, isRotated))
-
-        assertThat(systemUnderTest.getThumbnailPositionState(CANVAS_WIDTH, CANVAS_HEIGHT, isRtl))
-            .isEqualTo(ThumbnailPositionState(MATRIX, isRotated))
-    }
-
-    companion object {
-        const val TASK_ID = 0
-        const val THUMBNAIL_WIDTH = 100
-        const val THUMBNAIL_HEIGHT = 200
-        const val CANVAS_WIDTH = 300
-        const val CANVAS_HEIGHT = 600
-        val MATRIX =
-            Matrix().apply {
-                setValues(floatArrayOf(2.3f, 4.5f, 2.6f, 7.4f, 3.4f, 2.3f, 2.5f, 6.0f, 3.4f))
-            }
-    }
-}
diff --git a/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
similarity index 95%
rename from quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
index cb59f7d..26418d8 100644
--- a/quickstep/tests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/taskbar/controllers/TaskbarPinningControllerTest.kt
@@ -56,9 +56,9 @@
     private val taskbarSharedState = mock<TaskbarSharedState>()
     private var isInDesktopMode = false
     private val launcherPrefs =
-        mock<LauncherPrefs> {
-            on { get(TASKBAR_PINNING) } doReturn false
-            on { get(TASKBAR_PINNING_IN_DESKTOP_MODE) } doReturn false
+        mock<LauncherPrefs>().apply {
+            doReturn(false).whenever(this).get(TASKBAR_PINNING)
+            doReturn(false).whenever(this).get(TASKBAR_PINNING_IN_DESKTOP_MODE)
         }
     private val statsLogger = mock<StatsLogManager.StatsLogger>()
     private val statsLogManager = mock<StatsLogManager> { on { logger() } doReturn statsLogger }
@@ -70,7 +70,11 @@
         whenever(taskbarActivityContext.launcherPrefs).thenReturn(launcherPrefs)
         whenever(taskbarActivityContext.dragLayer).thenReturn(taskbarDragLayer)
         whenever(taskbarActivityContext.statsLogManager).thenReturn(statsLogManager)
-        whenever(taskbarControllers.taskbarDesktopModeController.areDesktopTasksVisible)
+        whenever(
+                taskbarControllers.taskbarDesktopModeController.isInDesktopModeAndNotInOverview(
+                    taskbarActivityContext.displayId
+                )
+            )
             .thenAnswer { _ -> isInDesktopMode }
         pinningController = spy(TaskbarPinningController(taskbarActivityContext))
         pinningController.init(taskbarControllers, taskbarSharedState)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ActiveTrackpadListTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ActiveTrackpadListTest.kt
new file mode 100644
index 0000000..b4c236e
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/ActiveTrackpadListTest.kt
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.hardware.input.InputManager
+import android.view.InputDevice
+import android.view.InputDevice.SOURCE_MOUSE
+import android.view.InputDevice.SOURCE_TOUCHPAD
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.IntArray
+import com.android.launcher3.util.SandboxApplication
+import com.android.launcher3.util.TestUtil
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.whenever
+
+@RunWith(AndroidJUnit4::class)
+class ActiveTrackpadListTest {
+
+    @get:Rule val context = SandboxApplication()
+
+    private val inputDeviceIds = IntArray()
+    private lateinit var inputManager: InputManager
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        inputManager = context.spyService(InputManager::class.java)
+        doAnswer { inputDeviceIds.toArray() }.whenever(inputManager).inputDeviceIds
+
+        doReturn(null).whenever(inputManager).getInputDevice(eq(1))
+        doReturn(mockDevice(SOURCE_MOUSE or SOURCE_TOUCHPAD))
+            .whenever(inputManager)
+            .getInputDevice(eq(2))
+        doReturn(mockDevice(SOURCE_MOUSE or SOURCE_TOUCHPAD))
+            .whenever(inputManager)
+            .getInputDevice(eq(3))
+        doReturn(mockDevice(SOURCE_MOUSE)).whenever(inputManager).getInputDevice(eq(4))
+    }
+
+    @Test
+    fun `initialize correct devices`() {
+        inputDeviceIds.addAll(IntArray.wrap(1, 2, 3, 4))
+
+        val list = ActiveTrackpadList(context) {}
+        assertEquals(2, list.size())
+        assertTrue(list.contains(2))
+        assertTrue(list.contains(3))
+    }
+
+    @Test
+    fun `update callback not called in constructor`() {
+        inputDeviceIds.addAll(IntArray.wrap(2, 3))
+
+        var updateCalled = false
+        val list = ActiveTrackpadList(context) { updateCalled = true }
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+
+        assertEquals(2, list.size())
+        assertFalse(updateCalled)
+    }
+
+    @Test
+    fun `update called on add only once`() {
+        var updateCalled = false
+        val list = ActiveTrackpadList(context) { updateCalled = true }
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+
+        assertFalse(updateCalled)
+        assertEquals(0, list.size())
+
+        list.onInputDeviceAdded(1)
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+        assertFalse(updateCalled)
+        assertEquals(0, list.size())
+
+        list.onInputDeviceAdded(2)
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+        assertTrue(updateCalled)
+        assertEquals(1, list.size())
+
+        updateCalled = false
+        list.onInputDeviceAdded(3)
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+        assertFalse(updateCalled)
+        assertEquals(2, list.size())
+    }
+
+    @Test
+    fun `update called on remove only once`() {
+        var updateCalled = false
+        inputDeviceIds.addAll(IntArray.wrap(1, 2, 3, 4))
+        val list = ActiveTrackpadList(context) { updateCalled = true }
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+        assertEquals(2, list.size())
+
+        list.onInputDeviceRemoved(2)
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+        assertEquals(1, list.size())
+        assertFalse(updateCalled)
+
+        list.onInputDeviceRemoved(3)
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+        assertEquals(0, list.size())
+        assertTrue(updateCalled)
+    }
+
+    private fun mockDevice(sources: Int) =
+        mock(InputDevice::class.java).apply { doReturn(sources).whenever(this).sources }
+}
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
index ee70e0a..76d36d3 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
@@ -16,7 +16,9 @@
 
 package com.android.quickstep.util
 
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.content.Context
+import android.content.res.Resources
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.launcher3.apppairs.AppPairIcon
 import com.android.launcher3.logging.StatsLogManager
@@ -28,6 +30,7 @@
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.Task.TaskKey
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66
 import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
 import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33
@@ -54,6 +57,7 @@
 @RunWith(AndroidJUnit4::class)
 class AppPairsControllerTest {
     @Mock lateinit var context: Context
+    @Mock lateinit var resources: Resources
     @Mock lateinit var splitSelectStateController: SplitSelectStateController
     @Mock lateinit var statsLogManager: StatsLogManager
 
@@ -109,6 +113,8 @@
         doNothing()
             .whenever(spyAppPairsController)
             .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
+        whenever(mockAppPairIcon.context.resources).thenReturn(resources)
+        whenever(DesktopModeStatus.canEnterDesktopMode(mockAppPairIcon.context)).thenReturn(false)
     }
 
     @Test
@@ -392,6 +398,68 @@
     }
 
     @Test
+    fun handleAppPairLaunchInApp_freeformTask1IsOnScreen_shouldLaunchAppPair() {
+        whenever(DesktopModeStatus.canEnterDesktopMode(mockAppPairIcon.context)).thenReturn(true)
+        /// Test launching apps 1 and 2 from app pair
+        whenever(mockTaskKey1.getId()).thenReturn(1)
+        whenever(mockTaskKey2.getId()).thenReturn(2)
+        // Task 1 is in freeform windowing mode
+        mockTaskKey1.windowingMode = WINDOWING_MODE_FREEFORM
+        // ... and app 1 is already on screen
+        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+            whenever(mockCachedTaskInfo.topGroupedTaskContainsTask(eq(1))).thenReturn(true)
+        } else {
+            whenever(mockCachedTaskInfo.taskId).thenReturn(1)
+        }
+
+        // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback
+        spyAppPairsController.handleAppPairLaunchInApp(
+            mockAppPairIcon,
+            listOf(mockItemInfo1, mockItemInfo2),
+        )
+        verify(splitSelectStateController)
+            .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture())
+        val callback: Consumer<Array<Task>> = callbackCaptor.value
+        callback.accept(arrayOf(mockTask1, mockTask2))
+
+        // Verify that launchAppPair was called
+        verify(spyAppPairsController, times(1)).launchAppPair(any(), any())
+        verify(spyAppPairsController, never())
+            .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
+    }
+
+    @Test
+    fun handleAppPairLaunchInApp_freeformTask2IsOnScreen_shouldLaunchAppPair() {
+        whenever(DesktopModeStatus.canEnterDesktopMode(mockAppPairIcon.context)).thenReturn(true)
+        /// Test launching apps 1 and 2 from app pair
+        whenever(mockTaskKey1.getId()).thenReturn(1)
+        whenever(mockTaskKey2.getId()).thenReturn(2)
+        // Task 2 is in freeform windowing mode
+        mockTaskKey1.windowingMode = WINDOWING_MODE_FREEFORM
+        // ... and app 2 is already on screen
+        if (com.android.wm.shell.Flags.enableShellTopTaskTracking()) {
+            whenever(mockCachedTaskInfo.topGroupedTaskContainsTask(eq(2))).thenReturn(true)
+        } else {
+            whenever(mockCachedTaskInfo.taskId).thenReturn(2)
+        }
+
+        // Trigger app pair launch, capture and run callback from findLastActiveTasksAndRunCallback
+        spyAppPairsController.handleAppPairLaunchInApp(
+            mockAppPairIcon,
+            listOf(mockItemInfo1, mockItemInfo2),
+        )
+        verify(splitSelectStateController)
+            .findLastActiveTasksAndRunCallback(any(), any(), callbackCaptor.capture())
+        val callback: Consumer<Array<Task>> = callbackCaptor.value
+        callback.accept(arrayOf(mockTask1, mockTask2))
+
+        // Verify that launchAppPair was called
+        verify(spyAppPairsController, times(1)).launchAppPair(any(), any())
+        verify(spyAppPairsController, never())
+            .launchToSide(anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull())
+    }
+
+    @Test
     fun handleAppPairLaunchInApp_shouldLaunchAppPairNormallyWhenUnrelatedSingleAppIsFullscreen() {
         // Test launching apps 1 and 2 from app pair
         whenever(mockTaskKey1.getId()).thenReturn(1)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt
index 7aed579..6fbf482 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/DesktopTaskTest.kt
@@ -29,35 +29,42 @@
 
     @Test
     fun testDesktopTask_sameInstance_isEqual() {
-        val task = DesktopTask(createTasks(1))
+        val task = DesktopTask(deskId = 0, createTasks(1))
         assertThat(task).isEqualTo(task)
     }
 
     @Test
     fun testDesktopTask_identicalConstructor_isEqual() {
-        val task1 = DesktopTask(createTasks(1))
-        val task2 = DesktopTask(createTasks(1))
+        val task1 = DesktopTask(deskId = 0, createTasks(1))
+        val task2 = DesktopTask(deskId = 0, createTasks(1))
         assertThat(task1).isEqualTo(task2)
     }
 
     @Test
     fun testDesktopTask_copy_isEqual() {
-        val task1 = DesktopTask(createTasks(1))
+        val task1 = DesktopTask(deskId = 0, createTasks(1))
         val task2 = task1.copy()
         assertThat(task1).isEqualTo(task2)
     }
 
     @Test
-    fun testDesktopTask_differentId_isNotEqual() {
-        val task1 = DesktopTask(createTasks(1))
-        val task2 = DesktopTask(createTasks(2))
+    fun testDesktopTask_differentDeskIds_isNotEqual() {
+        val task1 = DesktopTask(deskId = 0, createTasks(1))
+        val task2 = DesktopTask(deskId = 1, createTasks(1))
+        assertThat(task1).isNotEqualTo(task2)
+    }
+
+    @Test
+    fun testDesktopTask_differentTaskIds_isNotEqual() {
+        val task1 = DesktopTask(deskId = 0, createTasks(1))
+        val task2 = DesktopTask(deskId = 0, createTasks(2))
         assertThat(task1).isNotEqualTo(task2)
     }
 
     @Test
     fun testDesktopTask_differentLength_isNotEqual() {
-        val task1 = DesktopTask(createTasks(1))
-        val task2 = DesktopTask(createTasks(1, 2))
+        val task1 = DesktopTask(deskId = 0, createTasks(1))
+        val task2 = DesktopTask(deskId = 0, createTasks(1, 2))
         assertThat(task1).isNotEqualTo(task2)
     }
 
diff --git a/quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
similarity index 90%
rename from quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
rename to quickstep/tests/multivalentTests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
index c190cfe..555e62b 100644
--- a/quickstep/tests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GestureExclusionManagerTest.kt
@@ -18,11 +18,12 @@
 
 import android.graphics.Rect
 import android.graphics.Region
-import android.testing.AndroidTestingRunner
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.IWindowManager
+import androidx.test.annotation.UiThreadTest
 import androidx.test.filters.SmallTest
 import com.android.launcher3.util.Executors
+import com.android.launcher3.util.LauncherMultivalentJUnit
 import com.android.quickstep.util.GestureExclusionManager.ExclusionListener
 import org.junit.Before
 import org.junit.Test
@@ -31,11 +32,12 @@
 import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.reset
 import org.mockito.kotlin.verify
-import org.mockito.kotlin.verifyZeroInteractions
+import org.mockito.kotlin.verifyNoMoreInteractions
 
 /** Unit test for [GestureExclusionManager]. */
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@UiThreadTest
+@RunWith(LauncherMultivalentJUnit::class)
 class GestureExclusionManagerTest {
 
     @Mock private lateinit var windowManager: IWindowManager
@@ -72,7 +74,7 @@
         underTest.addListener(listener2)
 
         awaitTasksCompleted()
-        verifyZeroInteractions(windowManager)
+        verifyNoMoreInteractions(windowManager)
     }
 
     @Test
@@ -98,7 +100,7 @@
         underTest.removeListener(listener1)
 
         awaitTasksCompleted()
-        verifyZeroInteractions(windowManager)
+        verifyNoMoreInteractions(windowManager)
     }
 
     @Test
@@ -119,12 +121,12 @@
         awaitTasksCompleted()
         underTest.addListener(listener1)
         awaitTasksCompleted()
-        verifyZeroInteractions(listener1)
+        verifyNoMoreInteractions(listener1)
 
         underTest.addListener(listener2)
         awaitTasksCompleted()
 
-        verifyZeroInteractions(listener1)
+        verifyNoMoreInteractions(listener1)
         verify(listener2).onGestureExclusionChanged(r1, r2)
     }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
index 108cfb5..67fc62f 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/GroupTaskTest.kt
@@ -21,7 +21,6 @@
 import android.graphics.Rect
 import com.android.launcher3.util.LauncherMultivalentJUnit
 import com.android.launcher3.util.SplitConfigurationOptions
-import com.android.quickstep.views.TaskViewType
 import com.android.systemui.shared.recents.model.Task
 import com.android.wm.shell.shared.split.SplitScreenConstants
 import com.google.common.truth.Truth.assertThat
@@ -33,28 +32,28 @@
 
     @Test
     fun testGroupTask_sameInstance_isEqual() {
-        val task = GroupTask(createTask(1))
+        val task = SingleTask(createTask(1))
         assertThat(task).isEqualTo(task)
     }
 
     @Test
     fun testGroupTask_identicalConstructor_isEqual() {
-        val task1 = GroupTask(createTask(1))
-        val task2 = GroupTask(createTask(1))
+        val task1 = SingleTask(createTask(1))
+        val task2 = SingleTask(createTask(1))
         assertThat(task1).isEqualTo(task2)
     }
 
     @Test
     fun testGroupTask_copy_isEqual() {
-        val task1 = GroupTask(createTask(1))
+        val task1 = SingleTask(createTask(1))
         val task2 = task1.copy()
         assertThat(task1).isEqualTo(task2)
     }
 
     @Test
     fun testGroupTask_differentId_isNotEqual() {
-        val task1 = GroupTask(createTask(1))
-        val task2 = GroupTask(createTask(2))
+        val task1 = SingleTask(createTask(1))
+        val task2 = SingleTask(createTask(2))
         assertThat(task1).isNotEqualTo(task2)
     }
 
@@ -66,10 +65,10 @@
                 Rect(),
                 1,
                 2,
-                SplitScreenConstants.SNAP_TO_2_50_50
+                SplitScreenConstants.SNAP_TO_2_50_50,
             )
-        val task1 = GroupTask(createTask(1), createTask(2), splitBounds, TaskViewType.GROUPED)
-        val task2 = GroupTask(createTask(1), createTask(2), splitBounds, TaskViewType.GROUPED)
+        val task1 = SplitTask(createTask(1), createTask(2), splitBounds)
+        val task2 = SplitTask(createTask(1), createTask(2), splitBounds)
         assertThat(task1).isEqualTo(task2)
     }
 
@@ -81,7 +80,7 @@
                 Rect(),
                 1,
                 2,
-                SplitScreenConstants.SNAP_TO_2_50_50
+                SplitScreenConstants.SNAP_TO_2_50_50,
             )
         val splitBounds2 =
             SplitConfigurationOptions.SplitBounds(
@@ -89,17 +88,17 @@
                 Rect(),
                 1,
                 2,
-                SplitScreenConstants.SNAP_TO_2_33_66
+                SplitScreenConstants.SNAP_TO_2_33_66,
             )
-        val task1 = GroupTask(createTask(1), createTask(2), splitBounds1, TaskViewType.GROUPED)
-        val task2 = GroupTask(createTask(1), createTask(2), splitBounds2, TaskViewType.GROUPED)
+        val task1 = SplitTask(createTask(1), createTask(2), splitBounds1)
+        val task2 = SplitTask(createTask(1), createTask(2), splitBounds2)
         assertThat(task1).isNotEqualTo(task2)
     }
 
     @Test
     fun testGroupTask_differentType_isNotEqual() {
-        val task1 = GroupTask(createTask(1), null, null, TaskViewType.SINGLE)
-        val task2 = GroupTask(createTask(1), null, null, TaskViewType.DESKTOP)
+        val task1 = SingleTask(createTask(1))
+        val task2 = DesktopTask(deskId = 0, listOf(createTask(1)))
         assertThat(task1).isNotEqualTo(task2)
     }
 
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentOrientedStateHandlerTests.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentOrientedStateHandlerTests.kt
new file mode 100644
index 0000000..3cdf608
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/RecentOrientedStateHandlerTests.kt
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.view.Surface
+import android.view.Surface.ROTATION_0
+import android.view.Surface.ROTATION_180
+import android.view.Surface.ROTATION_90
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.quickstep.FallbackActivityInterface
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler
+import com.android.quickstep.orientation.RecentsPagedOrientationHandler.Companion.PORTRAIT
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.whenever
+
+/**
+ * Test all possible inputs to RecentsOrientedState.updateHandler. It tests all possible
+ * combinations of rotations and relevant methods (two methods that return boolean values) but it
+ * only provides the expected result when the final rotation is different from ROTATION_0 for
+ * simplicity. So any case not shown in resultMap you can assume results in ROTATION_0.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RecentOrientedStateHandlerTests {
+
+    data class TestCase(
+        val recentsRotation: Int,
+        val displayRotation: Int,
+        val touchRotation: Int,
+        val isRotationAllowed: Boolean,
+        val isFixedLandscape: Boolean,
+    ) {
+        override fun toString(): String {
+            return "TestCase(recentsRotation=${Surface.rotationToString(recentsRotation)}, " +
+                "displayRotation=${Surface.rotationToString(displayRotation)}, " +
+                "touchRotation=${Surface.rotationToString(touchRotation)}, " +
+                "isRotationAllowed=$isRotationAllowed, " +
+                "isFixedLandscape=$isFixedLandscape)"
+        }
+    }
+
+    private fun runTestCase(testCase: TestCase, expectedHandler: RecentsPagedOrientationHandler) {
+        val recentOrientedState =
+            spy(
+                RecentsOrientedState(
+                    ApplicationProvider.getApplicationContext(),
+                    FallbackActivityInterface.INSTANCE,
+                ) {}
+            )
+        whenever(recentOrientedState.isRecentsActivityRotationAllowed).thenAnswer {
+            testCase.isRotationAllowed
+        }
+        whenever(recentOrientedState.isLauncherFixedLandscape).thenAnswer {
+            testCase.isFixedLandscape
+        }
+
+        recentOrientedState.update(testCase.displayRotation, testCase.touchRotation)
+        val rotation = recentOrientedState.orientationHandler.rotation
+        assertWithMessage("$testCase to ${Surface.rotationToString(rotation)},")
+            .that(rotation)
+            .isEqualTo(expectedHandler.rotation)
+    }
+
+    @Test
+    fun `test fixed landscape when device is portrait`() {
+        runTestCase(
+            TestCase(
+                recentsRotation = ROTATION_0,
+                displayRotation = -1,
+                touchRotation = ROTATION_0,
+                isRotationAllowed = false,
+                isFixedLandscape = true,
+            ),
+            PORTRAIT,
+        )
+    }
+
+    @Test
+    fun `test fixed landscape when device is landscape`() {
+        runTestCase(
+            TestCase(
+                recentsRotation = ROTATION_90,
+                displayRotation = -1,
+                touchRotation = ROTATION_0,
+                isRotationAllowed = false,
+                isFixedLandscape = true,
+            ),
+            PORTRAIT,
+        )
+    }
+
+    @Test
+    fun `test fixed landscape when device is seascape`() {
+        runTestCase(
+            TestCase(
+                recentsRotation = ROTATION_180,
+                displayRotation = -1,
+                touchRotation = ROTATION_0,
+                isRotationAllowed = false,
+                isFixedLandscape = true,
+            ),
+            PORTRAIT,
+        )
+    }
+
+    @Test
+    fun `test fixed landscape when device is portrait and display rotation is portrait`() {
+        runTestCase(
+            TestCase(
+                recentsRotation = ROTATION_0,
+                displayRotation = ROTATION_0,
+                touchRotation = ROTATION_0,
+                isRotationAllowed = false,
+                isFixedLandscape = true,
+            ),
+            PORTRAIT,
+        )
+    }
+
+    @Test
+    fun `test fixed landscape when device is landscape and display rotation is landscape `() {
+        runTestCase(
+            TestCase(
+                recentsRotation = ROTATION_90,
+                displayRotation = ROTATION_90,
+                touchRotation = ROTATION_0,
+                isRotationAllowed = false,
+                isFixedLandscape = true,
+            ),
+            PORTRAIT,
+        )
+    }
+
+    @Test
+    fun `test fixed landscape when device is seascape and display rotation is seascape`() {
+        runTestCase(
+            TestCase(
+                recentsRotation = ROTATION_180,
+                displayRotation = ROTATION_180,
+                touchRotation = ROTATION_0,
+                isRotationAllowed = false,
+                isFixedLandscape = true,
+            ),
+            PORTRAIT,
+        )
+    }
+}
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 5051251..c9d7e1d 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitAnimationControllerTest.kt
@@ -89,11 +89,12 @@
     @Before
     fun setup() {
         whenever(mockTaskContainer.snapshotView).thenReturn(mockSnapshotView)
-        whenever(mockTaskContainer.splitAnimationThumbnail).thenReturn(mockBitmap)
+        whenever(mockTaskContainer.thumbnail).thenReturn(mockBitmap)
         whenever(mockTaskContainer.iconView).thenReturn(mockIconView)
         whenever(mockTaskContainer.task).thenReturn(mockTask)
         whenever(mockIconView.drawable).thenReturn(mockTaskViewDrawable)
         whenever(mockTaskView.taskContainers).thenReturn(List(1) { mockTaskContainer })
+        whenever(mockTaskView.firstTaskContainer).thenReturn(mockTaskContainer)
 
         whenever(splitSelectSource.drawable).thenReturn(mockSplitSourceDrawable)
         whenever(splitSelectSource.view).thenReturn(mockSplitSourceView)
@@ -117,7 +118,7 @@
         assertEquals(
             "Did not fallback to use splitSource icon drawable",
             mockSplitSourceDrawable,
-            splitAnimInitProps.iconDrawable
+            splitAnimInitProps.iconDrawable,
         )
     }
 
@@ -133,7 +134,7 @@
         assertEquals(
             "Did not use taskView icon drawable",
             mockTaskViewDrawable,
-            splitAnimInitProps.iconDrawable
+            splitAnimInitProps.iconDrawable,
         )
     }
 
@@ -152,7 +153,7 @@
         assertEquals(
             "Did not use taskView icon drawable",
             mockTaskViewDrawable,
-            splitAnimInitProps.iconDrawable
+            splitAnimInitProps.iconDrawable,
         )
     }
 
@@ -168,7 +169,7 @@
         assertEquals(
             "Did not use splitSource icon drawable",
             mockSplitSourceDrawable,
-            splitAnimInitProps.iconDrawable
+            splitAnimInitProps.iconDrawable,
         )
     }
 
@@ -190,13 +191,13 @@
         val splitAnimInitProps: SplitAnimationController.Companion.SplitAnimInitProps =
             splitAnimationController.getFirstAnimInitViews(
                 { mockGroupedTaskView },
-                { splitSelectSource }
+                { splitSelectSource },
             )
 
         assertEquals(
             "Did not use splitSource icon drawable",
             mockSplitSourceDrawable,
-            splitAnimInitProps.iconDrawable
+            splitAnimInitProps.iconDrawable,
         )
     }
 
@@ -214,7 +215,7 @@
                 any(),
                 any(),
                 any(),
-                any()
+                any(),
             )
 
         spySplitAnimationController.playSplitLaunchAnimation(
@@ -230,7 +231,7 @@
             null /* info */,
             null /* t */,
             {} /* finishCallback */,
-            1f /* cornerRadius */
+            1f, /* cornerRadius */
         )
 
         verify(spySplitAnimationController)
@@ -243,7 +244,7 @@
                 any(),
                 any(),
                 any(),
-                any()
+                any(),
             )
     }
 
@@ -267,7 +268,7 @@
             transitionInfo,
             transaction,
             {} /* finishCallback */,
-            1f /* cornerRadius */
+            1f, /* cornerRadius */
         )
 
         verify(spySplitAnimationController)
@@ -296,7 +297,7 @@
             transitionInfo,
             transaction,
             {} /* finishCallback */,
-            1f /* cornerRadius */
+            1f, /* cornerRadius */
         )
 
         verify(spySplitAnimationController)
@@ -325,7 +326,7 @@
             transitionInfo,
             transaction,
             {} /* finishCallback */,
-            1f /* cornerRadius */
+            1f, /* cornerRadius */
         )
 
         verify(spySplitAnimationController)
@@ -353,7 +354,7 @@
             transitionInfo,
             transaction,
             {} /* finishCallback */,
-            1f /* cornerRadius */
+            1f, /* cornerRadius */
         )
 
         verify(spySplitAnimationController)
@@ -381,7 +382,7 @@
             transitionInfo,
             transaction,
             {} /* finishCallback */,
-            1f /* cornerRadius */
+            1f, /* cornerRadius */
         )
 
         verify(spySplitAnimationController)
@@ -408,7 +409,7 @@
             transitionInfo,
             transaction,
             {} /* finishCallback */,
-            1f /* cornerRadius */
+            1f, /* cornerRadius */
         )
 
         verify(spySplitAnimationController)
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
index 708273e..e4bdba5 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/SplitSelectStateControllerTest.kt
@@ -102,12 +102,12 @@
     fun activeTasks_noMatchingTasks() {
         val nonMatchingComponent = ComponentKey(ComponentName("no", "match"), primaryUserHandle)
         val groupTask1 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("pomegranate", "juice"),
                 ComponentName("pumpkin", "pie"),
             )
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("hotdog", "juice"),
                 ComponentName("personal", "computer"),
             )
@@ -143,12 +143,12 @@
         val matchingComponent =
             ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
         val groupTask1 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName(matchingPackage, matchingClass),
                 ComponentName("pomegranate", "juice"),
             )
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("pumpkin", "pie"),
                 ComponentName("personal", "computer"),
             )
@@ -170,7 +170,7 @@
                     it[0].key.baseIntent.component?.className,
                     matchingClass,
                 )
-                assertEquals(it[0], groupTask1.task1)
+                assertEquals(it[0], groupTask1.topLeftTask)
             }
 
         // Capture callback from recentsModel#getTasks()
@@ -196,12 +196,12 @@
         val nonPrimaryUserComponent =
             ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle)
         val groupTask1 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName(matchingPackage, matchingClass),
                 ComponentName("pomegranate", "juice"),
             )
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("pumpkin", "pie"),
                 ComponentName("personal", "computer"),
             )
@@ -237,14 +237,14 @@
         val nonPrimaryUserComponent =
             ComponentKey(ComponentName(matchingPackage, matchingClass), nonPrimaryUserHandle)
         val groupTask1 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName(matchingPackage, matchingClass),
                 nonPrimaryUserHandle,
                 ComponentName("pomegranate", "juice"),
                 nonPrimaryUserHandle,
             )
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("pumpkin", "pie"),
                 ComponentName("personal", "computer"),
             )
@@ -267,7 +267,7 @@
                     matchingClass,
                 )
                 assertEquals("userId mismatched", it[0].key.userId, nonPrimaryUserHandle.identifier)
-                assertEquals(it[0], groupTask1.task1)
+                assertEquals(it[0], groupTask1.topLeftTask)
             }
 
         // Capture callback from recentsModel#getTasks()
@@ -293,12 +293,12 @@
         val matchingComponent =
             ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
         val groupTask1 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName(matchingPackage, matchingClass),
                 ComponentName("pumpkin", "pie"),
             )
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("pomegranate", "juice"),
                 ComponentName(matchingPackage, matchingClass),
             )
@@ -320,7 +320,7 @@
                     it[0].key.baseIntent.component?.className,
                     matchingClass,
                 )
-                assertEquals(it[0], groupTask1.task1)
+                assertEquals(it[0], groupTask1.topLeftTask)
             }
 
         // Capture callback from recentsModel#getTasks()
@@ -348,9 +348,9 @@
             ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
 
         val groupTask1 =
-            generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
+            generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("pomegranate", "juice"),
                 ComponentName(matchingPackage, matchingClass),
             )
@@ -374,7 +374,7 @@
                     it[1].key.baseIntent.component?.className,
                     matchingClass,
                 )
-                assertEquals(it[1], groupTask2.task2)
+                assertEquals(it[1], groupTask2.bottomRightTask)
             }
 
         // Capture callback from recentsModel#getTasks()
@@ -401,9 +401,9 @@
             ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
 
         val groupTask1 =
-            generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
+            generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("pomegranate", "juice"),
                 ComponentName(matchingPackage, matchingClass),
             )
@@ -426,7 +426,7 @@
                     it[0].key.baseIntent.component?.className,
                     matchingClass,
                 )
-                assertEquals(it[0], groupTask2.task2)
+                assertEquals(it[0], groupTask2.bottomRightTask)
                 assertNull("No tasks should have matched", it[1] /*task*/)
             }
 
@@ -454,12 +454,12 @@
             ComponentKey(ComponentName(matchingPackage, matchingClass), primaryUserHandle)
 
         val groupTask1 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName(matchingPackage, matchingClass),
                 ComponentName("pumpkin", "pie"),
             )
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("pomegranate", "juice"),
                 ComponentName(matchingPackage, matchingClass),
             )
@@ -482,7 +482,7 @@
                     it[0].key.baseIntent.component?.className,
                     matchingClass,
                 )
-                assertEquals(it[0], groupTask1.task1)
+                assertEquals(it[0], groupTask1.topLeftTask)
                 assertEquals(
                     "ComponentName package mismatched",
                     it[1].key.baseIntent.component?.packageName,
@@ -493,7 +493,7 @@
                     it[1].key.baseIntent.component?.className,
                     matchingClass,
                 )
-                assertEquals(it[1], groupTask2.task2)
+                assertEquals(it[1], groupTask2.bottomRightTask)
             }
 
         // Capture callback from recentsModel#getTasks()
@@ -524,14 +524,14 @@
             ComponentKey(ComponentName(matchingPackage2, matchingClass2), primaryUserHandle)
 
         val groupTask1 =
-            generateGroupTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
+            generateSplitTask(ComponentName("hotdog", "pie"), ComponentName("pumpkin", "pie"))
         val groupTask2 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName(matchingPackage2, matchingClass2),
                 ComponentName(matchingPackage, matchingClass),
             )
         val groupTask3 =
-            generateGroupTask(
+            generateSplitTask(
                 ComponentName("hotdog", "pie"),
                 ComponentName(matchingPackage, matchingClass),
             )
@@ -545,7 +545,7 @@
         val taskConsumer =
             Consumer<Array<Task>> {
                 assertEquals("Expected array length 2", 2, it.size)
-                assertEquals("Found wrong task", it[0], groupTask2.task1)
+                assertEquals("Found wrong task", it[0], groupTask2.topLeftTask)
             }
 
         // Capture callback from recentsModel#getTasks()
@@ -640,11 +640,11 @@
         verify(recentsView, times(0)).resetDesktopTaskFromSplitSelectState()
     }
 
-    // Generate GroupTask with default userId.
-    private fun generateGroupTask(
+    /** Generates a [SplitTask] with default userId. */
+    private fun generateSplitTask(
         task1ComponentName: ComponentName,
         task2ComponentName: ComponentName,
-    ): GroupTask {
+    ): SplitTask {
         val task1 = Task()
         var taskInfo = ActivityManager.RunningTaskInfo()
         taskInfo.taskId = getUniqueId()
@@ -660,20 +660,26 @@
         intent.component = task2ComponentName
         taskInfo.baseIntent = intent
         task2.key = Task.TaskKey(taskInfo)
-        return GroupTask(
+        return SplitTask(
             task1,
             task2,
-            SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50),
+            SplitConfigurationOptions.SplitBounds(
+                /* leftTopBounds = */ Rect(),
+                /* rightBottomBounds = */ Rect(),
+                /* leftTopTaskId = */ task1.key.id,
+                /* rightBottomTaskId = */ task2.key.id,
+                /* snapPosition = */ SNAP_TO_2_50_50,
+            ),
         )
     }
 
-    // Generate GroupTask with custom user handles.
-    private fun generateGroupTask(
+    /** Generates a [SplitTask] with custom user handles. */
+    private fun generateSplitTask(
         task1ComponentName: ComponentName,
         userHandle1: UserHandle,
         task2ComponentName: ComponentName,
         userHandle2: UserHandle,
-    ): GroupTask {
+    ): SplitTask {
         val task1 = Task()
         var taskInfo = ActivityManager.RunningTaskInfo()
         taskInfo.taskId = getUniqueId()
@@ -692,10 +698,16 @@
         intent.component = task2ComponentName
         taskInfo.baseIntent = intent
         task2.key = Task.TaskKey(taskInfo)
-        return GroupTask(
+        return SplitTask(
             task1,
             task2,
-            SplitConfigurationOptions.SplitBounds(Rect(), Rect(), -1, -1, SNAP_TO_2_50_50),
+            SplitConfigurationOptions.SplitBounds(
+                /* leftTopBounds = */ Rect(),
+                /* rightBottomBounds = */ Rect(),
+                /* leftTopTaskId = */ task1.key.id,
+                /* rightBottomTaskId = */ task2.key.id,
+                /* snapPosition = */ SNAP_TO_2_50_50,
+            ),
         )
     }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt
index 7aab75f..cb088fd 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskGridNavHelperTest.kt
@@ -16,12 +16,13 @@
 package com.android.quickstep.util
 
 import com.android.launcher3.util.IntArray
-import com.android.quickstep.util.TaskGridNavHelper.CLEAR_ALL_PLACEHOLDER_ID
-import com.android.quickstep.util.TaskGridNavHelper.DIRECTION_DOWN
-import com.android.quickstep.util.TaskGridNavHelper.DIRECTION_LEFT
-import com.android.quickstep.util.TaskGridNavHelper.DIRECTION_RIGHT
-import com.android.quickstep.util.TaskGridNavHelper.DIRECTION_TAB
-import com.android.quickstep.util.TaskGridNavHelper.DIRECTION_UP
+import com.android.quickstep.util.TaskGridNavHelper.Companion.ADD_DESK_PLACEHOLDER_ID
+import com.android.quickstep.util.TaskGridNavHelper.Companion.CLEAR_ALL_PLACEHOLDER_ID
+import com.android.quickstep.util.TaskGridNavHelper.TaskNavDirection.DOWN
+import com.android.quickstep.util.TaskGridNavHelper.TaskNavDirection.LEFT
+import com.android.quickstep.util.TaskGridNavHelper.TaskNavDirection.RIGHT
+import com.android.quickstep.util.TaskGridNavHelper.TaskNavDirection.TAB
+import com.android.quickstep.util.TaskGridNavHelper.TaskNavDirection.UP
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 
@@ -34,8 +35,7 @@
     */
     @Test
     fun equalLengthRows_noFocused_onTop_pressDown_goesToBottom() {
-        assertThat(getNextGridPage(currentPageTaskViewId = 1, DIRECTION_DOWN, delta = 1))
-            .isEqualTo(2)
+        assertThat(getNextGridPage(currentPageTaskViewId = 1, DOWN, delta = 1)).isEqualTo(2)
     }
 
     /*                      ↑----→
@@ -45,7 +45,7 @@
     */
     @Test
     fun equalLengthRows_noFocused_onTop_pressUp_goesToBottom() {
-        assertThat(getNextGridPage(currentPageTaskViewId = 1, DIRECTION_UP, delta = 1)).isEqualTo(2)
+        assertThat(getNextGridPage(currentPageTaskViewId = 1, UP, delta = 1)).isEqualTo(2)
     }
 
     /*                      ↓----↑
@@ -56,8 +56,7 @@
     */
     @Test
     fun equalLengthRows_noFocused_onBottom_pressDown_goesToTop() {
-        assertThat(getNextGridPage(currentPageTaskViewId = 2, DIRECTION_DOWN, delta = 1))
-            .isEqualTo(1)
+        assertThat(getNextGridPage(currentPageTaskViewId = 2, DOWN, delta = 1)).isEqualTo(1)
     }
 
     /*
@@ -67,7 +66,7 @@
     */
     @Test
     fun equalLengthRows_noFocused_onBottom_pressUp_goesToTop() {
-        assertThat(getNextGridPage(currentPageTaskViewId = 2, DIRECTION_UP, delta = 1)).isEqualTo(1)
+        assertThat(getNextGridPage(currentPageTaskViewId = 2, UP, delta = 1)).isEqualTo(1)
     }
 
     /*
@@ -77,8 +76,7 @@
     */
     @Test
     fun equalLengthRows_noFocused_onTop_pressLeft_goesLeft() {
-        assertThat(getNextGridPage(currentPageTaskViewId = 1, DIRECTION_LEFT, delta = 1))
-            .isEqualTo(3)
+        assertThat(getNextGridPage(currentPageTaskViewId = 1, LEFT, delta = 1)).isEqualTo(3)
     }
 
     /*
@@ -88,8 +86,7 @@
     */
     @Test
     fun equalLengthRows_noFocused_onBottom_pressLeft_goesLeft() {
-        assertThat(getNextGridPage(currentPageTaskViewId = 2, DIRECTION_LEFT, delta = 1))
-            .isEqualTo(4)
+        assertThat(getNextGridPage(currentPageTaskViewId = 2, LEFT, delta = 1)).isEqualTo(4)
     }
 
     /*
@@ -99,8 +96,7 @@
     */
     @Test
     fun equalLengthRows_noFocused_onTop_secondItem_pressRight_goesRight() {
-        assertThat(getNextGridPage(currentPageTaskViewId = 3, DIRECTION_RIGHT, delta = -1))
-            .isEqualTo(1)
+        assertThat(getNextGridPage(currentPageTaskViewId = 3, RIGHT, delta = -1)).isEqualTo(1)
     }
 
     /*
@@ -110,8 +106,7 @@
     */
     @Test
     fun equalLengthRows_noFocused_onBottom_secondItem_pressRight_goesRight() {
-        assertThat(getNextGridPage(currentPageTaskViewId = 4, DIRECTION_RIGHT, delta = -1))
-            .isEqualTo(2)
+        assertThat(getNextGridPage(currentPageTaskViewId = 4, RIGHT, delta = -1)).isEqualTo(2)
     }
 
     /*
@@ -123,7 +118,7 @@
     */
     @Test
     fun equalLengthRows_noFocused_onTop_pressRight_cycleToClearAll() {
-        assertThat(getNextGridPage(currentPageTaskViewId = 1, DIRECTION_RIGHT, delta = -1))
+        assertThat(getNextGridPage(currentPageTaskViewId = 1, RIGHT, delta = -1))
             .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID)
     }
 
@@ -136,7 +131,7 @@
     */
     @Test
     fun equalLengthRows_noFocused_onBottom_pressRight_cycleToClearAll() {
-        assertThat(getNextGridPage(currentPageTaskViewId = 2, DIRECTION_RIGHT, delta = -1))
+        assertThat(getNextGridPage(currentPageTaskViewId = 2, RIGHT, delta = -1))
             .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID)
     }
 
@@ -148,7 +143,7 @@
     */
     @Test
     fun equalLengthRows_noFocused_onTop_lastItem_pressLeft_toClearAll() {
-        assertThat(getNextGridPage(currentPageTaskViewId = 5, DIRECTION_LEFT, delta = 1))
+        assertThat(getNextGridPage(currentPageTaskViewId = 5, LEFT, delta = 1))
             .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID)
     }
 
@@ -160,7 +155,7 @@
     */
     @Test
     fun equalLengthRows_noFocused_onBottom_lastItem_pressLeft_toClearAll() {
-        assertThat(getNextGridPage(currentPageTaskViewId = 6, DIRECTION_LEFT, delta = 1))
+        assertThat(getNextGridPage(currentPageTaskViewId = 6, LEFT, delta = 1))
             .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID)
     }
 
@@ -175,11 +170,7 @@
     @Test
     fun equalLengthRows_noFocused_onClearAll_pressLeft_cycleToFirst() {
         assertThat(
-                getNextGridPage(
-                    currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID,
-                    DIRECTION_LEFT,
-                    delta = 1,
-                )
+                getNextGridPage(currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID, LEFT, delta = 1)
             )
             .isEqualTo(1)
     }
@@ -193,11 +184,7 @@
     @Test
     fun equalLengthRows_noFocused_onClearAll_pressRight_toLastInBottom() {
         assertThat(
-                getNextGridPage(
-                    currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID,
-                    DIRECTION_RIGHT,
-                    delta = -1,
-                )
+                getNextGridPage(currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID, RIGHT, delta = -1)
             )
             .isEqualTo(6)
     }
@@ -213,7 +200,7 @@
         assertThat(
                 getNextGridPage(
                     currentPageTaskViewId = FOCUSED_TASK_ID,
-                    DIRECTION_LEFT,
+                    LEFT,
                     delta = 1,
                     largeTileIds = listOf(FOCUSED_TASK_ID),
                 )
@@ -232,7 +219,7 @@
         assertThat(
                 getNextGridPage(
                     currentPageTaskViewId = FOCUSED_TASK_ID,
-                    DIRECTION_UP,
+                    UP,
                     delta = 1,
                     largeTileIds = listOf(FOCUSED_TASK_ID),
                 )
@@ -253,7 +240,7 @@
         assertThat(
                 getNextGridPage(
                     currentPageTaskViewId = FOCUSED_TASK_ID,
-                    DIRECTION_DOWN,
+                    DOWN,
                     delta = 1,
                     largeTileIds = listOf(FOCUSED_TASK_ID),
                 )
@@ -274,7 +261,7 @@
         assertThat(
                 getNextGridPage(
                     currentPageTaskViewId = FOCUSED_TASK_ID,
-                    DIRECTION_RIGHT,
+                    RIGHT,
                     delta = -1,
                     largeTileIds = listOf(FOCUSED_TASK_ID),
                 )
@@ -296,7 +283,7 @@
         assertThat(
                 getNextGridPage(
                     currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID,
-                    DIRECTION_LEFT,
+                    LEFT,
                     delta = 1,
                     largeTileIds = listOf(FOCUSED_TASK_ID),
                 )
@@ -315,7 +302,7 @@
         assertThat(
                 getNextGridPage(
                     currentPageTaskViewId = 7,
-                    DIRECTION_DOWN,
+                    DOWN,
                     delta = 1,
                     topIds = IntArray.wrap(1, 3, 5, 7),
                 )
@@ -334,7 +321,7 @@
         assertThat(
                 getNextGridPage(
                     /* topIds = */ currentPageTaskViewId = 7,
-                    DIRECTION_UP,
+                    UP,
                     delta = 1,
                     topIds = IntArray.wrap(1, 3, 5, 7),
                 )
@@ -352,7 +339,7 @@
         assertThat(
                 getNextGridPage(
                     /* topIds = */ currentPageTaskViewId = 6,
-                    DIRECTION_LEFT,
+                    LEFT,
                     delta = 1,
                     topIds = IntArray.wrap(1, 3, 5, 7),
                 )
@@ -371,7 +358,7 @@
         assertThat(
                 getNextGridPage(
                     /* topIds = */ currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID,
-                    DIRECTION_RIGHT,
+                    RIGHT,
                     delta = -1,
                     topIds = IntArray.wrap(1, 3, 5, 7),
                 )
@@ -390,7 +377,7 @@
         assertThat(
                 getNextGridPage(
                     currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID,
-                    DIRECTION_RIGHT,
+                    RIGHT,
                     delta = -1,
                     bottomIds = IntArray.wrap(2, 4, 6, 7),
                 )
@@ -405,8 +392,7 @@
     */
     @Test
     fun equalLengthRows_noFocused_onTop_pressTab_goesToBottom() {
-        assertThat(getNextGridPage(currentPageTaskViewId = 1, DIRECTION_TAB, delta = 1))
-            .isEqualTo(2)
+        assertThat(getNextGridPage(currentPageTaskViewId = 1, TAB, delta = 1)).isEqualTo(2)
     }
 
     /*
@@ -417,8 +403,7 @@
     */
     @Test
     fun equalLengthRows_noFocused_onBottom_pressTab_goesToNextTop() {
-        assertThat(getNextGridPage(currentPageTaskViewId = 2, DIRECTION_TAB, delta = 1))
-            .isEqualTo(3)
+        assertThat(getNextGridPage(currentPageTaskViewId = 2, TAB, delta = 1)).isEqualTo(3)
     }
 
     /*
@@ -430,8 +415,7 @@
     */
     @Test
     fun equalLengthRows_noFocused_onTop_pressTabWithShift_goesToPreviousBottom() {
-        assertThat(getNextGridPage(currentPageTaskViewId = 3, DIRECTION_TAB, delta = -1))
-            .isEqualTo(2)
+        assertThat(getNextGridPage(currentPageTaskViewId = 3, TAB, delta = -1)).isEqualTo(2)
     }
 
     /*
@@ -441,11 +425,39 @@
     */
     @Test
     fun equalLengthRows_noFocused_onBottom_pressTabWithShift_goesToTop() {
-        assertThat(getNextGridPage(currentPageTaskViewId = 2, DIRECTION_TAB, delta = -1))
+        assertThat(getNextGridPage(currentPageTaskViewId = 2, TAB, delta = -1)).isEqualTo(1)
+    }
+
+    /*
+                   5   3  [1]
+        CLEAR_ALL
+                   6   4   2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onTop_pressTabWithShift_noCycle_staysOnTop() {
+        assertThat(getNextGridPage(currentPageTaskViewId = 1, TAB, delta = -1, cycle = false))
             .isEqualTo(1)
     }
 
     /*
+                   5   3   1
+       [CLEAR_ALL]
+                   6   4   2
+    */
+    @Test
+    fun equalLengthRows_noFocused_onClearAll_pressTab_noCycle_staysOnClearAll() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID,
+                    TAB,
+                    delta = 1,
+                    cycle = false,
+                )
+            )
+            .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID)
+    }
+
+    /*
                         5   3   1
            CLEAR_ALL                FOCUSED_TASK←--DESKTOP
                         6   4   2
@@ -455,7 +467,7 @@
         assertThat(
                 getNextGridPage(
                     currentPageTaskViewId = DESKTOP_TASK_ID,
-                    DIRECTION_LEFT,
+                    LEFT,
                     delta = 1,
                     largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
                 )
@@ -473,7 +485,7 @@
         assertThat(
                 getNextGridPage(
                     currentPageTaskViewId = FOCUSED_TASK_ID,
-                    DIRECTION_RIGHT,
+                    RIGHT,
                     delta = -1,
                     largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
                 )
@@ -493,7 +505,7 @@
         assertThat(
                 getNextGridPage(
                     currentPageTaskViewId = DESKTOP_TASK_ID,
-                    DIRECTION_RIGHT,
+                    RIGHT,
                     delta = -1,
                     largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
                 )
@@ -514,7 +526,7 @@
         assertThat(
                 getNextGridPage(
                     currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID,
-                    DIRECTION_LEFT,
+                    LEFT,
                     delta = 1,
                     largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
                 )
@@ -533,7 +545,7 @@
         assertThat(
                 getNextGridPage(
                     currentPageTaskViewId = 2,
-                    DIRECTION_RIGHT,
+                    RIGHT,
                     delta = -1,
                     largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
                 )
@@ -552,7 +564,7 @@
         assertThat(
                 getNextGridPage(
                     currentPageTaskViewId = 1,
-                    DIRECTION_RIGHT,
+                    RIGHT,
                     delta = -1,
                     largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
                 )
@@ -571,7 +583,7 @@
         assertThat(
                 getNextGridPage(
                     currentPageTaskViewId = DESKTOP_TASK_ID,
-                    DIRECTION_TAB,
+                    TAB,
                     delta = 1,
                     largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
                 )
@@ -589,7 +601,7 @@
         assertThat(
                 getNextGridPage(
                     currentPageTaskViewId = FOCUSED_TASK_ID,
-                    DIRECTION_LEFT,
+                    LEFT,
                     delta = 1,
                     topIds = IntArray(),
                     bottomIds = IntArray.wrap(2),
@@ -611,7 +623,7 @@
         assertThat(
                 getNextGridPage(
                     currentPageTaskViewId = DESKTOP_TASK_ID,
-                    DIRECTION_TAB,
+                    TAB,
                     delta = -1,
                     largeTileIds = listOf(DESKTOP_TASK_ID, FOCUSED_TASK_ID),
                 )
@@ -619,16 +631,224 @@
             .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID)
     }
 
+    /*
+                        5   3   1→----|
+                                      ↓
+         CLEAR_ALL                   ADD_DESKTOP
+                        6   4   2
+    */
+    @Test
+    fun withAddDesktopButton_pressRightFromTop_goesToAddDesktopButton() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = 1,
+                    RIGHT,
+                    delta = -1,
+                    hasAddDesktopButton = true,
+                )
+            )
+            .isEqualTo(ADD_DESK_PLACEHOLDER_ID)
+    }
+
+    /*
+                    5   3   1
+       CLEAR_ALL                 ADD_DESKTOP
+                                  ↑
+                    6   4   2→----↑
+    */
+    @Test
+    fun withAddDesktopButton_pressRightFromBottom_goesToAddDesktopButton() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = 2,
+                    RIGHT,
+                    delta = -1,
+                    hasAddDesktopButton = true,
+                )
+            )
+            .isEqualTo(ADD_DESK_PLACEHOLDER_ID)
+    }
+
+    /*
+         ↓-------------------------------←|
+         |                                ↑
+         ↓      5   3   1                 |
+    CLEAR_ALL               ADD_DESKTOP--→
+                6   4   2
+    */
+    @Test
+    fun withAddDesktopButton_pressRightFromAddDesktopButton_goesToClearAllButton() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = ADD_DESK_PLACEHOLDER_ID,
+                    RIGHT,
+                    delta = -1,
+                    hasAddDesktopButton = true,
+                )
+            )
+            .isEqualTo(CLEAR_ALL_PLACEHOLDER_ID)
+    }
+
+    /*
+           |→--------------------------------|
+           |                                 |
+           ↑                5   3   1        ↓
+           ←------CLEAR_ALL             ADD_DESKTOP
+
+                            6   4   2
+    */
+    @Test
+    fun withAddDesktopButton_pressLeftFromClearAllButton_goesToAddDesktopButton() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = CLEAR_ALL_PLACEHOLDER_ID,
+                    LEFT,
+                    delta = 1,
+                    hasAddDesktopButton = true,
+                )
+            )
+            .isEqualTo(ADD_DESK_PLACEHOLDER_ID)
+    }
+
+    /*
+                        5   3   1
+                                   ←--↑
+        CLEAR_ALL                  ↓-→ADD_DESKTOP
+                        6   4   2
+    */
+    @Test
+    fun withAddDesktopButton_pressUpOnAddDesktop_stayOnAddDesktopButton() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = ADD_DESK_PLACEHOLDER_ID,
+                    UP,
+                    delta = 1,
+                    hasAddDesktopButton = true,
+                )
+            )
+            .isEqualTo(ADD_DESK_PLACEHOLDER_ID)
+    }
+
+    /*
+                        5   3   1
+        CLEAR_ALL                  ↑--→ADD_DESKTOP
+                                   ↑←--↓
+                        6   4   2
+    */
+    @Test
+    fun withAddDesktopButton_pressDownOnAddDesktop_stayOnAddDesktopButton() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = ADD_DESK_PLACEHOLDER_ID,
+                    DOWN,
+                    delta = 1,
+                    hasAddDesktopButton = true,
+                )
+            )
+            .isEqualTo(ADD_DESK_PLACEHOLDER_ID)
+    }
+
+    /*
+                        5   3   1
+           CLEAR_ALL                DESKTOP--→ADD_DESKTOP
+                        6   4   2
+    */
+    @Test
+    fun withAddDesktopButton_pressRightFromDesktopTask_goesToAddDesktopButton() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = ADD_DESK_PLACEHOLDER_ID,
+                    LEFT,
+                    delta = 1,
+                    largeTileIds = listOf(DESKTOP_TASK_ID),
+                    hasAddDesktopButton = true,
+                )
+            )
+            .isEqualTo(DESKTOP_TASK_ID)
+    }
+
+    /*
+                        5   3   1
+           CLEAR_ALL                DESKTOP←--ADD_DESKTOP
+                        6   4   2
+    */
+    @Test
+    fun withAddDesktopButton_pressLeftFromAddDesktopButton_goesToDesktopTask() {
+        assertThat(
+                getNextGridPage(
+                    currentPageTaskViewId = DESKTOP_TASK_ID,
+                    RIGHT,
+                    delta = -1,
+                    largeTileIds = listOf(DESKTOP_TASK_ID),
+                    hasAddDesktopButton = true,
+                )
+            )
+            .isEqualTo(ADD_DESK_PLACEHOLDER_ID)
+    }
+
+    // Col offset:  0   1   2
+    //             -----------
+    // ID grid:     4   2   0  start
+    //         end [5]  3   1
+    @Test
+    fun gridTaskViewIdOffsetPairInTabOrderSequence_towardsStart() {
+        val expected = listOf(Pair(4, 0), Pair(3, 1), Pair(2, 1), Pair(1, 2), Pair(0, 2))
+        assertThat(
+                gridTaskViewIdOffsetPairInTabOrderSequence(
+                        initialTaskViewId = 5,
+                        towardsStart = true,
+                    )
+                    .toList()
+            )
+            .isEqualTo(expected)
+    }
+
+    // Col offset:  2   1   0
+    //             -----------
+    // ID grid:     4   2  [0] start
+    //          end 5   3   1
+    @Test
+    fun gridTaskViewIdOffsetPairInTabOrderSequence_towardsEnd() {
+        val expected = listOf(Pair(1, 0), Pair(2, 1), Pair(3, 1), Pair(4, 2), Pair(5, 2))
+        assertThat(
+                gridTaskViewIdOffsetPairInTabOrderSequence(
+                        initialTaskViewId = 0,
+                        towardsStart = false,
+                    )
+                    .toList()
+            )
+            .isEqualTo(expected)
+    }
+
     private fun getNextGridPage(
         currentPageTaskViewId: Int,
-        direction: Int,
+        direction: TaskGridNavHelper.TaskNavDirection,
         delta: Int,
         topIds: IntArray = IntArray.wrap(1, 3, 5),
         bottomIds: IntArray = IntArray.wrap(2, 4, 6),
         largeTileIds: List<Int> = emptyList(),
+        hasAddDesktopButton: Boolean = false,
+        cycle: Boolean = true,
     ): Int {
-        val taskGridNavHelper = TaskGridNavHelper(topIds, bottomIds, largeTileIds)
-        return taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, true)
+        val taskGridNavHelper =
+            TaskGridNavHelper(topIds, bottomIds, largeTileIds, hasAddDesktopButton)
+        return taskGridNavHelper.getNextGridPage(currentPageTaskViewId, delta, direction, cycle)
+    }
+
+    private fun gridTaskViewIdOffsetPairInTabOrderSequence(
+        initialTaskViewId: Int,
+        towardsStart: Boolean,
+        topIds: IntArray = IntArray.wrap(0, 2, 4),
+        bottomIds: IntArray = IntArray.wrap(1, 3, 5),
+        largeTileIds: List<Int> = emptyList(),
+        hasAddDesktopButton: Boolean = false,
+    ): Sequence<Pair<Int, Int>> {
+        val taskGridNavHelper =
+            TaskGridNavHelper(topIds, bottomIds, largeTileIds, hasAddDesktopButton)
+        return taskGridNavHelper.gridTaskViewIdOffsetPairInTabOrderSequence(
+            initialTaskViewId,
+            towardsStart,
+        )
     }
 
     private companion object {
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
index fa81680..be76f9e 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TaskViewSimulatorTest.java
@@ -35,6 +35,9 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.util.AllModulesMinusWMProxy;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
 import com.android.launcher3.util.LauncherModelHelper;
@@ -46,6 +49,9 @@
 import com.android.quickstep.FallbackActivityInterface;
 import com.android.quickstep.util.SurfaceTransaction.MockProperties;
 
+import dagger.BindsInstance;
+import dagger.Component;
+
 import org.hamcrest.Description;
 import org.hamcrest.TypeSafeMatcher;
 import org.junit.Assert;
@@ -159,6 +165,11 @@
         void verifyNoTransforms() {
             LauncherModelHelper helper = new LauncherModelHelper();
             try {
+                DisplayController mockController = mock(DisplayController.class);
+
+                helper.sandboxContext.initDaggerComponent(
+                        DaggerTaskViewSimulatorTest_TaskViewSimulatorTestComponent.builder()
+                                .bindDisplayController(mockController));
                 int rotation = mDisplaySize.x > mDisplaySize.y
                         ? Surface.ROTATION_90 : Surface.ROTATION_0;
                 CachedDisplayInfo cdi = new CachedDisplayInfo(mDisplaySize, rotation);
@@ -192,10 +203,7 @@
 
                 DisplayController.Info info = new Info(
                         configurationContext, wmProxy, perDisplayBoundsCache);
-
-                DisplayController mockController = mock(DisplayController.class);
                 when(mockController.getInfo()).thenReturn(info);
-                helper.sandboxContext.putObject(DisplayController.INSTANCE, mockController);
 
                 mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(helper.sandboxContext)
                         .getBestMatch(mAppBounds.width(), mAppBounds.height(), rotation);
@@ -271,4 +279,18 @@
             description.appendValue(mExpected);
         }
     }
+
+    @LauncherAppSingleton
+    @Component(modules = {AllModulesMinusWMProxy.class})
+    interface TaskViewSimulatorTestComponent extends LauncherAppComponent {
+
+        @Component.Builder
+        interface Builder extends LauncherAppComponent.Builder {
+
+            @BindsInstance
+            Builder bindDisplayController(DisplayController controller);
+
+            TaskViewSimulatorTestComponent build();
+        }
+    }
 }
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TransformParamsTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TransformParamsTest.kt
new file mode 100644
index 0000000..6dbb667
--- /dev/null
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/TransformParamsTest.kt
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.util
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_OPEN
+import android.window.TransitionInfo
+import android.window.TransitionInfo.Change
+import android.window.TransitionInfo.FLAG_NONE
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.quickstep.RemoteAnimationTargets
+import com.android.quickstep.util.TransformParams.BuilderProxy.NO_OP
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TransformParamsTest {
+    private val surfaceTransaction = mock<SurfaceTransaction>()
+    private val transaction = mock<SurfaceControl.Transaction>()
+    private val transformParams = TransformParams(::surfaceTransaction)
+
+    private val freeformTaskInfo1 =
+        createTaskInfo(taskId = 1, windowingMode = WINDOWING_MODE_FREEFORM)
+    private val freeformTaskInfo2 =
+        createTaskInfo(taskId = 2, windowingMode = WINDOWING_MODE_FREEFORM)
+    private val fullscreenTaskInfo1 =
+        createTaskInfo(taskId = 1, windowingMode = WINDOWING_MODE_FULLSCREEN)
+
+    @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
+    @Before
+    fun setUp() {
+        whenever(surfaceTransaction.transaction).thenReturn(transaction)
+        whenever(surfaceTransaction.forSurface(anyOrNull()))
+            .thenReturn(mock<SurfaceTransaction.SurfaceProperties>())
+        transformParams.setCornerRadius(CORNER_RADIUS)
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+    fun createSurfaceParams_freeformTasks_overridesCornerRadius() {
+        val transitionInfo = TransitionInfo(TRANSIT_OPEN, FLAG_NONE)
+        val leash1 = mock<SurfaceControl>()
+        val leash2 = mock<SurfaceControl>()
+        transitionInfo.addChange(createChange(freeformTaskInfo1, leash = leash1))
+        transitionInfo.addChange(createChange(freeformTaskInfo2, leash = leash2))
+        transformParams.setTransitionInfo(transitionInfo)
+        transformParams.setTargetSet(createTargetSet(listOf(freeformTaskInfo1, freeformTaskInfo2)))
+
+        transformParams.createSurfaceParams(NO_OP)
+
+        verify(transaction).setCornerRadius(leash1, 0f)
+        verify(transaction).setCornerRadius(leash2, 0f)
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+    fun createSurfaceParams_freeformTasks_overridesCornerRadiusOnlyOnce() {
+        val transitionInfo = TransitionInfo(TRANSIT_OPEN, FLAG_NONE)
+        val leash1 = mock<SurfaceControl>()
+        val leash2 = mock<SurfaceControl>()
+        transitionInfo.addChange(createChange(freeformTaskInfo1, leash = leash1))
+        transitionInfo.addChange(createChange(freeformTaskInfo2, leash = leash2))
+        transformParams.setTransitionInfo(transitionInfo)
+        transformParams.setTargetSet(createTargetSet(listOf(freeformTaskInfo1, freeformTaskInfo2)))
+        transformParams.createSurfaceParams(NO_OP)
+
+        transformParams.createSurfaceParams(NO_OP)
+
+        verify(transaction).setCornerRadius(leash1, 0f)
+        verify(transaction).setCornerRadius(leash2, 0f)
+    }
+
+    @Test
+    @DisableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+    fun createSurfaceParams_flagDisabled_doesntOverrideCornerRadius() {
+        val transitionInfo = TransitionInfo(TRANSIT_OPEN, FLAG_NONE)
+        val leash1 = mock<SurfaceControl>()
+        val leash2 = mock<SurfaceControl>()
+        transitionInfo.addChange(createChange(freeformTaskInfo1, leash = leash1))
+        transitionInfo.addChange(createChange(freeformTaskInfo2, leash = leash2))
+        transformParams.setTransitionInfo(transitionInfo)
+        transformParams.setTargetSet(createTargetSet(listOf(freeformTaskInfo1, freeformTaskInfo2)))
+
+        transformParams.createSurfaceParams(NO_OP)
+
+        verify(transaction, never()).setCornerRadius(leash1, 0f)
+        verify(transaction, never()).setCornerRadius(leash2, 0f)
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX)
+    fun createSurfaceParams_fullscreenTasks_doesntOverrideCornerRadius() {
+        val transitionInfo = TransitionInfo(TRANSIT_OPEN, FLAG_NONE)
+        val leash = mock<SurfaceControl>()
+        transitionInfo.addChange(createChange(fullscreenTaskInfo1, leash = leash))
+        transformParams.setTransitionInfo(transitionInfo)
+        transformParams.setTargetSet(createTargetSet(listOf(fullscreenTaskInfo1)))
+
+        transformParams.createSurfaceParams(NO_OP)
+
+        verify(transaction, never()).setCornerRadius(leash, 0f)
+    }
+
+    private fun createTargetSet(taskInfos: List<RunningTaskInfo>): RemoteAnimationTargets {
+        val remoteAnimationTargets = mutableListOf<RemoteAnimationTarget>()
+        taskInfos.map { remoteAnimationTargets.add(createRemoteAnimationTarget(it)) }
+        return RemoteAnimationTargets(
+            remoteAnimationTargets.toTypedArray(),
+            /* wallpapers= */ null,
+            /* nonApps= */ null,
+            /* targetMode= */ TRANSIT_OPEN,
+        )
+    }
+
+    private fun createRemoteAnimationTarget(taskInfo: RunningTaskInfo): RemoteAnimationTarget {
+        val windowConfig = mock<WindowConfiguration>()
+        whenever(windowConfig.activityType).thenReturn(ACTIVITY_TYPE_STANDARD)
+        return RemoteAnimationTarget(
+            taskInfo.taskId,
+            /* mode= */ TRANSIT_OPEN,
+            /* leash= */ null,
+            /* isTranslucent= */ false,
+            /* clipRect= */ null,
+            /* contentInsets= */ null,
+            /* prefixOrderIndex= */ 0,
+            /* position= */ null,
+            /* localBounds= */ null,
+            /* screenSpaceBounds= */ null,
+            windowConfig,
+            /* isNotInRecents= */ false,
+            /* startLeash= */ null,
+            /* startBounds= */ null,
+            taskInfo,
+            /* allowEnterPip= */ false,
+        )
+    }
+
+    private fun createTaskInfo(taskId: Int, windowingMode: Int): RunningTaskInfo {
+        val taskInfo = RunningTaskInfo()
+        taskInfo.taskId = taskId
+        taskInfo.configuration.windowConfiguration.windowingMode = windowingMode
+        return taskInfo
+    }
+
+    private fun createChange(taskInfo: RunningTaskInfo, leash: SurfaceControl): Change {
+        val taskInfo = createTaskInfo(taskInfo.taskId, taskInfo.windowingMode)
+        val change = Change(taskInfo.token, mock<SurfaceControl>())
+        change.mode = TRANSIT_OPEN
+        change.taskInfo = taskInfo
+        change.leash = leash
+        return change
+    }
+
+    private companion object {
+        private const val CORNER_RADIUS = 30f
+    }
+}
diff --git a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
index c53c177..59ce637 100644
--- a/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
+++ b/quickstep/tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -20,6 +20,7 @@
 import static android.os.Process.myUserHandle;
 
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
@@ -33,7 +34,6 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.spy;
 
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetId;
@@ -42,13 +42,12 @@
 import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherApps;
+import android.os.Process;
 import android.os.UserHandle;
 import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.text.TextUtils;
 
-import androidx.test.core.content.pm.ApplicationInfoBuilder;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
@@ -70,8 +69,6 @@
 
 import java.util.Arrays;
 import java.util.List;
-import java.util.Set;
-import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
 @SmallTest
@@ -90,6 +87,7 @@
     private AppWidgetProviderInfo mApp4Provider1;
     private AppWidgetProviderInfo mApp4Provider2;
     private AppWidgetProviderInfo mApp5Provider1;
+    private AppWidgetProviderInfo mApp6PinOnlyProvider1;
     private List<AppWidgetProviderInfo> allWidgets;
 
     private FakeBgDataModelCallback mCallback = new FakeBgDataModelCallback();
@@ -116,16 +114,22 @@
                 ComponentName.createRelative("app4", ".provider2"));
         mApp5Provider1 = createAppWidgetProviderInfo(
                 ComponentName.createRelative("app5", "provider1"));
+        mApp6PinOnlyProvider1 = createAppWidgetProviderInfo(
+                ComponentName.createRelative("app6", "provider1"),
+                /*hideFromPicker=*/ true
+        );
+
+
         allWidgets = Arrays.asList(mApp1Provider1, mApp1Provider2, mApp2Provider1,
-                mApp4Provider1, mApp4Provider2, mApp5Provider1);
+                mApp4Provider1, mApp4Provider2, mApp5Provider1, mApp6PinOnlyProvider1);
 
         mLauncherApps = mModelHelper.sandboxContext.spyService(LauncherApps.class);
         doAnswer(i -> {
             String pkg = i.getArgument(0);
-            ApplicationInfo applicationInfo = ApplicationInfoBuilder.newBuilder()
-                    .setPackageName(pkg)
-                    .setName("App " + pkg)
-                    .build();
+            ApplicationInfo applicationInfo = new ApplicationInfo();
+            applicationInfo.packageName = pkg;
+            applicationInfo.name = "App " + pkg;
+            applicationInfo.uid = Process.myUid();
             applicationInfo.category = CATEGORY_PRODUCTIVITY;
             applicationInfo.flags = FLAG_INSTALLED;
             return applicationInfo;
@@ -226,46 +230,28 @@
     }
 
     @Test
-    @EnableFlags(Flags.FLAG_ENABLE_TIERED_WIDGETS_BY_DEFAULT_IN_PICKER)
-    public void widgetsRecommendationRan_keepsWidgetsNotOnWorkspace_addsWidgetsFromEligibleApps() {
+    public void widgetsRecommendations_excludesWidgetsHiddenForPicker() {
         runOnExecutorSync(MODEL_EXECUTOR, () -> {
-            WidgetsFilterDataProvider spiedFilterProvider = spy(
-                    mModelHelper.getModel().getWidgetsFilterDataProvider());
-            doAnswer(i -> new Predicate<WidgetItem>() {
-                @Override
-                public boolean test(WidgetItem widgetItem) {
-                    // app5's widget is already on workspace, but, app2 is not.
-                    // And app4's second widget is also not on workspace.
-                    return Set.of("app5", "app2", "app4").contains(
-                            widgetItem.componentName.getPackageName());
-                }
-            }).when(spiedFilterProvider).getPredictedWidgetsFilter();
-            mModelHelper.getBgDataModel().widgetsModel.updateWidgetFilters(spiedFilterProvider);
-            // App5's widget that's already on workspace.
-            AppTarget widget1 = new AppTarget(new AppTargetId("app5"), "app5", "provider1",
+
+            // Not installed widget - hence eligible
+            AppTarget widget1 = new AppTarget(new AppTargetId("app1"), "app1", "provider1",
                     mUserHandle);
-            // App4's widget eligible and not on workspace.
-            AppTarget widget2 = new AppTarget(new AppTargetId("app4"), "app4", "provider2",
+            // Provider marked as hidden from picker - hence not eligible
+            AppTarget widget6 = new AppTarget(new AppTargetId("app6"), "app6", "provider1",
                     mUserHandle);
 
             mCallback.mRecommendedWidgets = null;
             mModelHelper.getModel().enqueueModelUpdateTask(
-                    newWidgetsPredicationTask(List.of(widget1, widget2)));
-            runOnExecutorSync(MAIN_EXECUTOR, () -> {
-            });
+                    newWidgetsPredicationTask(List.of(widget1, widget6)));
+            runOnExecutorSync(MAIN_EXECUTOR, () -> { });
 
+            // Only widget 1 (and no widget 6 as its meant to be hidden from picker).
             List<PendingAddWidgetInfo> recommendedWidgets = mCallback.mRecommendedWidgets.items
                     .stream()
                     .map(itemInfo -> (PendingAddWidgetInfo) itemInfo)
                     .collect(Collectors.toList());
-            assertThat(recommendedWidgets).hasSize(2);
-            List<ComponentName> componentNames = recommendedWidgets.stream().map(
-                    w -> w.componentName).toList();
-            assertThat(componentNames).containsExactly(
-                    // Locally added, not on workspace, eligible app per filter
-                    mApp2Provider1.provider,
-                    // From prediction service, not on workspace, eligible app per filter
-                    mApp4Provider2.provider);
+            assertThat(recommendedWidgets).hasSize(1);
+            assertThat(recommendedWidgets.get(0).componentName.getPackageName()).isEqualTo("app1");
         });
     }
 
@@ -277,7 +263,8 @@
 
     private WidgetsPredictionUpdateTask newWidgetsPredicationTask(List<AppTarget> appTargets) {
         return new WidgetsPredictionUpdateTask(
-                new PredictorState(CONTAINER_WIDGETS_PREDICTION, "test_widgets_prediction"),
+                new PredictorState(CONTAINER_WIDGETS_PREDICTION, "test_widgets_prediction",
+                        DEFAULT_LOOKUP_FLAG),
                 appTargets);
     }
 
diff --git a/quickstep/tests/src/com/android/launcher3/statehandlers/DesktopVisibilityControllerTest.kt b/quickstep/tests/src/com/android/launcher3/statehandlers/DesktopVisibilityControllerTest.kt
new file mode 100644
index 0000000..4b8f2a2
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/statehandlers/DesktopVisibilityControllerTest.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.statehandlers
+
+import android.content.Context
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.launcher3.util.DaggerSingletonTracker
+import com.android.quickstep.SystemUiProxy
+import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
+import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_FRONTEND
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import org.junit.After
+import org.junit.Assert.assertFalse
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
+
+/**
+ * Tests the behavior of [DesktopVisibilityController] in regards to multiple desktops and multiple
+ * displays.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DesktopVisibilityControllerTest {
+
+    @get:Rule val setFlagsRule = SetFlagsRule()
+
+    private val mockitoSession =
+        mockitoSession()
+            .strictness(Strictness.LENIENT)
+            .mockStatic(DesktopModeStatus::class.java)
+            .startMocking()
+
+    private val context = mock<Context>()
+    private val systemUiProxy = mock<SystemUiProxy>()
+    private val lifeCycleTracker = mock<DaggerSingletonTracker>()
+    private lateinit var desktopVisibilityController: DesktopVisibilityController
+
+    @Before
+    fun setUp() {
+        whenever(context.resources).thenReturn(mock())
+        whenever(DesktopModeStatus.enableMultipleDesktops(context)).thenReturn(true)
+        desktopVisibilityController =
+            DesktopVisibilityController(context, systemUiProxy, lifeCycleTracker)
+    }
+
+    @After
+    fun tearDown() {
+        mockitoSession.finishMocking()
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, FLAG_ENABLE_MULTIPLE_DESKTOPS_FRONTEND)
+    fun noCrashWhenCheckingNonExistentDisplay() {
+        assertFalse(desktopVisibilityController.isInDesktopMode(displayId = 500))
+        assertFalse(desktopVisibilityController.isInDesktopModeAndNotInOverview(displayId = 300))
+    }
+}
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
index 67a0ee4..3f7c85c 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/TaskbarHoverToolTipControllerTest.java
@@ -127,11 +127,11 @@
         when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
         waitForIdleSync();
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 true);
     }
@@ -141,11 +141,11 @@
         when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
         waitForIdleSync();
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 false);
     }
@@ -155,11 +155,11 @@
         when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
         waitForIdleSync();
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 true);
     }
@@ -169,11 +169,11 @@
         when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
         waitForIdleSync();
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 false);
     }
@@ -184,11 +184,11 @@
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
         doReturn(true).when(mSpyFolderView).isOpen();
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
         waitForIdleSync();
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 false);
     }
@@ -199,10 +199,10 @@
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
         doReturn(true).when(mSpyFolderView).isOpen();
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
     }
 
     @Test
@@ -210,10 +210,10 @@
         when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_MOVE);
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_MOVE);
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mHoverFolderIcon, mMotionEvent);
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
     }
 
     @Test
@@ -221,11 +221,11 @@
         when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mAppPairIcon, mMotionEvent);
         waitForIdleSync();
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 true);
     }
@@ -235,11 +235,11 @@
         when(mMotionEvent.getAction()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_EXIT);
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mAppPairIcon, mMotionEvent);
         waitForIdleSync();
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 false);
     }
@@ -250,11 +250,11 @@
         when(mMotionEvent.getActionMasked()).thenReturn(MotionEvent.ACTION_HOVER_ENTER);
         when(taskbarActivityContext.isIconAlignedWithHotseat()).thenReturn(true);
 
-        boolean hoverHandled =
+        boolean hoverConsumed =
                 mTaskbarHoverToolTipController.onHover(mHoverBubbleTextView, mMotionEvent);
         waitForIdleSync();
 
-        assertThat(hoverHandled).isTrue();
+        assertThat(hoverConsumed).isFalse();
         verify(taskbarActivityContext).setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
                 true);
     }
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index 6a7b6f8..c215ff2 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -24,14 +24,22 @@
 import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.tapl.LaunchedAppState;
+import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.TestUtil;
+import com.android.launcher3.util.Wait;
+import com.android.quickstep.fallback.RecentsState;
+import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.views.RecentsView;
 
 import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
 
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
 /**
  * Base class for all instrumentation tests that deal with Quickstep.
  */
@@ -54,6 +62,44 @@
         }
     }
 
+    // Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call
+    // expecting the results of that gesture because the wait can hide flakeness.
+    protected void waitForRecentsWindowState(String message, Supplier<RecentsState> state) {
+        waitForRecentsWindowCondition(message, recentsWindow ->
+                recentsWindow.getStateManager().getCurrentStableState() == state.get());
+    }
+
+    // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
+    // flakiness.
+    protected void waitForRecentsWindowCondition(String
+            message, Function<RecentsWindowManager, Boolean> condition) {
+        waitForRecentsWindowCondition(message, condition, TestUtil.DEFAULT_UI_TIMEOUT);
+    }
+
+    protected <T> T getFromRecentsWindow(Function<RecentsWindowManager, T> f) {
+        if (!TestHelpers.isInLauncherProcess()) return null;
+        return getOnUiThread(() -> {
+            RecentsWindowManager recentsWindowManager =
+                    RecentsWindowManager.getRecentsWindowTracker().getCreatedContext();
+            return recentsWindowManager != null ? f.apply(recentsWindowManager) : null;
+        });
+    }
+
+    // Cannot be used in TaplTests after injecting any gesture using Tapl because this can hide
+    // flakiness.
+    protected void waitForRecentsWindowCondition(
+            String message, Function<RecentsWindowManager, Boolean> condition, long timeout) {
+        verifyKeyguardInvisible();
+        if (!TestHelpers.isInLauncherProcess()) return;
+        Wait.atMost(message, () -> getFromRecentsWindow(condition), mLauncher, timeout);
+    }
+
+    protected boolean isInRecentsWindowState(Supplier<RecentsState> state) {
+        if (!TestHelpers.isInLauncherProcess()) return true;
+        return getFromRecentsWindow(
+                recentsWindow -> recentsWindow.getStateManager().getState() == state.get());
+    }
+
     protected void assertTestActivityIsRunning(int activityNumber, String message) {
         assertTrue(message, mDevice.wait(
                 Until.hasObject(By.pkg(getAppPackageName()).text("TestActivity" + activityNumber)),
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
index fc757b4..0ccc76b 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractTaplTestsTaskbar.java
@@ -55,7 +55,9 @@
                 "com.android.launcher3.testcomponent.BaseTestingActivity");
         mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, layoutBuilder);
         AbstractLauncherUiTest.initialize(this);
-        startAppFast(CALCULATOR_APP_PACKAGE);
+        if (startCalendarAppDuringSetup()) {
+            startAppFast(CALCULATOR_APP_PACKAGE);
+        }
         mLauncher.enableBlockTimeout(true);
         mLauncher.showTaskbarIfHidden();
     }
@@ -72,8 +74,20 @@
         return DisplayController.isTransientTaskbar(context);
     }
 
+    protected boolean startCalendarAppDuringSetup() {
+        return true;
+    }
+
+    protected boolean expectTaskbarIconsMatchHotseat() {
+        return true;
+    }
+
     protected Taskbar getTaskbar() {
         Taskbar taskbar = mLauncher.getLaunchedAppState().getTaskbar();
+        if (!expectTaskbarIconsMatchHotseat()) {
+            return taskbar;
+        }
+
         List<String> taskbarIconNames = taskbar.getIconNames();
         List<String> hotseatIconNames = mLauncher.getHotseatIconNames();
 
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index 94e7c2e..7b73be7 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -17,27 +17,36 @@
 package com.android.quickstep
 
 import android.content.ComponentName
+import android.content.Context
 import android.content.Intent
+import android.content.pm.PackageManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.platform.app.InstrumentationRegistry
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.internal.R
 import com.android.launcher3.AbstractFloatingView
 import com.android.launcher3.AbstractFloatingViewHelper
 import com.android.launcher3.Flags.enableRefactorTaskThumbnail
 import com.android.launcher3.logging.StatsLogManager
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent
 import com.android.launcher3.model.data.TaskViewItemInfo
-import com.android.launcher3.uioverrides.QuickstepLauncher
 import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.launcher3.util.TransformingTouchDelegate
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay
+import com.android.quickstep.task.thumbnail.TaskContentView
 import com.android.quickstep.task.thumbnail.TaskThumbnailView
 import com.android.quickstep.views.LauncherRecentsView
+import com.android.quickstep.views.RecentsViewContainer
 import com.android.quickstep.views.TaskContainer
 import com.android.quickstep.views.TaskThumbnailViewDeprecated
 import com.android.quickstep.views.TaskView
 import com.android.quickstep.views.TaskViewIcon
 import com.android.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.Task.TaskKey
+import com.android.window.flags.Flags
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
 import com.google.common.truth.Truth.assertThat
@@ -57,7 +66,7 @@
 /** Test for [DesktopSystemShortcut] */
 class DesktopSystemShortcutTest {
 
-    private val launcher: QuickstepLauncher = mock()
+    private val launcher: RecentsViewContainer = mock()
     private val statsLogManager: StatsLogManager = mock()
     private val statsLogger: StatsLogManager.StatsLogger = mock()
     private val recentsView: LauncherRecentsView = mock()
@@ -66,6 +75,7 @@
     private val overlayFactory: TaskOverlayFactory = mock()
     private val factory: TaskShortcutFactory =
         DesktopSystemShortcut.createFactory(abstractFloatingViewHelper)
+    private val context: Context = spy(InstrumentationRegistry.getInstrumentation().targetContext)
 
     private lateinit var mockitoSession: StaticMockitoSession
 
@@ -78,6 +88,7 @@
                 .startMocking()
         whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
         whenever(overlayFactory.createOverlay(any())).thenReturn(mock<TaskOverlay<*>>())
+        whenever(launcher.asContext()).thenReturn(context)
     }
 
     @After
@@ -96,6 +107,80 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+    @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
+    fun createDesktopTaskShortcutFactory_transparentTask() {
+        val baseComponent = ComponentName("", /* class */ "")
+        val taskKey =
+            TaskKey(
+                /* id */ 1,
+                /* windowingMode */ 0,
+                Intent(),
+                baseComponent,
+                /* userId */ 0,
+                /* lastActiveTime */ 2000,
+                DEFAULT_DISPLAY,
+                baseComponent,
+                /* numActivities */ 1,
+                /* isTopActivityNoDisplay */ false,
+                /* isActivityStackTransparent */ true,
+            )
+        val taskContainer = createTaskContainer(Task(taskKey))
+        val shortcuts = factory.getShortcuts(launcher, taskContainer)
+        assertThat(shortcuts).isNull()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+    fun createDesktopTaskShortcutFactory_systemUiTask() {
+        val sysUiPackageName: String = context.resources.getString(R.string.config_systemUi)
+        val baseComponent = ComponentName(sysUiPackageName, /* class */ "")
+        val taskKey =
+            TaskKey(
+                /* id */ 1,
+                /* windowingMode */ 0,
+                Intent(),
+                baseComponent,
+                /* userId */ 0,
+                /* lastActiveTime */ 2000,
+                DEFAULT_DISPLAY,
+                baseComponent,
+                /* numActivities */ 1,
+                /* isTopActivityNoDisplay */ false,
+                /* isActivityStackTransparent */ false,
+            )
+        val taskContainer = createTaskContainer(Task(taskKey))
+        val shortcuts = factory.getShortcuts(launcher, taskContainer)
+        assertThat(shortcuts).isNull()
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+    fun createDesktopTaskShortcutFactory_defaultHomeTask() {
+        val packageManager: PackageManager = mock()
+        val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+        whenever(context.packageManager).thenReturn(packageManager)
+        whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+        val taskKey =
+            TaskKey(
+                /* id */ 1,
+                /* windowingMode */ 0,
+                Intent(),
+                homeActivities,
+                /* userId */ 0,
+                /* lastActiveTime */ 2000,
+                DEFAULT_DISPLAY,
+                homeActivities,
+                /* numActivities */ 1,
+                /* isTopActivityNoDisplay */ false,
+                /* isActivityStackTransparent */ false,
+            )
+        val taskContainer = createTaskContainer(Task(taskKey).apply { isDockable = true })
+        val shortcuts = factory.getShortcuts(launcher, taskContainer)
+        assertThat(shortcuts).isNull()
+    }
+
+    @Test
     fun createDesktopTaskShortcutFactory_undockable() {
         val unDockableTask = createTask().apply { isDockable = false }
         val taskContainer = createTaskContainer(unDockableTask)
@@ -113,6 +198,7 @@
         whenever(launcher.statsLogManager).thenReturn(statsLogManager)
         whenever(statsLogManager.logger()).thenReturn(statsLogger)
         whenever(statsLogger.withItemInfo(any())).thenReturn(statsLogger)
+        whenever(taskView.context).thenReturn(context)
         whenever(recentsView.moveTaskToDesktop(any(), any(), any())).thenAnswer {
             val successCallback = it.getArgument<Runnable>(2)
             successCallback.run()
@@ -142,12 +228,28 @@
     }
 
     private fun createTask() =
-        Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000)).apply { isDockable = true }
+        Task(
+                TaskKey(
+                    /* id */ 1,
+                    /* windowingMode */ 0,
+                    Intent(),
+                    ComponentName("", ""),
+                    /* userId */ 0,
+                    /* lastActiveTime */ 2000,
+                    DEFAULT_DISPLAY,
+                    ComponentName("", ""),
+                    /* numActivities */ 1,
+                    /* isTopActivityNoDisplay */ false,
+                    /* isActivityStackTransparent */ false,
+                )
+            )
+            .apply { isDockable = true }
 
     private fun createTaskContainer(task: Task) =
         TaskContainer(
             taskView,
             task,
+            mock<TaskContentView>(),
             if (enableRefactorTaskThumbnail()) mock<TaskThumbnailView>()
             else mock<TaskThumbnailViewDeprecated>(),
             mock<TaskViewIcon>(),
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index 5b46dc8..c78fe1c 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -21,6 +21,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.app.PendingIntent;
@@ -93,7 +94,8 @@
         final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher));
 
         return getFromLauncher(launcher -> {
-            TaskContainer taskContainer = task.getTaskContainers().get(0);
+            TaskContainer taskContainer = task.getFirstTaskContainer();
+            assertNotNull(taskContainer);
             assertTrue("Latest task is not Calculator", calculatorPackage.equals(
                     taskContainer.getTask().getTopComponent().getPackageName()));
             return taskContainer.getDigitalWellBeingToast();
@@ -101,7 +103,7 @@
     }
 
     private TaskView getLatestTask(Launcher launcher) {
-        return launcher.<RecentsView>getOverviewPanel().getTaskViewAt(0);
+        return launcher.<RecentsView>getOverviewPanel().getFirstTaskView();
     }
 
     private void runWithShellPermission(Runnable action) {
diff --git a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
index 9c2c13c..5aaed7d 100644
--- a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
@@ -17,23 +17,30 @@
 package com.android.quickstep
 
 import android.content.ComponentName
+import android.content.Context
 import android.content.Intent
+import android.content.pm.PackageManager
+import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.platform.app.InstrumentationRegistry
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.internal.R
 import com.android.launcher3.AbstractFloatingView
 import com.android.launcher3.AbstractFloatingViewHelper
 import com.android.launcher3.Flags.enableRefactorTaskThumbnail
 import com.android.launcher3.logging.StatsLogManager
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent
 import com.android.launcher3.model.data.TaskViewItemInfo
-import com.android.launcher3.uioverrides.QuickstepLauncher
 import com.android.launcher3.util.SplitConfigurationOptions
 import com.android.launcher3.util.TransformingTouchDelegate
 import com.android.quickstep.TaskOverlayFactory.TaskOverlay
+import com.android.quickstep.task.thumbnail.TaskContentView
 import com.android.quickstep.task.thumbnail.TaskThumbnailView
 import com.android.quickstep.views.LauncherRecentsView
+import com.android.quickstep.views.RecentsViewContainer
 import com.android.quickstep.views.TaskContainer
 import com.android.quickstep.views.TaskThumbnailViewDeprecated
 import com.android.quickstep.views.TaskView
@@ -62,7 +69,7 @@
 
     @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
 
-    private val launcher: QuickstepLauncher = mock()
+    private val launcher: RecentsViewContainer = mock()
     private val statsLogManager: StatsLogManager = mock()
     private val statsLogger: StatsLogManager.StatsLogger = mock()
     private val recentsView: LauncherRecentsView = mock()
@@ -71,6 +78,7 @@
     private val overlayFactory: TaskOverlayFactory = mock()
     private val factory: TaskShortcutFactory =
         ExternalDisplaySystemShortcut.createFactory(abstractFloatingViewHelper)
+    private val context: Context = spy(InstrumentationRegistry.getInstrumentation().targetContext)
 
     private lateinit var mockitoSession: StaticMockitoSession
 
@@ -83,6 +91,7 @@
                 .startMocking()
         whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
         whenever(overlayFactory.createOverlay(any())).thenReturn(mock<TaskOverlay<*>>())
+        whenever(launcher.asContext()).thenReturn(context)
     }
 
     @After
@@ -102,6 +111,89 @@
     }
 
     @Test
+    @EnableFlags(
+        Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT,
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+    )
+    @DisableFlags(Flags.FLAG_ENABLE_MODALS_FULLSCREEN_WITH_PERMISSION)
+    fun createExternalDisplayTaskShortcut_transparentTask() {
+        val baseComponent = ComponentName("", /* class */ "")
+        val taskKey =
+            TaskKey(
+                /* id */ 1,
+                /* windowingMode */ 0,
+                Intent(),
+                baseComponent,
+                /* userId */ 0,
+                /* lastActiveTime */ 2000,
+                DEFAULT_DISPLAY,
+                baseComponent,
+                /* numActivities */ 1,
+                /* isTopActivityNoDisplay */ false,
+                /* isActivityStackTransparent */ true,
+            )
+        val taskContainer = createTaskContainer(Task(taskKey))
+        val shortcuts = factory.getShortcuts(launcher, taskContainer)
+        assertThat(shortcuts).isNull()
+    }
+
+    @Test
+    @EnableFlags(
+        Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT,
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+    )
+    fun createExternalDisplayTaskShortcut_systemUiTask() {
+        val sysUiPackageName: String = context.resources.getString(R.string.config_systemUi)
+        val baseComponent = ComponentName(sysUiPackageName, /* class */ "")
+        val taskKey =
+            TaskKey(
+                /* id */ 1,
+                /* windowingMode */ 0,
+                Intent(),
+                baseComponent,
+                /* userId */ 0,
+                /* lastActiveTime */ 2000,
+                DEFAULT_DISPLAY,
+                baseComponent,
+                /* numActivities */ 1,
+                /* isTopActivityNoDisplay */ false,
+                /* isActivityStackTransparent */ false,
+            )
+        val taskContainer = createTaskContainer(Task(taskKey))
+        val shortcuts = factory.getShortcuts(launcher, taskContainer)
+        assertThat(shortcuts).isNull()
+    }
+
+    @Test
+    @EnableFlags(
+        Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT,
+        Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY,
+    )
+    fun createExternalDisplayTaskShortcut_defaultHomeTask() {
+        val packageManager: PackageManager = mock()
+        val homeActivities = ComponentName("defaultHomePackage", /* class */ "")
+        whenever(context.packageManager).thenReturn(packageManager)
+        whenever(packageManager.getHomeActivities(any())).thenReturn(homeActivities)
+        val taskKey =
+            TaskKey(
+                /* id */ 1,
+                /* windowingMode */ 0,
+                Intent(),
+                homeActivities,
+                /* userId */ 0,
+                /* lastActiveTime */ 2000,
+                DEFAULT_DISPLAY,
+                homeActivities,
+                /* numActivities */ 1,
+                /* isTopActivityNoDisplay */ false,
+                /* isActivityStackTransparent */ false,
+            )
+        val taskContainer = createTaskContainer(Task(taskKey).apply { isDockable = true })
+        val shortcuts = factory.getShortcuts(launcher, taskContainer)
+        assertThat(shortcuts).isNull()
+    }
+
+    @Test
     @EnableFlags(Flags.FLAG_MOVE_TO_EXTERNAL_DISPLAY_SHORTCUT)
     fun externalDisplaySystemShortcutClicked() {
         val task = createTask()
@@ -134,12 +226,28 @@
         verify(statsLogger).log(LauncherEvent.LAUNCHER_SYSTEM_SHORTCUT_EXTERNAL_DISPLAY_TAP)
     }
 
-    private fun createTask() = Task(TaskKey(1, 0, Intent(), ComponentName("", ""), 0, 2000))
+    private fun createTask() =
+        Task(
+            TaskKey(
+                /* id */ 1,
+                /* windowingMode */ 0,
+                Intent(),
+                ComponentName("", ""),
+                /* userId */ 0,
+                /* lastActiveTime */ 2000,
+                DEFAULT_DISPLAY,
+                ComponentName("", ""),
+                /* numActivities */ 1,
+                /* isTopActivityNoDisplay */ false,
+                /* isActivityStackTransparent */ false,
+            )
+        )
 
     private fun createTaskContainer(task: Task) =
         TaskContainer(
             taskView,
             task,
+            mock<TaskContentView>(),
             if (enableRefactorTaskThumbnail()) mock<TaskThumbnailView>()
             else mock<TaskThumbnailViewDeprecated>(),
             mock<TaskViewIcon>(),
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 5bb2fad..a4c9ef2 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -333,13 +333,11 @@
 
     private class OverviewUpdateHandler implements OverviewChangeListener {
 
-        final RecentsAnimationDeviceState mRads;
         final OverviewComponentObserver mObserver;
         final CountDownLatch mChangeCounter;
 
         OverviewUpdateHandler() {
             Context ctx = getInstrumentation().getTargetContext();
-            mRads = new RecentsAnimationDeviceState(ctx);
             mObserver = OverviewComponentObserver.INSTANCE.get(ctx);
             mChangeCounter = new CountDownLatch(1);
             if (mObserver.getHomeIntent().getComponent()
@@ -358,7 +356,6 @@
 
         void destroy() {
             mObserver.removeOverviewChangeListener(this);
-            mRads.destroy();
         }
     }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
index 5dc6932..ef6f55e 100644
--- a/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/InputConsumerUtilsTest.java
@@ -16,8 +16,6 @@
 
 package com.android.quickstep;
 
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
 import static com.android.quickstep.InputConsumerUtils.newBaseConsumer;
 import static com.android.quickstep.InputConsumerUtils.newConsumer;
 
@@ -32,6 +30,7 @@
 import android.annotation.Nullable;
 import android.os.Looper;
 import android.view.Choreographer;
+import android.view.Display;
 import android.view.MotionEvent;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -40,6 +39,9 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppModule;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.taskbar.TaskbarManager;
 import com.android.launcher3.taskbar.bubbles.BubbleBarController;
@@ -54,9 +56,8 @@
 import com.android.launcher3.taskbar.bubbles.BubbleStashedHandleViewController;
 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
 import com.android.launcher3.util.LockedUserState;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.SandboxApplication;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.quickstep.fallback.window.RecentsWindowManager;
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
 import com.android.quickstep.inputconsumers.BubbleBarInputConsumer;
 import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
@@ -76,6 +77,9 @@
 import com.android.systemui.shared.system.InputChannelCompat;
 import com.android.systemui.shared.system.InputMonitorCompat;
 
+import dagger.BindsInstance;
+import dagger.Component;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -94,12 +98,12 @@
 @RunWith(AndroidJUnit4.class)
 public class InputConsumerUtilsTest {
 
-    @NonNull private final MainThreadInitializedObject.SandboxContext mContext =
-            new MainThreadInitializedObject.SandboxContext(getApplicationContext());
-    @NonNull private final TaskAnimationManager mTaskAnimationManager = new TaskAnimationManager(
-            mContext, mock(RecentsWindowManager.class));
-    @NonNull private final InputMonitorCompat mInputMonitorCompat = new InputMonitorCompat("", 0);
+    @Rule public final SandboxApplication mContext = new SandboxApplication();
 
+    @NonNull private final InputMonitorCompat mInputMonitorCompat =
+            new InputMonitorCompat("", Display.DEFAULT_DISPLAY);
+
+    private TaskAnimationManager mTaskAnimationManager;
     private InputChannelCompat.InputEventReceiver mInputEventReceiver;
     @Nullable private ResetGestureInputConsumer mResetGestureInputConsumer;
     @NonNull private Function<GestureState, AnimatedFloat> mSwipeUpProxyProvider = (state) -> null;
@@ -121,8 +125,18 @@
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
     @Before
-    public void setupMainThreadInitializedObjects() {
-        mContext.putObject(LockedUserState.INSTANCE, mLockedUserState);
+    public void setupTaskAnimationManager() {
+        mTaskAnimationManager = new TaskAnimationManager(
+                mContext, mDeviceState);
+    }
+
+    @Before
+    public void setupDaggerGraphOverrides() {
+        mContext.initDaggerComponent(DaggerInputConsumerUtilsTest_TestComponent
+                .builder()
+                .bindLockedState(mLockedUserState)
+                .bindRotationHelper(mock(RotationTouchHelper.class))
+                .bindRecentsState(mDeviceState));
     }
 
     @Before
@@ -188,7 +202,6 @@
         when(mDeviceState.canStartSystemGesture()).thenReturn(true);
         when(mDeviceState.isFullyGesturalNavMode()).thenReturn(true);
         when(mDeviceState.getNavBarPosition()).thenReturn(mock(NavBarPosition.class));
-        when(mDeviceState.getRotationTouchHelper()).thenReturn(mock(RotationTouchHelper.class));
     }
 
     @After
@@ -473,7 +486,6 @@
         MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
         InputConsumer inputConsumer = newConsumer(
                 mContext,
-                mContext,
                 mResetGestureInputConsumer,
                 mOverviewComponentObserver,
                 mDeviceState,
@@ -590,4 +602,18 @@
 
         return bubbleControllers;
     }
+
+    @LauncherAppSingleton
+    @Component(modules = {LauncherAppModule.class})
+    interface TestComponent extends LauncherAppComponent {
+        @Component.Builder
+        interface Builder extends LauncherAppComponent.Builder {
+            @BindsInstance Builder bindLockedState(LockedUserState state);
+            @BindsInstance Builder bindRotationHelper(RotationTouchHelper helper);
+            @BindsInstance Builder bindRecentsState(RecentsAnimationDeviceState state);
+
+            @Override
+            TestComponent build();
+        }
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 77f4c05..c713c3d 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -16,7 +16,7 @@
 
 package com.android.quickstep;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.quickstep.NavigationModeSwitchRule.Mode.ALL;
 import static com.android.quickstep.NavigationModeSwitchRule.Mode.THREE_BUTTON;
@@ -26,6 +26,7 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.Process;
 import android.util.Log;
 
 import androidx.test.uiautomator.UiDevice;
@@ -153,7 +154,9 @@
 
         Log.d(TAG, "setActiveOverlay: " + overlayPackage + "...");
         UiDevice.getInstance(getInstrumentation()).executeShellCommand(
-                "cmd overlay enable-exclusive --category " + overlayPackage);
+                String.format("cmd overlay enable-exclusive --user %d --category %s",
+                        Process.myUserHandle().getIdentifier(),
+                        overlayPackage));
 
         if (currentSysUiNavigationMode() != expectedMode) {
             final CountDownLatch latch = new CountDownLatch(1);
diff --git a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
index ff0ad53..154d86d 100644
--- a/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
+++ b/quickstep/tests/src/com/android/quickstep/OrientationTouchTransformerTest.java
@@ -43,6 +43,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.launcher3.testing.shared.ResourceUtils;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.RotationUtils;
 import com.android.launcher3.util.WindowBounds;
@@ -299,9 +300,10 @@
     @Test
     public void testSimpleOrientationTouchTransformer() {
         final DisplayController displayController = mock(DisplayController.class);
-        doReturn(mInfo).when(displayController).getInfo();
+        doReturn(mInfo).when(displayController).getInfoForDisplay(anyInt());
         final SimpleOrientationTouchTransformer transformer =
-                new SimpleOrientationTouchTransformer(getApplicationContext(), displayController);
+                new SimpleOrientationTouchTransformer(getApplicationContext(), displayController,
+                        mock(DaggerSingletonTracker.class));
         final MotionEvent move1 = generateMotionEvent(MotionEvent.ACTION_MOVE, 100, 10);
         transformer.transform(move1, Surface.ROTATION_90);
         // The position is transformed to 90 degree.
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
deleted file mode 100644
index b3c486c..0000000
--- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.quickstep;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.TestCase.assertNull;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.app.ActivityManager.RecentTaskInfo;
-import android.app.KeyguardManager;
-import android.app.TaskInfo;
-import android.content.Context;
-import android.content.res.Resources;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.R;
-import com.android.launcher3.util.LooperExecutor;
-import com.android.quickstep.util.GroupTask;
-import com.android.quickstep.views.TaskViewType;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.wm.shell.shared.GroupedTaskInfo;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-@SmallTest
-public class RecentTasksListTest {
-
-    @Mock
-    private Context mContext;
-    @Mock
-    private Resources mResources;
-    @Mock
-    private SystemUiProxy mSystemUiProxy;
-    @Mock
-    private TopTaskTracker mTopTaskTracker;
-
-    // Class under test
-    private RecentTasksList mRecentTasksList;
-
-    @Before
-    public void setup() {
-        MockitoAnnotations.initMocks(this);
-        LooperExecutor mockMainThreadExecutor = mock(LooperExecutor.class);
-        KeyguardManager mockKeyguardManager = mock(KeyguardManager.class);
-
-        // Set desktop mode supported
-        when(mContext.getResources()).thenReturn(mResources);
-        when(mResources.getBoolean(R.bool.config_isDesktopModeSupported)).thenReturn(true);
-
-        mRecentTasksList = new RecentTasksList(mContext, mockMainThreadExecutor,
-                mockKeyguardManager, mSystemUiProxy, mTopTaskTracker);
-    }
-
-    @Test
-    public void onRecentTasksChanged_doesNotFetchTasks() throws Exception {
-        mRecentTasksList.onRecentTasksChanged();
-        verify(mSystemUiProxy, times(0))
-                .getRecentTasks(anyInt(), anyInt());
-    }
-
-    @Test
-    public void loadTasksInBackground_onlyKeys_noValidTaskDescription() throws Exception  {
-        GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks(
-                new RecentTaskInfo(), new RecentTaskInfo(), null);
-        when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
-                .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
-
-        List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1,
-                true);
-
-        assertEquals(1, taskList.size());
-        assertNull(taskList.get(0).task1.taskDescription.getLabel());
-        assertNull(taskList.get(0).task2.taskDescription.getLabel());
-    }
-
-    @Test
-    public void loadTasksInBackground_GetRecentTasksException() throws Exception  {
-        when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
-                .thenThrow(new SystemUiProxy.GetRecentTasksException("task load failed"));
-
-        RecentTasksList.TaskLoadResult taskList = mRecentTasksList.loadTasksInBackground(
-                Integer.MAX_VALUE, -1, false);
-
-        assertThat(taskList.mRequestId).isEqualTo(-1);
-        assertThat(taskList).isEmpty();
-    }
-
-    @Test
-    public void loadTasksInBackground_moreThanKeys_hasValidTaskDescription() throws Exception  {
-        String taskDescription = "Wheeee!";
-        RecentTaskInfo task1 = new RecentTaskInfo();
-        task1.taskDescription = new ActivityManager.TaskDescription(taskDescription);
-        RecentTaskInfo task2 = new RecentTaskInfo();
-        task2.taskDescription = new ActivityManager.TaskDescription();
-        GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forSplitTasks(task1, task2, null);
-        when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
-                .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
-
-        List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(Integer.MAX_VALUE, -1,
-                false);
-
-        assertEquals(1, taskList.size());
-        assertEquals(taskDescription, taskList.get(0).task1.taskDescription.getLabel());
-        assertNull(taskList.get(0).task2.taskDescription.getLabel());
-    }
-
-    @Test
-    public void loadTasksInBackground_freeformTask_createsDesktopTask() throws Exception  {
-        List<TaskInfo> tasks = Arrays.asList(
-                createRecentTaskInfo(1 /* taskId */),
-                createRecentTaskInfo(4 /* taskId */),
-                createRecentTaskInfo(5 /* taskId */));
-        GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forFreeformTasks(
-                tasks, Collections.emptySet() /* minimizedTaskIds */);
-        when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
-                .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
-
-        List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(
-                Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */);
-
-        assertEquals(1, taskList.size());
-        assertEquals(TaskViewType.DESKTOP, taskList.get(0).taskViewType);
-        List<Task> actualFreeformTasks = taskList.get(0).getTasks();
-        assertEquals(3, actualFreeformTasks.size());
-        assertEquals(1, actualFreeformTasks.get(0).key.id);
-        assertEquals(4, actualFreeformTasks.get(1).key.id);
-        assertEquals(5, actualFreeformTasks.get(2).key.id);
-    }
-
-    @Test
-    public void loadTasksInBackground_freeformTask_onlyMinimizedTasks_doesNotCreateDesktopTask()
-            throws Exception {
-        List<TaskInfo> tasks = Arrays.asList(
-                createRecentTaskInfo(1 /* taskId */),
-                createRecentTaskInfo(4 /* taskId */),
-                createRecentTaskInfo(5 /* taskId */));
-        Set<Integer> minimizedTaskIds =
-                Arrays.stream(new Integer[]{1, 4, 5}).collect(Collectors.toSet());
-        GroupedTaskInfo recentTaskInfos = GroupedTaskInfo.forFreeformTasks(tasks, minimizedTaskIds);
-        when(mSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
-                .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
-
-        List<GroupTask> taskList = mRecentTasksList.loadTasksInBackground(
-                Integer.MAX_VALUE /* numTasks */, -1 /* requestId */, false /* loadKeysOnly */);
-
-        assertEquals(0, taskList.size());
-    }
-
-    private TaskInfo createRecentTaskInfo(int taskId) {
-        RecentTaskInfo recentTaskInfo = new RecentTaskInfo();
-        recentTaskInfo.taskId = taskId;
-        return recentTaskInfo;
-    }
-}
diff --git a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt b/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
deleted file mode 100644
index 80fbce7..0000000
--- a/quickstep/tests/src/com/android/quickstep/RecentsAnimationDeviceStateTest.kt
+++ /dev/null
@@ -1,205 +0,0 @@
-package com.android.quickstep
-
-import android.content.Context
-import android.testing.AndroidTestingRunner
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.filters.SmallTest
-import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
-import com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE
-import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
-import com.android.launcher3.util.DisplayController.Info
-import com.android.launcher3.util.NavigationMode
-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
-import org.mockito.Mock
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.verifyZeroInteractions
-import org.mockito.kotlin.whenever
-
-/** Unit test for [RecentsAnimationDeviceState]. */
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class RecentsAnimationDeviceStateTest {
-
-    @Mock private lateinit var exclusionManager: GestureExclusionManager
-    @Mock private lateinit var info: Info
-
-    private val context = ApplicationProvider.getApplicationContext() as Context
-    private lateinit var underTest: RecentsAnimationDeviceState
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        underTest = RecentsAnimationDeviceState(context, exclusionManager)
-    }
-
-    @Test
-    fun registerExclusionListener_success() {
-        underTest.registerExclusionListener()
-
-        verify(exclusionManager).addListener(underTest)
-    }
-
-    @Test
-    fun registerExclusionListener_again_fail() {
-        underTest.registerExclusionListener()
-        reset(exclusionManager)
-
-        underTest.registerExclusionListener()
-
-        verifyZeroInteractions(exclusionManager)
-    }
-
-    @Test
-    fun unregisterExclusionListener_success() {
-        underTest.registerExclusionListener()
-        reset(exclusionManager)
-
-        underTest.unregisterExclusionListener()
-
-        verify(exclusionManager).removeListener(underTest)
-    }
-
-    @Test
-    fun unregisterExclusionListener_again_fail() {
-        underTest.registerExclusionListener()
-        underTest.unregisterExclusionListener()
-        reset(exclusionManager)
-
-        underTest.unregisterExclusionListener()
-
-        verifyZeroInteractions(exclusionManager)
-    }
-
-    @Test
-    fun onDisplayInfoChanged_noButton_registerExclusionListener() {
-        doReturn(NavigationMode.NO_BUTTON).whenever(info).getNavigationMode()
-
-        underTest.onDisplayInfoChanged(context, info, CHANGE_ROTATION or CHANGE_NAVIGATION_MODE)
-
-        verify(exclusionManager).addListener(underTest)
-    }
-
-    @Test
-    fun onDisplayInfoChanged_twoButton_unregisterExclusionListener() {
-        underTest.registerExclusionListener()
-        whenever(info.getNavigationMode()).thenReturn(NavigationMode.TWO_BUTTONS)
-        reset(exclusionManager)
-
-        underTest.onDisplayInfoChanged(context, info, CHANGE_ROTATION or CHANGE_NAVIGATION_MODE)
-
-        verify(exclusionManager).removeListener(underTest)
-    }
-
-    @Test
-    fun onDisplayInfoChanged_changeDensity_noOp() {
-        underTest.registerExclusionListener()
-        whenever(info.getNavigationMode()).thenReturn(NavigationMode.NO_BUTTON)
-        reset(exclusionManager)
-
-        underTest.onDisplayInfoChanged(context, info, CHANGE_DENSITY)
-
-        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/RecentsDeviceProfileRepositoryImplTest.kt b/quickstep/tests/src/com/android/quickstep/RecentsDeviceProfileRepositoryImplTest.kt
new file mode 100644
index 0000000..418d66c
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/RecentsDeviceProfileRepositoryImplTest.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.launcher3.FakeInvariantDeviceProfileTest
+import com.android.quickstep.recents.data.RecentsDeviceProfile
+import com.android.quickstep.recents.data.RecentsDeviceProfileRepositoryImpl
+import com.android.quickstep.views.RecentsViewContainer
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
+
+class RecentsDeviceProfileRepositoryImplTest : FakeInvariantDeviceProfileTest() {
+    private val recentsViewContainer: RecentsViewContainer = mock()
+
+    private lateinit var mockitoSession: StaticMockitoSession
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+        mockitoSession =
+            mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .mockStatic(DesktopModeStatus::class.java)
+                .startMocking()
+        whenever(recentsViewContainer.asContext()).thenReturn(context)
+    }
+
+    @After
+    fun tearDown() {
+        mockitoSession.finishMocking()
+    }
+
+    @Test
+    fun deviceProfileMappedCorrectlyForPhone() {
+        val deviceProfileRepo = RecentsDeviceProfileRepositoryImpl(recentsViewContainer)
+        initializeVarsForPhone()
+        val phoneDeviceProfile = newDP()
+        whenever(recentsViewContainer.deviceProfile).thenReturn(phoneDeviceProfile)
+
+        whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(false)
+        assertThat(deviceProfileRepo.getRecentsDeviceProfile())
+            .isEqualTo(RecentsDeviceProfile(isLargeScreen = false, canEnterDesktopMode = false))
+    }
+
+    @Test
+    fun deviceProfileMappedCorrectlyForTablet() {
+        val deviceProfileRepo = RecentsDeviceProfileRepositoryImpl(recentsViewContainer)
+        initializeVarsForTablet()
+        val tabletDeviceProfile = newDP()
+        whenever(recentsViewContainer.deviceProfile).thenReturn(tabletDeviceProfile)
+
+        whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
+        assertThat(deviceProfileRepo.getRecentsDeviceProfile())
+            .isEqualTo(RecentsDeviceProfile(isLargeScreen = true, canEnterDesktopMode = true))
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
index b15b78e..88be752 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplPrivateSpaceTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 
+import android.os.Process;
 import android.util.Log;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -74,8 +75,9 @@
     }
 
     private void createAndStartPrivateProfileUser() {
-        String createUserOutput = executeShellCommand("pm create-user --profileOf 0 --user-type "
-                + "android.os.usertype.profile.PRIVATE " + PRIVATE_PROFILE_NAME);
+        int myUserId = Process.myUserHandle().getIdentifier();
+        String createUserOutput = executeShellCommand("pm create-user --profileOf " + myUserId
+                + " --user-type android.os.usertype.profile.PRIVATE " + PRIVATE_PROFILE_NAME);
         updatePrivateProfileSetupSuccessful("pm create-user", createUserOutput);
         String[] tokens = createUserOutput.split("\\s+");
         mProfileUserId = Integer.parseInt(tokens[tokens.length - 1]);
@@ -163,7 +165,6 @@
     }
 
     @Test
-    @ScreenRecordRule.ScreenRecord // b/355466672
     public void testPrivateSpaceLockingBehaviour() throws IOException {
         assumeFalse(mLauncher.isTablet()); // b/367258373
         // Scroll to the bottom of All Apps
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsLockedTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsLockedTaskbar.java
new file mode 100644
index 0000000..b2617dd
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsLockedTaskbar.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+
+import static com.android.launcher3.util.TestConstants.AppNames.TEST_APP_NAME;
+import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT;
+import static com.android.wm.shell.shared.desktopmode.DesktopModeStatus.ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.WindowManagerGlobal;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.tapl.HomeAllApps;
+import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
+import com.android.launcher3.util.rule.SetPropRule;
+import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
+import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
+import com.android.window.flags.Flags;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExternalResource;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TaplTestsLockedTaskbar extends AbstractTaplTestsTaskbar {
+    private static final String TAG = "TaplTestsLockedTaskbar";
+
+    @Rule
+    public SetPropRule mSetPropRule =
+            new SetPropRule(ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAY_SYS_PROP, "true");
+
+    // Default-to-desktop feature requires the display to be freeform mode.
+    @Rule
+    public ExternalResource mFreeformDisplayRule = new ExternalResource() {
+        private int mOriginalWindowingMode = WINDOWING_MODE_UNDEFINED;
+
+        @Override
+        protected void before() {
+            mOriginalWindowingMode = setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
+        }
+
+        @Override
+        protected void after() {
+            if (mOriginalWindowingMode != WINDOWING_MODE_UNDEFINED) {
+                setDisplayWindowingMode(mOriginalWindowingMode);
+            }
+        }
+    };
+
+    @Override
+    public void setUp() throws Exception {
+        Assume.assumeTrue(mLauncher.isTablet());
+        Assume.assumeTrue(Flags.enterDesktopByDefaultOnFreeformDisplays());
+        Assume.assumeTrue(DesktopModeStatus.canEnterDesktopMode(getTargetContext()));
+        super.setUp();
+    }
+
+    @Override
+    protected boolean startCalendarAppDuringSetup() {
+        return false;
+    }
+
+    @Override
+    protected boolean expectTaskbarIconsMatchHotseat() {
+        return false;
+    }
+
+    @Test
+    @PortraitLandscape
+    @NavigationModeSwitch
+    @TaskbarModeSwitch(mode = PERSISTENT)
+    public void testTaskbarVisibility() {
+        // The taskbar should be visible on home.
+        mDevice.pressHome();
+        waitForResumed("Launcher internal state is still Background");
+        mLauncher.getLaunchedAppState().assertTaskbarVisible();
+
+        // The taskbar should be visible when a freeform task is active.
+        startAppFast(CALCULATOR_APP_PACKAGE);
+        mLauncher.getLaunchedAppState().assertTaskbarVisible();
+    }
+
+    @Test
+    @PortraitLandscape
+    @NavigationModeSwitch
+    @TaskbarModeSwitch(mode = PERSISTENT)
+    public void testDragFromAllAppsToWorspace() {
+        mDevice.pressHome();
+        waitForResumed("Launcher internal state is still Background");
+
+        final HomeAllApps allApps = getTaskbar().openAllAppsOnHome();
+        allApps.freeze();
+        try {
+            allApps.getAppIcon(TEST_APP_NAME).dragToWorkspace(false, false);
+            assertThat(mLauncher.getWorkspace().getWorkspaceAppIcon(TEST_APP_NAME)).isNotNull();
+        } finally {
+            allApps.unfreeze();
+        }
+    }
+
+    private int setDisplayWindowingMode(int windowingMode) {
+        try {
+            int originalWindowingMode =
+                    WindowManagerGlobal.getWindowManagerService().getWindowingMode(DEFAULT_DISPLAY);
+            WindowManagerGlobal.getWindowManagerService().setWindowingMode(
+                    DEFAULT_DISPLAY, windowingMode);
+            return originalWindowingMode;
+        } catch (RemoteException e) {
+            Log.e(TAG, "error setting windowing mode", e);
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt b/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt
index f58c84e..75947ab 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsOverviewDesktop.kt
@@ -57,16 +57,25 @@
             .switchToOverview()
             .apply { flingForward() }
             .also { moveTaskToDesktop(TEST_ACTIVITY_1) }
-
         TEST_ACTIVITIES.forEach { assertTestAppLaunched(it) }
 
-        // Launch static DesktopTaskView
-        val desktop =
+        // Launch static DesktopTaskView without live tile in Overview
+        val desktopTask =
             mLauncher.goHome().switchToOverview().getTestActivityTask(TEST_ACTIVITIES).open()
         TEST_ACTIVITIES.forEach { assertTestAppLaunched(it) }
 
         // Launch live-tile DesktopTaskView
-        desktop.switchToOverview().getTestActivityTask(TEST_ACTIVITIES).open()
+        desktopTask.switchToOverview().getTestActivityTask(TEST_ACTIVITIES).open()
+        TEST_ACTIVITIES.forEach { assertTestAppLaunched(it) }
+
+        // Launch static DesktopTaskView with live tile in Overview
+        mLauncher.goHome()
+        startTestActivity(TEST_ACTIVITY_EXTRA)
+        mLauncher.launchedAppState
+            .switchToOverview()
+            .apply { flingBackward() }
+            .getTestActivityTask(TEST_ACTIVITIES)
+            .open()
         TEST_ACTIVITIES.forEach { assertTestAppLaunched(it) }
     }
 
@@ -113,25 +122,25 @@
         val desktop = moveTaskToDesktop(TEST_ACTIVITY_EXTRA)
         var overview = desktop.switchToOverview()
 
-        // Open focused task and go back to Overview to validate whether it has adjacent tasks in
-        // its both sides (grid task on left and desktop tasks at its right side)
-        val focusedTaskOpened = overview.getTestActivityTask(TEST_ACTIVITY_2).open()
+        // Open first fullscreen task and go back to Overview to validate whether it has adjacent
+        // tasks in its both sides (grid task on left and desktop tasks at its right side)
+        val firstFullscreenTaskOpened = overview.getTestActivityTask(TEST_ACTIVITY_2).open()
 
-        // Fling to desktop task and dismiss the focused task to check repositioning of
+        // Fling to desktop task and dismiss the first fullscreen task to check repositioning of
         // grid tasks.
-        overview = focusedTaskOpened.switchToOverview().apply { flingBackward() }
+        overview = firstFullscreenTaskOpened.switchToOverview().apply { flingBackward() }
         val desktopTask = overview.currentTask
         assertWithMessage("The current task is not a Desktop.").that(desktopTask.isDesktop).isTrue()
 
-        // Get focused task (previously opened task) then dismiss this task
-        val focusedTaskInOverview = overview.getTestActivityTask(TEST_ACTIVITY_2)
-        assertTaskContentDescription(focusedTaskInOverview, TEST_ACTIVITY_2)
-        focusedTaskInOverview.dismiss()
+        // Get first fullscreen task (previously opened task) then dismiss this task
+        val firstFullscreenTaskInOverview = overview.getTestActivityTask(TEST_ACTIVITY_2)
+        assertTaskContentDescription(firstFullscreenTaskInOverview, TEST_ACTIVITY_2)
+        firstFullscreenTaskInOverview.dismiss()
 
-        // Dismiss DesktopTask to validate whether the new focused task will take its position
+        // Dismiss DesktopTask to validate whether the new task will take its position
         desktopTask.dismiss()
 
-        // Dismiss last focused task
+        // Dismiss last fullscreen task
         val lastFocusedTask = overview.currentTask
         assertTaskContentDescription(lastFocusedTask, TEST_ACTIVITY_1)
         lastFocusedTask.dismiss()
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index f1fe2d2..b207d4a 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -16,12 +16,14 @@
 
 package com.android.quickstep;
 
+import static com.android.launcher3.Flags.enableLauncherOverviewInWindow;
 import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
 import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
 import static com.android.quickstep.TaskbarModeSwitchRule.Mode.TRANSIENT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
@@ -30,13 +32,13 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.LargeTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.Until;
 
-import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.tapl.BaseOverview;
 import com.android.launcher3.tapl.LaunchedAppState;
@@ -49,10 +51,11 @@
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
 import com.android.launcher3.util.TestUtil;
 import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
+import com.android.launcher3.util.rule.ScreenRecordRule;
 import com.android.launcher3.util.rule.TestStabilityRule;
 import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
 import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
+import com.android.quickstep.fallback.RecentsState;
 import com.android.quickstep.views.RecentsView;
 
 import org.junit.After;
@@ -61,6 +64,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Comparator;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class TaplTestsQuickstep extends AbstractQuickStepTest {
@@ -70,21 +77,32 @@
     private static final String READ_DEVICE_CONFIG_PERMISSION =
             "android.permission.READ_DEVICE_CONFIG";
 
+    private enum ExpectedState {
+
+        HOME(LauncherState.NORMAL, RecentsState.HOME),
+        OVERVIEW(LauncherState.OVERVIEW, RecentsState.DEFAULT);
+
+        private final LauncherState mLauncherState;
+        private final RecentsState mRecentsState;
+
+        ExpectedState(LauncherState launcherState, RecentsState recentsState) {
+            this.mLauncherState = launcherState;
+            this.mRecentsState = recentsState;
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        executeOnLauncher(launcher -> {
-            RecentsView recentsView = launcher.getOverviewPanel();
-            recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(true);
-        });
+        runOnRecentsView(recentsView ->
+                recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(true));
     }
 
     @After
     public void tearDown() {
-        executeOnLauncherInTearDown(launcher -> {
-            RecentsView recentsView = launcher.getOverviewPanel();
-            recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false);
-        });
+        runOnRecentsView(recentsView ->
+                recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false),
+                /* forTearDown= */ true);
     }
 
     public static void startTestApps() throws Exception {
@@ -93,14 +111,6 @@
         startTestActivity(2);
     }
 
-    private void startTestAppsWithCheck() throws Exception {
-        startTestApps();
-        executeOnLauncher(launcher -> assertTrue(
-                "Launcher activity is the top activity; expecting another activity to be the top "
-                        + "one",
-                isInLaunchedApp(launcher)));
-    }
-
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
@@ -117,28 +127,26 @@
         startTestAppsWithCheck();
         // mLauncher.pressHome() also tests an important case of pressing home while in background.
         Overview overview = mLauncher.goHome().switchToOverview();
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(
-                launcher -> assertTrue("Don't have at least 3 tasks", getTaskCount(launcher) >= 3));
+        assertIsInState(
+                "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
+        runOnRecentsView(recentsView -> assertTrue("Don't have at least 3 tasks",
+                recentsView.getTaskViewCount() >= 3));
 
         // Test flinging forward and backward.
-        executeOnLauncher(launcher -> assertEquals("Current task in Overview is not 0",
-                0, getCurrentOverviewPage(launcher)));
+        runOnRecentsView(recentsView -> assertEquals("Current task in Overview is not 0",
+                0, recentsView.getCurrentPage()));
 
         overview.flingForward();
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        final Integer currentTaskAfterFlingForward = getFromLauncher(
-                launcher -> getCurrentOverviewPage(launcher));
-        executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
+        assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
+        final Integer currentTaskAfterFlingForward =
+                getFromRecentsView(RecentsView::getCurrentPage);
+        runOnRecentsView(recentsView -> assertTrue("Current task in Overview is still 0",
                 currentTaskAfterFlingForward > 0));
 
         overview.flingBackward();
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(launcher -> assertTrue("Flinging back in Overview did nothing",
-                getCurrentOverviewPage(launcher) < currentTaskAfterFlingForward));
+        assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
+        runOnRecentsView(recentsView -> assertTrue("Flinging back in Overview did nothing",
+                recentsView.getCurrentPage() < currentTaskAfterFlingForward));
 
         // Test opening a task.
         OverviewTask task = mLauncher.goHome().switchToOverview().getCurrentTask();
@@ -147,30 +155,25 @@
         assertTrue("Test activity didn't open from Overview", mDevice.wait(Until.hasObject(
                         By.pkg(getAppPackageName()).text("TestActivity2")),
                 TestUtil.DEFAULT_UI_TIMEOUT));
-        executeOnLauncher(launcher -> assertTrue(
-                "Launcher activity is the top activity; expecting another activity to be the top "
-                        + "one",
-                isInLaunchedApp(launcher)));
+        expectLaunchedAppState();
 
         // Test dismissing a task.
         overview = mLauncher.goHome().switchToOverview();
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        final Integer numTasks = getFromLauncher(launcher -> getTaskCount(launcher));
+        assertIsInState("Launcher internal state didn't switch to Overview",
+                ExpectedState.OVERVIEW);
+        final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount);
         task = overview.getCurrentTask();
         assertNotNull("overview.getCurrentTask() returned null (2)", task);
         task.dismiss();
-        executeOnLauncher(
-                launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
-                        numTasks - 1, getTaskCount(launcher)));
+        runOnRecentsView(recentsView -> assertEquals(
+                "Dismissing a task didn't remove 1 task from Overview",
+                numTasks - 1, recentsView.getTaskViewCount()));
 
         // Test dismissing all tasks.
         mLauncher.goHome().switchToOverview().dismissAllTasks();
-        assertTrue("Launcher internal state is not Home",
-                isInState(() -> LauncherState.NORMAL));
-        executeOnLauncher(
-                launcher -> assertEquals("Still have tasks after dismissing all",
-                        0, getTaskCount(launcher)));
+        assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
+        runOnRecentsView(recentsView -> assertEquals("Still have tasks after dismissing all",
+                0, recentsView.getTaskViewCount()));
     }
 
     /**
@@ -192,12 +195,10 @@
     public void testDismissOverviewWithEscKey() throws Exception {
         startTestAppsWithCheck();
         final Overview overview = mLauncher.goHome().switchToOverview();
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
 
         overview.dismissByEscKey();
-        assertTrue("Launcher internal state is not Home",
-                isInState(() -> LauncherState.NORMAL));
+        assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
     }
 
     @Test
@@ -218,11 +219,9 @@
 
         selectModeButtons.dismissByEscKey();
 
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
         overview.dismissByEscKey();
-        assertTrue("Launcher internal state is not Home",
-                isInState(() -> LauncherState.NORMAL));
+        assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
     }
 
     @Test
@@ -230,11 +229,11 @@
         startTestAppsWithCheck();
         startAppFast(CALCULATOR_APP_PACKAGE); // Ensure Calculator is last opened app.
         Workspace home = mLauncher.goHome();
-        assertTrue("Launcher state is not Home", isInState(() -> LauncherState.NORMAL));
+        assertIsInState("Launcher state is not Home", ExpectedState.HOME);
 
         Overview overview = home.openOverviewFromActionPlusTabKeyboardShortcut();
 
-        assertTrue("Launcher state is not Overview", isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher state is not Overview", ExpectedState.OVERVIEW);
         overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused.
     }
 
@@ -243,30 +242,14 @@
         startTestAppsWithCheck();
         startAppFast(CALCULATOR_APP_PACKAGE); // Ensure Calculator is last opened app.
         Workspace home = mLauncher.goHome();
-        assertTrue("Launcher state is not Home", isInState(() -> LauncherState.NORMAL));
+        assertIsInState("Launcher state is not Home", ExpectedState.HOME);
 
         Overview overview = home.openOverviewFromRecentsKeyboardShortcut();
 
-        assertTrue("Launcher state is not Overview", isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher state is not Overview", ExpectedState.OVERVIEW);
         overview.launchFocusedTaskByEnterKey(CALCULATOR_APP_PACKAGE); // Assert app is focused.
     }
 
-    private int getCurrentOverviewPage(Launcher launcher) {
-        return launcher.<RecentsView>getOverviewPanel().getCurrentPage();
-    }
-
-    private int getTaskCount(Launcher launcher) {
-        return launcher.<RecentsView>getOverviewPanel().getTaskViewCount();
-    }
-
-    private int getTopRowTaskCountForTablet(Launcher launcher) {
-        return launcher.<RecentsView>getOverviewPanel().getTopRowTaskCountForTablet();
-    }
-
-    private int getBottomRowTaskCountForTablet(Launcher launcher) {
-        return launcher.<RecentsView>getOverviewPanel().getBottomRowTaskCountForTablet();
-    }
-
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
@@ -274,8 +257,8 @@
         startTestAppsWithCheck();
         assertNotNull("Workspace.switchToOverview() returned null",
                 mLauncher.goHome().switchToOverview());
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState(
+                "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
     }
 
     @Test
@@ -300,27 +283,13 @@
 
         assertNotNull("Background.switchToOverview() returned null",
                 launchedAppState.switchToOverview());
-        assertTrue("Launcher internal state didn't switch to Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-    }
-
-    private void quickSwitchToPreviousAppAndAssert(boolean toRight) {
-        final LaunchedAppState launchedAppState = getAndAssertLaunchedApp();
-        if (toRight) {
-            launchedAppState.quickSwitchToPreviousApp();
-        } else {
-            launchedAppState.quickSwitchToPreviousAppSwipeLeft();
-        }
-
-        // While enable shell transition, Launcher can be resumed due to transient launch.
-        waitForLauncherCondition("Launcher shouldn't stay in resume forever",
-                this::isInLaunchedApp, 3000 /* timeout */);
+        assertIsInState(
+                "Launcher internal state didn't switch to Overview", ExpectedState.OVERVIEW);
     }
 
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
-    @ScreenRecord // b/313464374
     @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/325659406
     public void testQuickSwitchFromApp() throws Exception {
         startTestActivity(2);
@@ -388,11 +357,6 @@
         }
     }
 
-    private boolean isHardwareKeyboard() {
-        return Configuration.KEYBOARD_QWERTY
-                == mTargetContext.getResources().getConfiguration().keyboard;
-    }
-
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
@@ -413,11 +377,11 @@
         // Debug if we need to goHome to prevent wrong previous state b/315525621
         mLauncher.goHome();
         mLauncher.getWorkspace().switchToAllApps().pressBackToWorkspace();
-        waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+        waitForState("Launcher internal state didn't switch to Home", ExpectedState.HOME);
 
         startAppFast(CALCULATOR_APP_PACKAGE);
         mLauncher.getLaunchedAppState().pressBackToWorkspace();
-        waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
+        waitForState("Launcher internal state didn't switch to Home", ExpectedState.HOME);
     }
 
     @Test
@@ -432,16 +396,14 @@
         }
 
         Overview overview = mLauncher.goHome().switchToOverview();
-        executeOnLauncher(
-                launcher -> assertTrue("Don't have at least 13 tasks",
-                        getTaskCount(launcher) >= 13));
+        runOnRecentsView(recentsView -> assertTrue("Don't have at least 13 tasks",
+                recentsView.getTaskViewCount() >= 13));
 
         // Test scroll the first task off screen
         overview.scrollCurrentTaskOffScreen();
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
-                getCurrentOverviewPage(launcher) > 0));
+        assertIsInState("Launcher internal state is not Overview", ExpectedState.OVERVIEW);
+        runOnRecentsView(recentsView -> assertTrue("Current task in Overview is still 0",
+                recentsView.getCurrentPage() > 0));
 
         // Test opening the task.
         overview.getCurrentTask().open();
@@ -454,41 +416,38 @@
         // Scroll the task offscreen as it is now first
         overview = mLauncher.goHome().switchToOverview();
         overview.scrollCurrentTaskOffScreen();
-        assertTrue("Launcher internal state is not Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
-                getCurrentOverviewPage(launcher) > 0));
+        assertIsInState(
+                "Launcher internal state is not Overview", ExpectedState.OVERVIEW);
+        runOnRecentsView(recentsView -> assertTrue("Current task in Overview is still 0",
+                recentsView.getCurrentPage() > 0));
 
         // Test dismissing the later task.
-        final Integer numTasks = getFromLauncher(this::getTaskCount);
+        final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount);
         overview.getCurrentTask().dismiss();
-        executeOnLauncher(
-                launcher -> assertEquals("Dismissing a task didn't remove 1 task from Overview",
-                        numTasks - 1, getTaskCount(launcher)));
-        executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after dismissal",
-                (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
-                        launcher)) <= 1)));
+        runOnRecentsView(recentsView -> assertEquals(
+                "Dismissing a task didn't remove 1 task from Overview",
+                numTasks - 1, recentsView.getTaskViewCount()));
+        runOnRecentsView(recentsView -> assertTrue("Grid did not rebalance after dismissal",
+                (Math.abs(recentsView.getTopRowTaskCountForTablet()
+                        - recentsView.getBottomRowTaskCountForTablet()) <= 1)));
 
-        // TODO(b/308841019): Re-enable after fixing Overview jank when dismiss
-//        // Test dismissing more tasks.
-//        assertTrue("Launcher internal state didn't remain in Overview",
-//                isInState(() -> LauncherState.OVERVIEW));
-//        overview.getCurrentTask().dismiss();
-//        assertTrue("Launcher internal state didn't remain in Overview",
-//                isInState(() -> LauncherState.OVERVIEW));
-//        overview.getCurrentTask().dismiss();
-//        executeOnLauncher(launcher -> assertTrue("Grid did not rebalance after multiple
-//        dismissals",
-//                (Math.abs(getTopRowTaskCountForTablet(launcher) - getBottomRowTaskCountForTablet(
-//                        launcher)) <= 1)));
+        // Test dismissing more tasks.
+        assertIsInState(
+                "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW);
+        overview.getCurrentTask().dismiss();
+        assertIsInState(
+                "Launcher internal state didn't remain in Overview", ExpectedState.OVERVIEW);
+        overview.getCurrentTask().dismiss();
+        runOnRecentsView(recentsView -> assertTrue(
+                "Grid did not rebalance after multiple dismissals",
+                (Math.abs(recentsView.getTopRowTaskCountForTablet()
+                        - recentsView.getBottomRowTaskCountForTablet()) <= 1)));
 
         // Test dismissing all tasks.
         mLauncher.goHome().switchToOverview().dismissAllTasks();
-        assertTrue("Launcher internal state is not Home",
-                isInState(() -> LauncherState.NORMAL));
-        executeOnLauncher(
-                launcher -> assertEquals("Still have tasks after dismissing all",
-                        0, getTaskCount(launcher)));
+        assertIsInState("Launcher internal state is not Home", ExpectedState.HOME);
+        runOnRecentsView(recentsView -> assertEquals("Still have tasks after dismissing all",
+                0, recentsView.getTaskViewCount()));
     }
 
     @Test
@@ -497,22 +456,18 @@
         startTestAppsWithCheck();
 
         Overview overview = mLauncher.goHome().switchToOverview();
-        assertTrue("Launcher internal state should be Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(
-                launcher -> assertTrue("Should have at least 3 tasks",
-                        getTaskCount(launcher) >= 3));
+        assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
+        runOnRecentsView(recentsView -> assertTrue("Should have at least 3 tasks",
+                recentsView.getTaskViewCount() >= 3));
 
         // It should not dismiss overview when tapping between tasks
         overview.touchBetweenTasks();
         overview = mLauncher.getOverview();
-        assertTrue("Launcher internal state should be Overview",
-                isInState(() -> LauncherState.OVERVIEW));
+        assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
 
         // Dismiss when tapping to the right of the focused task
         overview.touchOutsideFirstTask();
-        assertTrue("Launcher internal state should be Home",
-                isInState(() -> LauncherState.NORMAL));
+        assertIsInState("Launcher internal state should be Home", ExpectedState.HOME);
     }
 
     @Test
@@ -524,34 +479,28 @@
         startTestAppsWithCheck();
 
         Overview overview = mLauncher.goHome().switchToOverview();
-        assertTrue("Launcher internal state should be Overview",
-                isInState(() -> LauncherState.OVERVIEW));
-        executeOnLauncher(
-                launcher -> assertTrue("Should have at least 3 tasks",
-                        getTaskCount(launcher) >= 3));
+        assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
+        runOnRecentsView(recentsView -> assertTrue("Should have at least 3 tasks",
+                recentsView.getTaskViewCount() >= 3));
 
         if (mLauncher.isTransientTaskbar()) {
             // On transient taskbar, it should dismiss when tapping outside taskbar bounds.
             overview.touchTaskbarBottomCorner(/* tapRight= */ false);
-            assertTrue("Launcher internal state should be Normal",
-                    isInState(() -> LauncherState.NORMAL));
+            assertIsInState("Launcher internal state should be Normal", ExpectedState.HOME);
 
             overview = mLauncher.getWorkspace().switchToOverview();
 
             // On transient taskbar, it should dismiss when tapping outside taskbar bounds.
             overview.touchTaskbarBottomCorner(/* tapRight= */ true);
-            assertTrue("Launcher internal state should be Normal",
-                    isInState(() -> LauncherState.NORMAL));
+            assertIsInState("Launcher internal state should be Normal", ExpectedState.HOME);
         } else {
             // On persistent taskbar, it should not dismiss when tapping the taskbar
             overview.touchTaskbarBottomCorner(/* tapRight= */ false);
-            assertTrue("Launcher internal state should be Overview",
-                    isInState(() -> LauncherState.OVERVIEW));
+            assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
 
             // On persistent taskbar, it should not dismiss when tapping the taskbar
             overview.touchTaskbarBottomCorner(/* tapRight= */ true);
-            assertTrue("Launcher internal state should be Overview",
-                    isInState(() -> LauncherState.OVERVIEW));
+            assertIsInState("Launcher internal state should be Overview", ExpectedState.OVERVIEW);
         }
     }
 
@@ -577,21 +526,164 @@
     public void testExcludeFromRecents() throws Exception {
         startExcludeFromRecentsTestActivity();
         OverviewTask currentTask = getAndAssertLaunchedApp().switchToOverview().getCurrentTask();
-        // TODO(b/342627272): the expected content description shouldn't be null but for now there
-        // is a bug that causes it to sometimes be for excludeForRecents tasks.
         assertTrue("Can't find ExcludeFromRecentsTestActivity after entering Overview from it",
-                currentTask.containsContentDescription("ExcludeFromRecents")
-                        || currentTask.containsContentDescription(null));
+                currentTask.containsContentDescription("ExcludeFromRecents"));
         // Going home should clear out the excludeFromRecents task.
         BaseOverview overview = mLauncher.goHome().switchToOverview();
         if (overview.hasTasks()) {
             currentTask = overview.getCurrentTask();
             assertFalse("Found ExcludeFromRecentsTestActivity after entering Overview from Home",
-                    currentTask.containsContentDescription(
-                            "ExcludeFromRecents")
-                            || currentTask.containsContentDescription(null));
+                    currentTask.containsContentDescription("ExcludeFromRecents"));
         } else {
             // Presumably the test started with 0 tasks and remains that way after going home.
         }
     }
+
+    @Test
+    @PortraitLandscape
+    @ScreenRecordRule.ScreenRecord // TODO(b/396447643): Remove screen record.
+    public void testDismissCancel() throws Exception {
+        startTestAppsWithCheck();
+        Overview overview = mLauncher.goHome().switchToOverview();
+        assertIsInState("Launcher internal state didn't switch to Overview",
+                ExpectedState.OVERVIEW);
+        final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount);
+        OverviewTask task = overview.getCurrentTask();
+        assertNotNull("overview.getCurrentTask() returned null (2)", task);
+
+        task.dismissCancel();
+
+        runOnRecentsView(recentsView -> assertEquals(
+                "Canceling dismissing a task removed a task from Overview",
+                numTasks == null ? 0 : numTasks, recentsView.getTaskViewCount()));
+    }
+
+    @Test
+    @PortraitLandscape
+    public void testDismissBottomRow() throws Exception {
+        assumeTrue(mLauncher.isTablet());
+        mLauncher.goHome().switchToOverview().dismissAllTasks();
+        startTestAppsWithCheck();
+        Overview overview = mLauncher.goHome().switchToOverview();
+        assertIsInState("Launcher internal state didn't switch to Overview",
+                ExpectedState.OVERVIEW);
+        final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount);
+        OverviewTask bottomTask = overview.getCurrentTasksForTablet().stream().max(
+                Comparator.comparingInt(OverviewTask::getTaskCenterY)).get();
+        assertNotNull("bottomTask null", bottomTask);
+
+        bottomTask.dismiss();
+
+        runOnRecentsView(recentsView -> assertEquals(
+                "Dismissing a bottomTask didn't remove 1 bottomTask from Overview",
+                numTasks - 1, recentsView.getTaskViewCount()));
+    }
+
+    @Test
+    @PortraitLandscape
+    public void testDismissLastGridRow() throws Exception {
+        assumeTrue(mLauncher.isTablet());
+        mLauncher.goHome().switchToOverview().dismissAllTasks();
+        startTestAppsWithCheck();
+        startTestActivity(3);
+        startTestActivity(4);
+        runOnRecentsView(
+                recentsView -> assertNotEquals("Grid overview should have unequal row counts",
+                        recentsView.getTopRowTaskCountForTablet(),
+                        recentsView.getBottomRowTaskCountForTablet()));
+        Overview overview = mLauncher.goHome().switchToOverview();
+        assertIsInState("Launcher internal state didn't switch to Overview",
+                ExpectedState.OVERVIEW);
+        overview.flingForwardUntilClearAllVisible();
+        assertTrue("Clear All not visible.", overview.isClearAllVisible());
+        final Integer numTasks = getFromRecentsView(RecentsView::getTaskViewCount);
+        OverviewTask lastGridTask = overview.getCurrentTasksForTablet().stream().min(
+                Comparator.comparingInt(OverviewTask::getTaskCenterX)).get();
+        assertNotNull("lastGridTask null.", lastGridTask);
+
+        lastGridTask.dismiss();
+
+        runOnRecentsView(recentsView -> assertEquals(
+                "Dismissing a lastGridTask didn't remove 1 lastGridTask from Overview",
+                numTasks - 1, recentsView.getTaskViewCount()));
+        runOnRecentsView(recentsView -> assertEquals("Grid overview should have equal row counts.",
+                recentsView.getTopRowTaskCountForTablet(),
+                recentsView.getBottomRowTaskCountForTablet()));
+        assertTrue("Clear All not visible.", overview.isClearAllVisible());
+    }
+
+    private void startTestAppsWithCheck() throws Exception {
+        startTestApps();
+        expectLaunchedAppState();
+    }
+
+    private void quickSwitchToPreviousAppAndAssert(boolean toRight) {
+        final LaunchedAppState launchedAppState = getAndAssertLaunchedApp();
+        if (toRight) {
+            launchedAppState.quickSwitchToPreviousApp();
+        } else {
+            launchedAppState.quickSwitchToPreviousAppSwipeLeft();
+        }
+
+        // While enable shell transition, Launcher can be resumed due to transient launch.
+        waitForLauncherCondition("Launcher shouldn't stay in resume forever",
+                this::isInLaunchedApp, 3000 /* timeout */);
+    }
+
+    private boolean isHardwareKeyboard() {
+        return Configuration.KEYBOARD_QWERTY
+                == mTargetContext.getResources().getConfiguration().keyboard;
+    }
+
+    private void assertIsInState(
+            @NonNull String failureMessage, @NonNull ExpectedState expectedState) {
+        assertTrue(failureMessage, enableLauncherOverviewInWindow()
+                ? isInRecentsWindowState(() -> expectedState.mRecentsState)
+                : isInState(() -> expectedState.mLauncherState));
+    }
+
+    private void waitForState(
+            @NonNull String failureMessage, @NonNull ExpectedState expectedState) {
+        if (enableLauncherOverviewInWindow()) {
+            waitForRecentsWindowState(failureMessage, () -> expectedState.mRecentsState);
+        } else {
+            waitForState(failureMessage, () -> expectedState.mLauncherState);
+        }
+    }
+
+    private void expectLaunchedAppState() {
+        executeOnLauncher(launcher -> assertTrue(
+                "Launcher activity is the top activity; expecting another activity to be the top "
+                        + "one",
+                isInLaunchedApp(launcher)));
+    }
+
+    private <T> T getFromRecentsView(Function<RecentsView, T> f) {
+        return getFromRecentsView(f, false);
+    }
+
+    private <T> T getFromRecentsView(Function<RecentsView, T> f, boolean forTearDown) {
+        if (enableLauncherOverviewInWindow()) {
+            return getFromRecentsWindow(recentsWindowManager ->
+                    (forTearDown && recentsWindowManager == null)
+                            ? null :  f.apply(recentsWindowManager.getOverviewPanel()));
+        } else {
+            return getFromLauncher(launcher -> (forTearDown && launcher == null)
+                    ? null : f.apply(launcher.getOverviewPanel()));
+        }
+    }
+
+    private void runOnRecentsView(Consumer<RecentsView> f) {
+        runOnRecentsView(f, false);
+    }
+
+    private void runOnRecentsView(Consumer<RecentsView> f, boolean forTearDown) {
+        getFromRecentsView(recentsView -> {
+            if (forTearDown && recentsView == null) {
+                return null;
+            }
+            f.accept(recentsView);
+            return null;
+        }, forTearDown);
+    }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
index daa4ec3..37ac4a0 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsSplitscreen.java
@@ -16,8 +16,6 @@
 package com.android.quickstep;
 
 
-import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
-
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
@@ -32,7 +30,6 @@
 import com.android.launcher3.tapl.Taskbar;
 import com.android.launcher3.tapl.TaskbarAppIcon;
 import com.android.quickstep.util.SplitScreenTestUtils;
-import com.android.wm.shell.Flags;
 
 import org.junit.After;
 import org.junit.Before;
@@ -89,25 +86,14 @@
                 .getSplitScreenMenuItem()
                 .click();
 
-        if (enableSplitContextually()) {
-            // We're staying in all apps, use same instance
-            mLauncher.getAllApps()
-                    .getAppIcon(CALCULATOR_APP_NAME)
-                    .launchIntoSplitScreen();
-        } else {
-            // We're in overview, use taskbar instance
-            mLauncher.getLaunchedAppState()
-                    .getTaskbar()
-                    .getAppIcon(CALCULATOR_APP_NAME)
-                    .launchIntoSplitScreen();
-        }
+        // We're staying in all apps, use same instance
+        mLauncher.getAllApps()
+                .getAppIcon(CALCULATOR_APP_NAME)
+                .launchIntoSplitScreen();
     }
 
     @Test
     public void testSaveAppPairMenuItemOrActionExistsOnSplitPair() {
-        assumeTrue("App pairs feature is currently not enabled, no test needed",
-                Flags.enableAppPairs());
-
         Overview overview = SplitScreenTestUtils.createAndLaunchASplitPairInOverview(mLauncher);
 
         if (mLauncher.isGridOnlyOverviewEnabled() || !mLauncher.isTablet()) {
@@ -120,9 +106,6 @@
 
     @Test
     public void testSaveAppPairMenuItemDoesNotExistOnSingleTask() throws Exception {
-        assumeTrue("App pairs feature is currently not enabled, no test needed",
-                Flags.enableAppPairs());
-
         startAppFast(CALCULATOR_APP_PACKAGE);
 
         assertFalse("Save app pair menu item is erroneously appearing on single task",
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
index c24e974..ec245ee 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTaskbar.java
@@ -24,7 +24,6 @@
 import androidx.test.filters.LargeTest;
 
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
-import com.android.launcher3.util.rule.ScreenRecordRule;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -134,7 +133,6 @@
 
     @Test
     @PortraitLandscape
-    @ScreenRecordRule.ScreenRecord // b/349439239
     public void testLaunchAppInSplitscreen_fromTaskbarAllApps() {
         getTaskbar().openAllApps()
                 .getAppIcon(TEST_APP_NAME)
diff --git a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt b/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt
new file mode 100644
index 0000000..47108e0
--- /dev/null
+++ b/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.desktop
+
+import android.animation.Animator
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.res.Resources
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.util.DisplayMetrics
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import androidx.core.util.Supplier
+import com.android.app.animation.Interpolators
+import com.android.internal.jank.Cuj
+import com.android.launcher3.desktop.DesktopAppLaunchAnimatorHelper
+import com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.window.flags.Flags
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+class DesktopAppLaunchAnimatorHelperTest {
+
+    @get:Rule val setFlagsRule = SetFlagsRule()
+
+    private val context = mock<Context>()
+    private val resources = mock<Resources>()
+    private val transaction = mock<SurfaceControl.Transaction>()
+    private val transactionSupplier = mock<Supplier<SurfaceControl.Transaction>>()
+
+    private lateinit var helper: DesktopAppLaunchAnimatorHelper
+
+    @Before
+    fun setUp() {
+        helper =
+            DesktopAppLaunchAnimatorHelper(
+                context = context,
+                launchType = AppLaunchType.LAUNCH,
+                cujType = Cuj.CUJ_DESKTOP_MODE_APP_LAUNCH_FROM_INTENT,
+                transactionSupplier = transactionSupplier,
+            )
+        whenever(transactionSupplier.get()).thenReturn(transaction)
+        whenever(transaction.setCrop(any(), any())).thenReturn(transaction)
+        whenever(transaction.setCornerRadius(any(), any())).thenReturn(transaction)
+
+        whenever(context.resources).thenReturn(resources)
+        whenever(resources.displayMetrics).thenReturn(DisplayMetrics())
+        whenever(context.mainThreadHandler).thenReturn(MAIN_EXECUTOR.handler)
+    }
+
+    @Test
+    fun launchTransition_returnsLaunchAnimator() {
+        val openChange =
+            TransitionInfo.Change(mock(), mock()).apply {
+                mode = WindowManager.TRANSIT_OPEN
+                taskInfo = TASK_INFO_FREEFORM
+            }
+        val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
+        transitionInfo.addChange(openChange)
+
+        val actual = helper.createAnimators(transitionInfo, finishCallback = {})
+
+        assertThat(actual).hasSize(1)
+        assertLaunchAnimator(actual[0])
+    }
+
+    @Test
+    fun noLaunchTransition_returnsEmptyAnimatorsList() {
+        val pipChange =
+            TransitionInfo.Change(mock(), mock()).apply {
+                mode = WindowManager.TRANSIT_PIP
+                taskInfo = TASK_INFO_FREEFORM
+            }
+        val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
+        transitionInfo.addChange(pipChange)
+
+        val actual = helper.createAnimators(transitionInfo, finishCallback = {})
+
+        assertThat(actual).hasSize(0)
+    }
+
+    @Test
+    fun minimizeTransition_returnsLaunchAndMinimizeAnimator() {
+        val openChange =
+            TransitionInfo.Change(mock(), mock()).apply {
+                mode = WindowManager.TRANSIT_OPEN
+                taskInfo = TASK_INFO_FREEFORM
+            }
+        val minimizeChange =
+            TransitionInfo.Change(mock(), mock()).apply {
+                mode = WindowManager.TRANSIT_TO_BACK
+                taskInfo = TASK_INFO_FREEFORM
+            }
+        val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
+        transitionInfo.addChange(openChange)
+        transitionInfo.addChange(minimizeChange)
+
+        val actual = helper.createAnimators(transitionInfo, finishCallback = {})
+
+        assertThat(actual).hasSize(2)
+        assertLaunchAnimator(actual[0])
+        assertMinimizeAnimator(actual[1])
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX)
+    fun trampolineTransition_flagEnabled_returnsLaunchAndCloseAnimator() {
+        val openChange =
+            TransitionInfo.Change(mock(), mock()).apply {
+                mode = WindowManager.TRANSIT_OPEN
+                taskInfo = TASK_INFO_FREEFORM
+            }
+        val closeChange =
+            TransitionInfo.Change(mock(), mock()).apply {
+                mode = WindowManager.TRANSIT_CLOSE
+                taskInfo = TASK_INFO_FREEFORM
+            }
+        val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
+        transitionInfo.addChange(openChange)
+        transitionInfo.addChange(closeChange)
+
+        val actual = helper.createAnimators(transitionInfo, finishCallback = {})
+
+        assertThat(actual).hasSize(2)
+        assertTrampolineLaunchAnimator(actual[0])
+        assertCloseAnimator(actual[1])
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX)
+    fun trampolineTransition_flagDisabled_returnsLaunchAnimator() {
+        val openChange =
+            TransitionInfo.Change(mock(), mock()).apply {
+                mode = WindowManager.TRANSIT_OPEN
+                taskInfo = TASK_INFO_FREEFORM
+            }
+        val closeChange =
+            TransitionInfo.Change(mock(), mock()).apply {
+                mode = WindowManager.TRANSIT_CLOSE
+                taskInfo = TASK_INFO_FREEFORM
+            }
+        val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
+        transitionInfo.addChange(openChange)
+        transitionInfo.addChange(closeChange)
+
+        val actual = helper.createAnimators(transitionInfo, finishCallback = {})
+
+        assertThat(actual).hasSize(1)
+        assertLaunchAnimator(actual[0])
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX)
+    fun trampolineTransition_flagEnabled_hitDesktopWindowLimit_returnsLaunchMinimizeCloseAnimator() {
+        val openChange =
+            TransitionInfo.Change(mock(), mock()).apply {
+                mode = WindowManager.TRANSIT_OPEN
+                taskInfo = TASK_INFO_FREEFORM
+            }
+        val minimizeChange =
+            TransitionInfo.Change(mock(), mock()).apply {
+                mode = WindowManager.TRANSIT_TO_BACK
+                taskInfo = TASK_INFO_FREEFORM
+            }
+        val closeChange =
+            TransitionInfo.Change(mock(), mock()).apply {
+                mode = WindowManager.TRANSIT_CLOSE
+                taskInfo = TASK_INFO_FREEFORM
+            }
+        val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
+        transitionInfo.addChange(openChange)
+        transitionInfo.addChange(minimizeChange)
+        transitionInfo.addChange(closeChange)
+
+        val actual = helper.createAnimators(transitionInfo, finishCallback = {})
+
+        assertThat(actual).hasSize(3)
+        assertTrampolineLaunchAnimator(actual[0])
+        assertMinimizeAnimator(actual[1])
+        assertCloseAnimator(actual[2])
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_TRAMPOLINE_CLOSE_ANIMATION_BUGFIX)
+    fun trampolineTransition_flagDisabled_hitDesktopWindowLimit_returnsLaunchMinimizeAnimator() {
+        val openChange =
+            TransitionInfo.Change(mock(), mock()).apply {
+                mode = WindowManager.TRANSIT_OPEN
+                taskInfo = TASK_INFO_FREEFORM
+            }
+        val minimizeChange =
+            TransitionInfo.Change(mock(), mock()).apply {
+                mode = WindowManager.TRANSIT_TO_BACK
+                taskInfo = TASK_INFO_FREEFORM
+            }
+        val closeChange =
+            TransitionInfo.Change(mock(), mock()).apply {
+                mode = WindowManager.TRANSIT_CLOSE
+                taskInfo = TASK_INFO_FREEFORM
+            }
+        val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
+        transitionInfo.addChange(openChange)
+        transitionInfo.addChange(minimizeChange)
+        transitionInfo.addChange(closeChange)
+
+        val actual = helper.createAnimators(transitionInfo, finishCallback = {})
+
+        assertThat(actual).hasSize(2)
+        assertLaunchAnimator(actual[0])
+        assertMinimizeAnimator(actual[1])
+    }
+
+    private fun assertLaunchAnimator(animator: Animator) {
+        assertThat(animator).isInstanceOf(AnimatorSet::class.java)
+        assertThat((animator as AnimatorSet).childAnimations.size).isEqualTo(2)
+        assertThat(animator.childAnimations[0]).isInstanceOf(ValueAnimator::class.java)
+        assertThat(animator.childAnimations[0].interpolator)
+            .isEqualTo(AppLaunchType.LAUNCH.boundsAnimationParams.interpolator)
+        assertThat(animator.childAnimations[0].duration)
+            .isEqualTo(AppLaunchType.LAUNCH.boundsAnimationParams.durationMs)
+        assertThat(animator.childAnimations[1]).isInstanceOf(ValueAnimator::class.java)
+        assertThat(animator.childAnimations[1].interpolator).isEqualTo(Interpolators.LINEAR)
+        assertThat(animator.childAnimations[1].duration)
+            .isEqualTo(AppLaunchType.LAUNCH.alphaDurationMs)
+    }
+
+    private fun assertTrampolineLaunchAnimator(animator: Animator) {
+        assertThat(animator).isInstanceOf(AnimatorSet::class.java)
+        assertThat((animator as AnimatorSet).childAnimations.size).isEqualTo(1)
+        assertThat(animator.childAnimations[0]).isInstanceOf(ValueAnimator::class.java)
+        assertThat(animator.childAnimations[0].interpolator).isEqualTo(Interpolators.LINEAR)
+        assertThat(animator.childAnimations[0].duration)
+            .isEqualTo(AppLaunchType.LAUNCH.alphaDurationMs)
+    }
+
+    private fun assertMinimizeAnimator(animator: Animator) {
+        assertThat(animator).isInstanceOf(AnimatorSet::class.java)
+        assertThat((animator as AnimatorSet).childAnimations.size).isEqualTo(2)
+        assertThat(animator.childAnimations[0]).isInstanceOf(ValueAnimator::class.java)
+        assertThat(animator.childAnimations[0].interpolator)
+            .isInstanceOf(Interpolators.STANDARD_ACCELERATE::class.java)
+        assertThat(animator.childAnimations[0].duration).isEqualTo(200)
+        assertThat(animator.childAnimations[1]).isInstanceOf(ValueAnimator::class.java)
+        assertThat(animator.childAnimations[1].interpolator)
+            .isInstanceOf(Interpolators.LINEAR::class.java)
+        assertThat(animator.childAnimations[1].duration).isEqualTo(100)
+    }
+
+    private fun assertCloseAnimator(animator: Animator) {
+        assertThat(animator).isInstanceOf(ValueAnimator::class.java)
+        assertThat(animator.interpolator).isInstanceOf(Interpolators.LINEAR::class.java)
+        assertThat(animator.duration).isEqualTo(100)
+    }
+
+    private companion object {
+        val TASK_INFO_FREEFORM =
+            ActivityManager.RunningTaskInfo().apply {
+                baseIntent =
+                    Intent().apply {
+                        component = ComponentName("com.example.app", "com.example.app.MainActivity")
+                    }
+                configuration.windowConfiguration.windowingMode =
+                    WindowConfiguration.WINDOWING_MODE_FREEFORM
+            }
+    }
+}
diff --git a/res/drawable/ic_desktop_add.xml b/res/drawable/ic_desktop_add.xml
index fa5e0de..d31b04b 100644
--- a/res/drawable/ic_desktop_add.xml
+++ b/res/drawable/ic_desktop_add.xml
@@ -14,8 +14,8 @@
   ~ limitations under the License.
   -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="48dp"
-    android:height="48dp"
+    android:width="24dp"
+    android:height="24dp"
     android:viewportWidth="960"
     android:viewportHeight="960">
     <path
diff --git a/res/drawable/ic_unpin.xml b/res/drawable/ic_unpin.xml
new file mode 100644
index 0000000..557b4f9
--- /dev/null
+++ b/res/drawable/ic_unpin.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2025 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <group>
+    <clip-path
+        android:pathData="M0,0h24v24h-24z"/>
+    <path
+        android:pathData="M17,3V5H16V13.175L14,11.175V5H10V7.175L7.825,5L7,4.175V3H17ZM12,23L11,22V16H6V14L8,12V10.85L1.4,4.2L2.8,2.8L21.2,21.2L19.75,22.6L13.15,16H13V22L12,23ZM8.85,14H11.15L10.05,12.9L10,12.85L8.85,14Z"
+        android:fillColor="#FF000000"/>
+  </group>
+</vector>
diff --git a/res/drawable/private_space_install_app_icon.xml b/res/drawable/private_space_install_app_icon.xml
index cfec2b1..1e7fe43 100644
--- a/res/drawable/private_space_install_app_icon.xml
+++ b/res/drawable/private_space_install_app_icon.xml
@@ -13,19 +13,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="60dp"
-    android:height="60dp"
-    android:viewportWidth="60"
-    android:viewportHeight="60">
-    <group>
-        <clip-path
-            android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z" />
-        <path
-            android:pathData="M30 0H30A30 30 0 0 1 60 30V30A30 30 0 0 1 30 60H30A30 30 0 0 1 0 30V30A30 30 0 0 1 30 0Z"
-            android:fillColor="@color/material_color_surface_container_lowest" />
-        <path
-            android:pathData="M29 31h-6v-2h6v-6h2v6h6v2h-6v6h-2v-6Z"
-            android:fillColor="@color/material_color_on_surface" />
-    </group>
-</vector>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/material_color_surface_container_lowest"/>
+    <foreground android:drawable="@drawable/private_space_install_app_icon_foreground" />
+</adaptive-icon>
diff --git a/res/drawable/private_space_install_app_icon_foreground.xml b/res/drawable/private_space_install_app_icon_foreground.xml
new file mode 100644
index 0000000..d55abe7
--- /dev/null
+++ b/res/drawable/private_space_install_app_icon_foreground.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2025 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="60dp"
+    android:height="60dp"
+    android:viewportWidth="60"
+    android:viewportHeight="60">
+    <path
+        android:pathData="M29 31h-6v-2h6v-6h2v6h6v2h-6v6h-2v-6Z"
+        android:fillColor="@color/material_color_on_surface" />
+</vector>
diff --git a/res/layout/all_apps_personal_work_tabs.xml b/res/layout/all_apps_personal_work_tabs.xml
index b6a8ed8..1435f82 100644
--- a/res/layout/all_apps_personal_work_tabs.xml
+++ b/res/layout/all_apps_personal_work_tabs.xml
@@ -34,6 +34,7 @@
         android:layout_weight="1"
         android:background="@drawable/all_apps_tabs_background"
         android:text="@string/all_apps_personal_tab"
+        android:contentDescription="@string/all_apps_personal_tab_content_description"
         android:textColor="@color/all_apps_tab_text"
         android:textSize="14sp"
         style="?android:attr/borderlessButtonStyle" />
@@ -46,6 +47,7 @@
         android:layout_weight="1"
         android:background="@drawable/all_apps_tabs_background"
         android:text="@string/all_apps_work_tab"
+        android:contentDescription="@string/all_apps_work_tab_content_description"
         android:textColor="@color/all_apps_tab_text"
         android:textSize="14sp"
         style="?android:attr/borderlessButtonStyle" />
diff --git a/res/layout/launcher.xml b/res/layout/launcher.xml
index 83c8d6c..adf4597 100644
--- a/res/layout/launcher.xml
+++ b/res/layout/launcher.xml
@@ -29,6 +29,7 @@
         android:importantForAccessibility="no">
 
         <com.android.launcher3.views.AccessibilityActionsView
+            android:id="@+id/accessibility_action_view"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:contentDescription="@string/home_screen"
diff --git a/res/layout/work_apps_edu.xml b/res/layout/work_apps_edu.xml
index 0e2c19a..a19d13a 100644
--- a/res/layout/work_apps_edu.xml
+++ b/res/layout/work_apps_edu.xml
@@ -46,6 +46,8 @@
             android:layout_width="@dimen/rounded_button_width"
             android:layout_height="@dimen/rounded_button_width"
             android:layout_marginTop="@dimen/work_edu_card_button_margin_top"
+            android:clickable="true"
+            android:contentDescription="@string/accessibility_close"
             android:gravity="center"
             android:background="@drawable/inset_rounded_action_button">
             <ImageButton
@@ -54,7 +56,7 @@
                 android:clickable="false"
                 android:scaleType="centerInside"
                 android:layout_gravity="center"
-                android:contentDescription="@string/accessibility_close"
+                android:importantForAccessibility="no"
                 android:background="@android:color/transparent"
                 android:src="@drawable/ic_close_work_edu" />
         </FrameLayout>
diff --git a/res/layout/work_mode_utility_view.xml b/res/layout/work_mode_utility_view.xml
index fc112ce..b68ff3e 100644
--- a/res/layout/work_mode_utility_view.xml
+++ b/res/layout/work_mode_utility_view.xml
@@ -14,6 +14,7 @@
   ~ limitations under the License.
   -->
 <com.android.launcher3.allapps.WorkUtilityView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/work_utility_view"
     android:layout_height="wrap_content"
     android:layout_width="wrap_content"
     android:orientation="vertical"
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 4dd5e1d..a2a1cf2 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Kon geen programme kry wat by \"<xliff:g id="QUERY">%1$s</xliff:g>\" pas nie"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Alle apps"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Applys"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Kennisgewings"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Raak en hou om \'n kortpad te skuif."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Dubbeltik en hou om \'n kortpad te skuif of gebruik gepasmaakte handelinge."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Bladsy %1$d van %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Tuisskerm %1$d van %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nuwe tuisskermbladsy"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Vouer oopgemaak, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Tik om die vouer toe te maak"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tik om nuwe naam te stoor"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Vouer hernoem na <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Vouer: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Vouer: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> of meer items"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Naamlose vouer"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Apppaar: <xliff:g id="APP1">%1$s</xliff:g> en <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Muurpapier en styl"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Wysig tuisskerm"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Legstukkelys is toegemaak"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Voeg by tuisskerm"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Skuif item hierheen"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Item is by tuisskerm gevoeg"</string>
     <string name="item_removed" msgid="851119963877842327">"Item is verwyder"</string>
     <string name="undo" msgid="4151576204245173321">"Ontdoen"</string>
     <string name="action_move" msgid="4339390619886385032">"Skuif item"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Maak toe"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Persoonlik"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Werk"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Werkprofiel"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Werkprogramme het \'n kenteken en is sigbaar vir jou IT-administrateur"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Het dit"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index c40ef69..ac462ac 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"ከ«<xliff:g id="QUERY">%1$s</xliff:g>» ጋር የሚዛመዱ ምንም መተግበሪያዎች አልተገኙም"</string>
     <string name="label_application" msgid="8531721983832654978">"መተግበሪያ"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"ሁሉም መተግበሪያዎች"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"የመተግበሪያዎች ዝርዝር"</string>
     <string name="notifications_header" msgid="1404149926117359025">"ማሳወቂያዎች"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"አቋራጭን ለማንቀሳቀስ ይንኩ እና ይያዙ"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"አቋራጭን ለማንቀሳቀስ ወይም ብጁ እርምጃዎችን ለመጠቀም ሁለቴ መታ ያድርጉ እና ይያዙ።"</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"ገፅ %1$d ከ%2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"መነሻ ማያ ገፅ %1$d ከ%2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"አዲስ የመነሻ ማያ ገፅ"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"አቃፊ ተከፍቷል፣ <xliff:g id="WIDTH">%1$d</xliff:g> በ<xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"አቃፊን ለመዝጋት መታ ያድርጉ"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"ዳግም የተሰጠውን ስም ለማስቀመጥ መታ ያድርጉ"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"አቃፊ <xliff:g id="NAME">%1$s</xliff:g> ተብሎ ዳግም ተሰይሟል"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"አቃፊ፦ <xliff:g id="NAME">%1$s</xliff:g>፣ <xliff:g id="SIZE">%2$d</xliff:g> ንጥሎች"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"አቃፊ፦ <xliff:g id="NAME">%1$s</xliff:g>፣ <xliff:g id="SIZE">%2$d</xliff:g> ወይም ተጨማሪ ንጥሎች"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"ያልተሰየመ አቃፊ"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"የመተግበሪያ ጥምረት፦ <xliff:g id="APP1">%1$s</xliff:g> እና <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ልጣፍ እና ቅጥ"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"መነሻ ማያ ገጽን አርትዕ"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"የመግብሮች ዝርዝር ተዘግቷል"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"ወደ መነሻ ማያ ገፅ አክል"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ንጥልን ወደዚህ ውሰድ"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"ወደ መነሻ ማያ ገፅ ንጥል ታክሏል"</string>
     <string name="item_removed" msgid="851119963877842327">"ንጥል ነገር ተንቀሳቅሷል"</string>
     <string name="undo" msgid="4151576204245173321">"ቀልብስ"</string>
     <string name="action_move" msgid="4339390619886385032">"ንጥልን አንቀሳቅስ"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"ዝጋ"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"የግል"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"ሥራ"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"የሥራ መገለጫ"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"የሥራ መተግበሪያዎች ባጅ የተደረገባቸው እና ለእርስዎ የአይቲ አስተዳዳሪ የሚታዩ ናቸው"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"ገባኝ"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 98c4f3e..83b07ec 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -24,7 +24,7 @@
     <string name="activity_not_found" msgid="8071924732094499514">"لم يتم تثبيت التطبيق."</string>
     <string name="activity_not_available" msgid="7456344436509528827">"التطبيق ليس متاحًا"</string>
     <string name="safemode_shortcut_error" msgid="9160126848219158407">"تم إيقاف التطبيق الذي تم تنزيله في الوضع الآمن"</string>
-    <string name="safemode_widget_error" msgid="4863470563535682004">"الأدوات غير مفعّلة في الوضع الآمن"</string>
+    <string name="safemode_widget_error" msgid="4863470563535682004">"التطبيقات المصغَّرة غير مفعّلة في الوضع الآمن"</string>
     <string name="shortcut_not_available" msgid="2536503539825726397">"الاختصار غير متاح"</string>
     <string name="home_screen" msgid="5629429142036709174">"الشاشة الرئيسية"</string>
     <string name="set_default_home_app" msgid="5808906607627586381">"يمكن ضبط \"<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>\" كتطبيق الشاشة الرئيسية التلقائي من خلال \"الإعدادات\""</string>
@@ -38,13 +38,13 @@
     <string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"لا يمكن استخدام هذين التطبيقَين في الوقت نفسه على هذا الجهاز"</string>
     <string name="app_pair_needs_unfold" msgid="4588897528143807002">"افتح الجهاز لاستخدام هذين التطبيقَين في الوقت نفسه"</string>
     <string name="app_pair_not_available" msgid="3556767440808032031">"ميزة \"استخدام تطبيقين في الوقت نفسه\" غير متوفّرة"</string>
-    <string name="long_press_widget_to_add" msgid="3587712543577675817">"انقر مع الاستمرار لنقل أداة."</string>
-    <string name="long_accessible_way_to_add" msgid="2733588281439571974">"انقر مرتين مع تثبيت إصبعك لنقل أداة أو استخدام الإجراءات المخصّصة."</string>
+    <string name="long_press_widget_to_add" msgid="3587712543577675817">"انقر مع الاستمرار لنقل تطبيق مصغَّر."</string>
+    <string name="long_accessible_way_to_add" msgid="2733588281439571974">"انقر مرتين مع تثبيت إصبعك لنقل تطبيق مصغَّر أو استخدام الإجراءات المخصّصة."</string>
     <string name="widget_picker_widget_options_button_description" msgid="4770099264476852363">"خيارات إضافية"</string>
     <string name="widget_picker_show_all_widgets_menu_item_title" msgid="9023638224586908119">"عرض كل التطبيقات المصغّرة"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"‏العرض %1$d الطول %2$d"</string>
-    <string name="widget_preview_context_description" msgid="9045841361655787574">"أداة <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
+    <string name="widget_preview_context_description" msgid="9045841361655787574">"التطبيق المصغَّر <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"‏التطبيق المصغّرة \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\"، بعرض ‎%2$d وارتفاع ‎%3$d"</string>
     <string name="add_item_request_drag_hint" msgid="8730547755622776606">"يُرجى النقر مع الاستمرار على التطبيق المصغّر لنقله إلى الشاشة الرئيسية"</string>
     <string name="add_to_home_screen" msgid="9168649446635919791">"إضافة إلى الشاشة الرئيسية"</string>
@@ -62,7 +62,7 @@
     <string name="widget_button_text" msgid="2880537293434387943">"التطبيقات المصغّرة"</string>
     <string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"بحث"</string>
     <string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"محو النص من مربّع البحث"</string>
-    <string name="no_widgets_available" msgid="4337693382501046170">"الأدوات والاختصارات غير متاحة."</string>
+    <string name="no_widgets_available" msgid="4337693382501046170">"التطبيقات المصغَّرة والاختصارات غير متاحة."</string>
     <string name="no_search_results" msgid="3787956167293097509">"لم يتم العثور على تطبيقات مصغّرة أو اختصارات."</string>
     <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"التطبيقات الشخصية"</string>
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"تطبيقات العمل"</string>
@@ -75,13 +75,14 @@
     <string name="widgets_list_expand_button_label" msgid="7912016136574932622">"عرض الكل"</string>
     <string name="widgets_list_expand_button_content_description" msgid="4600513860973450888">"عرض كل التطبيقات المصغّرة"</string>
     <string name="widgets_list_expanded" msgid="7374857868788557730">"جارٍ عرض كل التطبيقات المصغّرة"</string>
-    <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"انقر لتغيير إعدادات الأداة"</string>
-    <string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"تغيير إعدادات الأداة"</string>
+    <string name="reconfigurable_widget_education_tip" msgid="6336962690888067057">"انقر لتغيير إعدادات التطبيق المصغَّر"</string>
+    <string name="widget_reconfigure_button_content_description" msgid="8811472721881205250">"تغيير إعدادات التطبيق المصغَّر"</string>
     <string name="all_apps_search_bar_hint" msgid="1390553134053255246">"بحث في التطبيقات"</string>
     <string name="all_apps_loading_message" msgid="5813968043155271636">"جارٍ تحميل التطبيقات…"</string>
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"لم يتم العثور على أي تطبيقات تتطابق مع \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"تطبيق"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"جميع التطبيقات"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"قائمة التطبيقات"</string>
     <string name="notifications_header" msgid="1404149926117359025">"الإشعارات"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"انقر مع الاستمرار لنقل اختصار"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"انقر مرتين مع تثبيت إصبعك لنقل اختصار أو استخدام الإجراءات المخصّصة."</string>
@@ -106,7 +107,7 @@
     <string name="permdesc_read_settings" msgid="4208061150510996676">"يسمح هذا الإذن للتطبيق بالاطلاع على الإعدادات والاختصارات على الشاشة الرئيسية."</string>
     <string name="permlab_write_settings" msgid="4820028712156303762">"تعديل الإعدادات والاختصارات على الشاشة الرئيسية"</string>
     <string name="permdesc_write_settings" msgid="726859348127868466">"يسمح هذا الإذن للتطبيق بتغيير الإعدادات والاختصارات على الشاشة الرئيسية."</string>
-    <string name="gadget_error_text" msgid="740356548025791839">"يتعذّر تحميل الأداة."</string>
+    <string name="gadget_error_text" msgid="740356548025791839">"يتعذّر تحميل التطبيق المصغَّر."</string>
     <string name="gadget_setup_text" msgid="8348374825537681407">"إعدادات التطبيق المصغّر"</string>
     <string name="gadget_complete_setup_text" msgid="309040266978007925">"انقر لإكمال الإعداد."</string>
     <string name="uninstall_system_app_text" msgid="4172046090762920660">"هذا تطبيق نظام وتتعذر إزالته."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"‏الصفحة %1$d من %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"‏الشاشة الرئيسية %1$d من %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"صفحة الشاشة الرئيسية الجديدة"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"نشط"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"تم التصغير"</string>
     <string name="folder_opened" msgid="94695026776264709">"تم فتح المجلد، بمقاس <xliff:g id="WIDTH">%1$d</xliff:g> في <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"انقر لإغلاق المجلد"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"انقر لحفظ الاسم الجديد"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"تمت إعادة تسمية المجلد إلى <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"المجلد: <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="SIZE">%2$d</xliff:g> عنصر"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"المجلد: <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="SIZE">%2$d</xliff:g> عنصر أو أكثر"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"مجلد بدون اسم"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"استخدام تطبيقين في الوقت نفسه: تطبيق \"<xliff:g id="APP1">%1$s</xliff:g>\" و\"<xliff:g id="APP2">%2$s</xliff:g>\""</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"الخلفية والأسلوب"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"تعديل الشاشة الرئيسية"</string>
@@ -156,11 +160,10 @@
     <string name="dialog_update_message" msgid="4176784553982226114">"لم يتمّ تحديث التطبيق الخاص بهذا الرمز. يمكنك تحديث التطبيق يدويًا لإعادة تفعيل هذا الاختصار أو إزالة الرمز."</string>
     <string name="dialog_update" msgid="2178028071796141234">"تحديث"</string>
     <string name="dialog_remove" msgid="6510806469849709407">"إزالة"</string>
-    <string name="widgets_list" msgid="796804551140113767">"قائمة الأدوات"</string>
-    <string name="widgets_list_closed" msgid="6141506579418771922">"تم إغلاق قائمة الأدوات."</string>
+    <string name="widgets_list" msgid="796804551140113767">"قائمة التطبيقات المصغَّرة"</string>
+    <string name="widgets_list_closed" msgid="6141506579418771922">"تم إغلاق قائمة التطبيقات المصغَّرة."</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"إضافة تطبيق للشاشة الرئيسية"</string>
     <string name="action_move_here" msgid="2170188780612570250">"نقل العنصر إلى هنا"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"تمت إضافة العنصر إلى الشاشة الرئيسية"</string>
     <string name="item_removed" msgid="851119963877842327">"تمّت إزالة العنصر."</string>
     <string name="undo" msgid="4151576204245173321">"تراجع"</string>
     <string name="action_move" msgid="4339390619886385032">"نقل العنصر"</string>
@@ -179,12 +182,16 @@
     <string name="action_increase_height" msgid="459390020612501122">"زيادة الارتفاع"</string>
     <string name="action_decrease_width" msgid="1374549771083094654">"تقليل العرض"</string>
     <string name="action_decrease_height" msgid="282377193880900022">"تقليل الارتفاع"</string>
-    <string name="widget_resized" msgid="9130327887929620">"تم تغيير حجم الأداة إلى العرض <xliff:g id="NUMBER_0">%1$s</xliff:g> والارتفاع <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
+    <string name="widget_resized" msgid="9130327887929620">"تم تغيير حجم التطبيق المصغَّر إلى العرض <xliff:g id="NUMBER_0">%1$s</xliff:g> والارتفاع <xliff:g id="NUMBER_1">%2$s</xliff:g>"</string>
     <string name="action_deep_shortcut" msgid="4766835855579976045">"قائمة الاختصارات"</string>
     <string name="action_dismiss_notification" msgid="5909461085055959187">"تجاهل"</string>
     <string name="accessibility_close" msgid="2277148124685870734">"إغلاق"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"شخصية"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"للعمل"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"ملف العمل"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"تحمل تطبيقات العمل مميّزة بشارة ومرئية لمشرف تكنولوجيا المعلومات."</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"حسنًا"</string>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index acce0d2..6db0ffb 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"ৰ সৈতে মিলা কোনো এপ্ বিচাৰি পোৱা নগ\'ল"</string>
     <string name="label_application" msgid="8531721983832654978">"এপ্"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"আটাইবোৰ এপ্"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"এপৰ সূচী"</string>
     <string name="notifications_header" msgid="1404149926117359025">"জাননীসমূহ"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"শ্বৰ্টকাট স্থানান্তৰ কৰিবলৈ দুবাৰ টিপি ধৰি ৰাখক।"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"কোনো শ্বৰ্টকাট স্থানান্তৰ কৰিবলৈ দুবাৰ টিপি ধৰি ৰাখক অথবা কাষ্টম কাৰ্য ব্যৱহাৰ কৰক।"</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%2$dৰ %1$d পৃষ্ঠা"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"গৃহ স্ক্ৰীন %2$dৰ %1$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"গৃহ স্ক্ৰীনৰ নতুন পৃষ্ঠা"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"ফ’ল্ডাৰ খোলা হ’ল, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"ফ\'ল্ডাৰ বন্ধ কৰিবলৈ টিপক"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"সলনি কৰা নাম ছেভ কৰিবলৈ টিপক"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"ফ\'ল্ডাৰৰ নাম সলনি কৰি <xliff:g id="NAME">%1$s</xliff:g> কৰা হৈছে"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"ফ’ল্ডাৰ: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> টা বস্তু"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"ফ’ল্ডাৰ: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> টা অথবা তাতকৈ অধিক বস্তু"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"নামবিহীন ফ’ল্ডাৰ"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"এপ্ পেয়াৰ কৰা: <xliff:g id="APP1">%1$s</xliff:g> আৰু <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ৱালপেপাৰ আৰু শৈলী"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"গৃহ স্ক্ৰীন সম্পাদনা কৰক"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"ৱিজেটৰ তালিকা বন্ধ কৰা হ’ল"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"গৃহ স্ক্ৰীনত যোগ কৰক"</string>
     <string name="action_move_here" msgid="2170188780612570250">"বস্তুটো ইয়ালৈ স্থানান্তৰ কৰক"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"বস্তুটো গৃহ স্ক্ৰীনত যোগ কৰা হ’ল"</string>
     <string name="item_removed" msgid="851119963877842327">"বস্তুটো আঁতৰোৱা হ’ল"</string>
     <string name="undo" msgid="4151576204245173321">"আনডু কৰক"</string>
     <string name="action_move" msgid="4339390619886385032">"বস্তু স্থানান্তৰ কৰক"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"বন্ধ কৰক"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"ব্যক্তিগত"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"কৰ্মস্থান"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"কৰ্মস্থানৰ প্ৰ\'ফাইল"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"কৰ্মস্থানৰ এপ্‌সমূহ প্ৰতীকেৰে চিহ্নিত কৰা হয় আৰু সেইবোৰ আপোনাৰ আইটি প্ৰশাসকৰ বাবে দৃশ্যমান হয়"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"বুজি পালোঁ"</string>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 8eda426..28035df 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"<xliff:g id="QUERY">%1$s</xliff:g> sorğusuna uyğun tətbiq tapılmadı"</string>
     <string name="label_application" msgid="8531721983832654978">"Tətbiq"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Bütün tətbiqlər"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Tətbiq siyahısı"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Bildirişlər"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Qısayolu daşımaq üçün toxunub saxlayın."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Qısayolu daşımaq üçün iki dəfə toxunub saxlayın və ya fərdi əməliyyatlardan istifadə edin."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Səhifə %1$d of %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Əsas Səhifə ekranı %1$d of %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Yeni əsas ekran səhifəsi"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Qovluq açıldı, <xliff:g id="HEIGHT">%2$d</xliff:g> hündürlük ilə <xliff:g id="WIDTH">%1$d</xliff:g> enində"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Qovluq bağlamaq üçün toxunun"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Ad dəyişikliyini yadda saxlamaq üçün toxunun"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Qovluq adı <xliff:g id="NAME">%1$s</xliff:g> ilə dəyişdirildi"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Qovluq: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> element"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Qovluq: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> və ya daha çox element"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Adsız qovluq"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Tətbiq cütü: <xliff:g id="APP1">%1$s</xliff:g> və <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Divar kağızı və üslub"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Əsas ekranı redaktə edin"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Vidcet siyahısı bağlandı"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Əsas ekrana əlavə edin"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Elementi bura köçürün"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Element əsas ekrana əlavə edildi"</string>
     <string name="item_removed" msgid="851119963877842327">"Element silindi"</string>
     <string name="undo" msgid="4151576204245173321">"Ləğv edin"</string>
     <string name="action_move" msgid="4339390619886385032">"Elementi köçürün"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Bağlayın"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Şəxsi"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"İş"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"İş profili"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"İş tətbiqləri nişanlanıb və İT administratorunuza görünür"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Anladım"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 33941ff..c8782e2 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nije pronađena nijedna aplikacija za „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikacija"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Sve aplikacije"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Lista aplikacija"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Obaveštenja"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Dodirnite i zadržite radi pomeranja prečice."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Dvaput dodirnite i zadržite da biste pomerali prečicu ili koristite prilagođene radnje."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%1$d. stranica od %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"%1$d. početni ekran od %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nova stranica početnog ekrana"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Aktivno"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Smanjeno"</string>
     <string name="folder_opened" msgid="94695026776264709">"Folder je otvoren, <xliff:g id="WIDTH">%1$d</xliff:g> puta <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Dodirnite da biste zatvorili folder"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Dodirnite da biste sačuvali preimenovanje"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Folder je preimenovan u <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> stavke"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ili više stavki"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Neimenovani folder"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Par aplikacija: <xliff:g id="APP1">%1$s</xliff:g> i <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Pozadina i stil"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Izmeni početni ekran"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Lista vidžeta je zatvorena"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Dodajte na početni ekran"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Premesti stavku ovde"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Stavka je dodata na početni ekran"</string>
     <string name="item_removed" msgid="851119963877842327">"Stavka je uklonjena"</string>
     <string name="undo" msgid="4151576204245173321">"Opozovi"</string>
     <string name="action_move" msgid="4339390619886385032">"Premesti stavku"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Zatvori"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Lično"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Posao"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Poslovni profil"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Poslovne aplikacije su označene značkom i IT administrator može da ih vidi"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Važi"</string>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index d395006..ffb3d91 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Праграм, якія адпавядаюць запыту \"<xliff:g id="QUERY">%1$s</xliff:g>\", не знойдзена"</string>
     <string name="label_application" msgid="8531721983832654978">"Праграма"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Усе праграмы"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Спіс праграм"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Апавяшчэнні"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Націсніце і ўтрымлівайце ярлык для перамяшчэння."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Дакраніцеся двойчы і ўтрымлівайце, каб перамясціць ярлык або выкарыстоўваць спецыяльныя дзеянні."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Старонка %1$d з %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Галоўны экран %1$d з %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Новая старонка галоўнага экрана"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Папка адкрыта, <xliff:g id="WIDTH">%1$d</xliff:g> на <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Краніце, каб закрыць папку"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Краніце, каб захаваць новую назву"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Папка перайменавана ў <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Папка: <xliff:g id="NAME">%1$s</xliff:g>, элементы: <xliff:g id="SIZE">%2$d</xliff:g>"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Папка: <xliff:g id="NAME">%1$s</xliff:g>, элементы: <xliff:g id="SIZE">%2$d</xliff:g> ці больш"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Папка без назвы"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Спалучэнне праграм: <xliff:g id="APP1">%1$s</xliff:g> і <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Шпалеры і стыль"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Змяніць Галоўны экран"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Спіс віджэтаў закрыты"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Дадаць на галоўны экран"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Перамясціць элемент сюды"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Элемент дададзены на галоўны экран"</string>
     <string name="item_removed" msgid="851119963877842327">"Элемент выдалены"</string>
     <string name="undo" msgid="4151576204245173321">"Адрабіць"</string>
     <string name="action_move" msgid="4339390619886385032">"Перамясціць элемент"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Закрыць"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Асабістыя"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Працоўныя"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Працоўны профіль"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Працоўныя праграмы пазначаны спецыяльнымі значкамі, а таксама бачныя IT-адміністратару"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Зразумела"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index cf298c3..a4c3faf 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -62,7 +62,7 @@
     <string name="widget_button_text" msgid="2880537293434387943">"Приспособления"</string>
     <string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"Търсене"</string>
     <string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"Изчистване на текста от полето за търсене"</string>
-    <string name="no_widgets_available" msgid="4337693382501046170">"Няма налице преки пътища и приспособления"</string>
+    <string name="no_widgets_available" msgid="4337693382501046170">"Няма налични преки пътища и приспособления"</string>
     <string name="no_search_results" msgid="3787956167293097509">"Няма открити преки пътища или приспособления"</string>
     <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Лични"</string>
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Служебни"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Няма намерени приложения, съответстващи на „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
     <string name="label_application" msgid="8531721983832654978">"Приложение"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Всички приложения"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Списък с приложения"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Известия"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Докоснете и задръжте за преместване на пряк път."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Докоснете двукратно и задръжте за преместване на пряк път или използвайте персонализирани действия."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Страница %1$d от %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Начален екран %1$d от %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Нова страница на началния екран"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Активно"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Намалено"</string>
     <string name="folder_opened" msgid="94695026776264709">"Папката е отворена – <xliff:g id="WIDTH">%1$d</xliff:g> на <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Докоснете, за да затворите папката"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Докоснете, за да запазите новото име"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Папката е преименувана на „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Папка: „<xliff:g id="NAME">%1$s</xliff:g>“ – <xliff:g id="SIZE">%2$d</xliff:g> елемента"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Папка: „<xliff:g id="NAME">%1$s</xliff:g>“ – <xliff:g id="SIZE">%2$d</xliff:g> или повече елементи"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Папка без име"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Двойка приложения: <xliff:g id="APP1">%1$s</xliff:g> и <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Тапет и стил"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Редактиране на началния екран"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Списъкът с приспособления е затворен"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Добавяне към началния екран"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Преместване на елемента тук"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Елементът е добавен към началния екран"</string>
     <string name="item_removed" msgid="851119963877842327">"Елементът е премахнат"</string>
     <string name="undo" msgid="4151576204245173321">"Отмяна"</string>
     <string name="action_move" msgid="4339390619886385032">"Преместване на елемента"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Затваряне"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Лични"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Служебни"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Служебен потребителски профил"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Служебните приложения са означени със значка и са видими за системния администратор"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Разбрах"</string>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index f2654bb..6bc2483 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" এর সাথে মেলে এমন কোনো অ্যাপ পাওয়া যায়নি"</string>
     <string name="label_application" msgid="8531721983832654978">"অ্যাপ"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"সব অ্যাপ"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"অ্যাপ তালিকা"</string>
     <string name="notifications_header" msgid="1404149926117359025">"বিজ্ঞপ্তি"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"একটি শর্টকাট সরাতে টাচ করে ধরে রাখুন।"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"একটি শর্টকাট সরাতে বা কাস্টম অ্যাকশন ব্যবহার করতে ডবল ট্যাপ করে ধরে রাখুন।"</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%2$dটির মধ্যে %1$dটি পৃষ্ঠা"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"%2$dটির %1$d নম্বর হোম স্ক্রিন"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"নতুন হোম স্ক্রীনের পৃষ্ঠা"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"ফোল্ডার খোলা হয়েছে, <xliff:g id="WIDTH">%1$d</xliff:g> বাই <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"ফোল্ডার বন্ধ করতে আলতো চাপ দিন"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"পুনঃনামকরণ সংরক্ষণ করতে আলতো চাপ দিন"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"ফোল্ডারের নাম পাল্টে <xliff:g id="NAME">%1$s</xliff:g> করা হয়েছে"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"ফোল্ডার: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g>টি আইটেম"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"ফোল্ডার: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g>টি বা তার বেশি আইটেম"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"নামবিহীন ফোল্ডার"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"অ্যাপ পেয়ার: <xliff:g id="APP1">%1$s</xliff:g> ও <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ওয়ালপেপার এবং স্টাইল"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"হোম স্ক্রিন এডিট করুন"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"উইজেটের তালিকা বন্ধ করা হয়েছে"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"হোম স্ক্রিনে যোগ করুন"</string>
     <string name="action_move_here" msgid="2170188780612570250">"এখানে আইটেম সরান"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"হোম স্ক্রীনে আইটেম যোগ করা হয়েছে"</string>
     <string name="item_removed" msgid="851119963877842327">"আইটেম সরানো হয়েছে"</string>
     <string name="undo" msgid="4151576204245173321">"ফিরিয়ে আনুন"</string>
     <string name="action_move" msgid="4339390619886385032">"আইটেম সরান"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"বন্ধ করুন"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"ব্যক্তিগত"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"অফিস"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"অফিসের প্রোফাইল"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"অফিস অ্যাপে ব্যাজ যোগ করা হয়েছে এবং আপনার আইটি অ্যাডমিন সেগুলি দেখতে পাবেন"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"বুঝেছি"</string>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index 3277e22..394e30f 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -41,7 +41,7 @@
     <string name="long_press_widget_to_add" msgid="3587712543577675817">"Dodirnite i zadržite da pomjerite vidžet."</string>
     <string name="long_accessible_way_to_add" msgid="2733588281439571974">"Dvaput dodirnite i zadržite da pomjerite vidžet ili da koristite prilagođene radnje."</string>
     <string name="widget_picker_widget_options_button_description" msgid="4770099264476852363">"Više opcija"</string>
-    <string name="widget_picker_show_all_widgets_menu_item_title" msgid="9023638224586908119">"Prikazuj sve vidžete"</string>
+    <string name="widget_picker_show_all_widgets_menu_item_title" msgid="9023638224586908119">"Prikaži sve vidžete"</string>
     <string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Širina %1$d, visina %2$d"</string>
     <string name="widget_preview_context_description" msgid="9045841361655787574">"Vidžet <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nije pronađena nijedna aplikacija za upit \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikacija"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Sve aplikacije"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Lista aplikacija"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Obavještenja"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Dodirnite i zadržite da pomjerite prečicu."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Dvaput dodirnite i zadržite da pomjerite prečicu ili da koristite prilagođene radnje."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Strana %1$d od %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Početni ekran %1$d od %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nova stranica početnog ekrana"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Folder je otvoren, (š) <xliff:g id="WIDTH">%1$d</xliff:g> (v) <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Dodirnite da zatvorite folder"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Dodirnite da sačuvate promjenu naziva"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Ime foldera je promijenjeno u <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, br. stavki: <xliff:g id="SIZE">%2$d</xliff:g>"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ili više stavki"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Neimenovani folder"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Par aplikacija: <xliff:g id="APP1">%1$s</xliff:g> i <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Pozadinska slika i stil"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Uredi Početni ekran"</string>
@@ -140,7 +146,7 @@
     <string name="title_change_settings" msgid="1376365968844349552">"Promijeni postavke"</string>
     <string name="notification_dots_service_title" msgid="4284221181793592871">"Prikaži tačke za obavještenja"</string>
     <string name="developer_options_title" msgid="700788437593726194">"Opcije za programere"</string>
-    <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Dodaj ikone aplikacija na početni ekran"</string>
+    <string name="auto_add_shortcuts_label" msgid="4926805029653694105">"Dodavanje ikona aplikacija na početni ekran"</string>
     <string name="auto_add_shortcuts_description" msgid="7117251166066978730">"Za nove aplikacije"</string>
     <string name="package_state_unknown" msgid="7592128424511031410">"Nepoznato"</string>
     <string name="abandoned_clean_this" msgid="7610119707847920412">"Ukloni"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Spisak vidžeta je zatvoren"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Dodavanje na početni ekran"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Premjesti stavku ovdje"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Stavka je dodana na Početni ekran."</string>
     <string name="item_removed" msgid="851119963877842327">"Stavka je uklonjena"</string>
     <string name="undo" msgid="4151576204245173321">"Poništi"</string>
     <string name="action_move" msgid="4339390619886385032">"Premjesti stavku"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Zatvaranje"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Lično"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Poslovno"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Radni profil"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Poslovne aplikacije su označene i vaš IT administrator ih može vidjeti"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Razumijem"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index f8f8e28..07a21ab 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"No s\'ha trobat cap aplicació que coincideixi amb \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"Aplicació"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Totes les aplicacions"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Llista d\'aplicacions"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notificacions"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Fes doble toc i mantén premut per moure una drecera."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Fes doble toc i mantén premut per moure una drecera o per utilitzar accions personalitzades."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Pàgina %1$d de %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Pantalla d\'inici %1$d de %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Pàgina de la pantalla d\'inici nova"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Actiu"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Minimitzat"</string>
     <string name="folder_opened" msgid="94695026776264709">"S\'ha obert la carpeta, <xliff:g id="WIDTH">%1$d</xliff:g> per <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Toca per tancar la carpeta"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Toca per desar el nom nou"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"S\'ha canviat el nom de la carpeta a <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elements"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> o més elements"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Carpeta sense nom"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Parella d\'aplicacions: <xliff:g id="APP1">%1$s</xliff:g> i <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Estil i fons de pantalla"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Edita la pantalla d\'inici"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"S\'ha tancat la llista de widgets"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Afegeix a la pantalla d\'inici"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Mou l\'element aquí"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"S\'ha afegit l\'element a la pantalla d\'inici"</string>
     <string name="item_removed" msgid="851119963877842327">"S\'ha suprimit l\'element"</string>
     <string name="undo" msgid="4151576204245173321">"Desfés"</string>
     <string name="action_move" msgid="4339390619886385032">"Desplaça l\'element"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Tanca"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personal"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Treball"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de treball"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Les aplicacions de treball tenen una insígnia i el teu administrador de TI les pot veure"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Entesos"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 5fa9154..82bbd12 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -46,12 +46,12 @@
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"šířka %1$d, výška %2$d"</string>
     <string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> widget"</string>
     <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, šířka %2$d, výška %3$d"</string>
-    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Pokud chcete widgetem pohybovat po ploše, podržte ho"</string>
+    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Pokud chcete widgetem pohybovat po ploše, podržte ho."</string>
     <string name="add_to_home_screen" msgid="9168649446635919791">"Přidat na plochu"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> byl přidán na plochu"</string>
     <string name="suggested_widgets_header_title" msgid="1844314680798145222">"Návrhy"</string>
     <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Nejdůležitější aplikace"</string>
-    <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Zprávy a časopisy"</string>
+    <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Noviny a časopisy"</string>
     <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Zábava"</string>
     <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Sociální sítě"</string>
     <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Návrhy pro vás"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Dotazu „<xliff:g id="QUERY">%1$s</xliff:g>“ neodpovídají žádné aplikace"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikace"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Všechny aplikace"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Seznam aplikací"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Oznámení"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Klepnutím a podržením přesunete zkratku."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Dvojitým klepnutím a podržením přesunete zkratku, případně použijte vlastní akce."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Strana %1$d z %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Plocha %1$d z %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nová stránka plochy"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Složka otevřena, rozměry <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Klepnutím složku zavřete"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Klepnutím změnu názvu uložíte"</string>
@@ -123,8 +128,9 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Složka přejmenována na <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Složka: <xliff:g id="NAME">%1$s</xliff:g>, počet položek: <xliff:g id="SIZE">%2$d</xliff:g>"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Složka: <xliff:g id="NAME">%1$s</xliff:g>, počet položek: <xliff:g id="SIZE">%2$d</xliff:g> nebo více"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Nepojmenovaná složka"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Dvojice aplikací: <xliff:g id="APP1">%1$s</xliff:g> a <xliff:g id="APP2">%2$s</xliff:g>"</string>
-    <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Tapeta a styl"</string>
+    <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Tapety a styl"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Upravit plochu"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"Nastavení plochy"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Zakázáno administrátorem"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Seznam widgetů zavřen"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Přidat na plochu"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Přesunout položku sem"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Položka byla přidána na plochu"</string>
     <string name="item_removed" msgid="851119963877842327">"Položka byla odstraněna"</string>
     <string name="undo" msgid="4151576204245173321">"Zpět"</string>
     <string name="action_move" msgid="4339390619886385032">"Přesunout položku"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Zavřít"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Osobní"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Pracovní"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Pracovní profil"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Pracovní aplikace jsou označené a váš administrátor IT je vidí"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Rozumím"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 16e3473..1485f75 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Der blev ikke fundet nogen apps, som matcher \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Alle apps"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Liste over apps"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifikationer"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Hold en genvej nede for at flytte den."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Tryk to gange, og hold en genvej nede for at flytte den eller bruge tilpassede handlinger."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Side %1$d ud af %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Startskærm %1$d ud af %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Ny startskærm"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Mappen er åben, <xliff:g id="WIDTH">%1$d</xliff:g> gange <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Tryk for at lukke mappen"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tryk for at gemme omdøbningen"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Mappen er omdøbt til <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Mappe: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elementer"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Mappe: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> eller flere elementer"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Unavngiven mappe"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Appsammenknytning: <xliff:g id="APP1">%1$s</xliff:g> og <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Baggrund og stil"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Rediger startskærm"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Listen med widgets blev lukket"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Føj til startskærm"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Flyt elementet hertil"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Elementet er føjet til startskærmen"</string>
     <string name="item_removed" msgid="851119963877842327">"Elementet er fjernet"</string>
     <string name="undo" msgid="4151576204245173321">"Fortryd"</string>
     <string name="action_move" msgid="4339390619886385032">"Flyt element"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Luk"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personlig"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Arbejde"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Arbejdsprofil"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Arbejdsapps har badges og kan ses af din it-administrator"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index cb64821..4a7fb7f 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Keine Apps für \"<xliff:g id="QUERY">%1$s</xliff:g>\" gefunden"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Alle Apps"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Liste der Apps"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Benachrichtigungen"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Zum Verschieben einer Verknüpfung gedrückt halten"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Doppeltippen und halten, um eine Verknüpfung zu bewegen oder benutzerdefinierte Aktionen zu nutzen."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Seite %1$d von %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Startbildschirm %1$d von %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Neue Startbildschirmseite"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Ordner geöffnet, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Ordner zum Schließen antippen"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Neuen Namen zum Speichern antippen"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Ordner umbenannt in <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Ordner: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> Elemente"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Ordner: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> oder mehr Elemente"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Unbenannter Ordner"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"App-Paar: <xliff:g id="APP1">%1$s</xliff:g> und <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Hintergrund &amp; Stil"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Startbildschirm bearbeiten"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Widgetliste geschlossen"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Zum Startbildschirm hinzufügen"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Element hierhin verschieben"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Element zum Startbildschirm hinzugefügt"</string>
     <string name="item_removed" msgid="851119963877842327">"Element entfernt"</string>
     <string name="undo" msgid="4151576204245173321">"Rückgängig"</string>
     <string name="action_move" msgid="4339390619886385032">"Element verschieben"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Schließen"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Privat"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Geschäftlich"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Arbeitsprofil"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Geschäftliche Apps sind gekennzeichnet und für deinen IT-Administrator sichtbar"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 672ba05..de2bdc7 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Δεν βρέθηκαν εφαρμογές αντιστοίχισης για \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"Εφαρμογή"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Όλες οι εφαρμογές"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Λίστα εφαρμογών"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Ειδοποιήσεις"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Πατήστε παρατεταμένα για μετακίνηση συντόμευσης."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Πατήστε δύο φορές παρατεταμένα για μετακίνηση συντόμευσης ή χρήση προσαρμοσμένων ενεργειών."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Σελίδα %1$d από %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Αρχική οθόνη %1$d από %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Νέα σελίδα αρχικής οθόνης"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Άνοιγμα φακέλου, <xliff:g id="WIDTH">%1$d</xliff:g> επί <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Πατήστε για να κλείσετε το φάκελο"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Πατήστε για να αποθηκεύσετε τη νέα ονομασία"</string>
@@ -123,10 +128,11 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Ο φάκελος μετονομάστηκε σε <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Φάκελος: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> στοιχεία"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Φάκελος: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ή περισσότερα στοιχεία"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Φάκελος χωρίς όνομα"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Ζεύγος εφαρμογών: <xliff:g id="APP1">%1$s</xliff:g> και <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Ταπετσαρία και στιλ"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Επεξεργασία αρχικής οθόνης"</string>
-    <string name="settings_button_text" msgid="8873672322605444408">"Ρυθμίσεις Αρχ. Οθ."</string>
+    <string name="settings_button_text" msgid="8873672322605444408">"Ρυθμ. Αρχικής οθόνης"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Απενεργοποιήθηκε από τον διαχειριστή σας"</string>
     <string name="allow_rotation_title" msgid="7222049633713050106">"Να επιτρέπεται η περιστροφή της αρχικής οθόνης"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Όταν το τηλέφωνο περιστρέφεται"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Η λίστα γραφικών στοιχείων έκλεισε"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Προσθήκη στην αρχική οθόνη"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Μετακίνηση στοιχείου εδώ"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Το στοιχείο προστέθηκε στην αρχική οθόνη"</string>
     <string name="item_removed" msgid="851119963877842327">"Το στοιχείο καταργήθηκε"</string>
     <string name="undo" msgid="4151576204245173321">"Αναίρεση"</string>
     <string name="action_move" msgid="4339390619886385032">"Μετακίνηση στοιχείου"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Κλείσιμο"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Προσωπικές"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Εργασίας"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Προφίλ εργασίας"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Οι εφαρμογές εργασιών φέρουν σήμα και είναι ορατές στον διαχειριστή IT σας"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Το κατάλαβα"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index a4f88de..7ce9c0c 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"No apps found matching \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"All apps"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Apps list"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Touch &amp; hold to move a shortcut."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Double-tap &amp; hold to move a shortcut or use custom actions."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d of %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Home screen %1$d of %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"New home screen page"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Active"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Minimised"</string>
     <string name="folder_opened" msgid="94695026776264709">"Folder opened, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Tap to close folder"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tap to save rename"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> or more items"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Unnamed folder"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"App pair: <xliff:g id="APP1">%1$s</xliff:g> and <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper and style"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Edit home screen"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Widgets list closed"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Add to home screen"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Item added to home screen"</string>
     <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
     <string name="undo" msgid="4151576204245173321">"Undo"</string>
     <string name="action_move" msgid="4339390619886385032">"Move item"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Close"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personal"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Work"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Work profile"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Work apps are badged and visible to your IT admin"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 363bb4b..71fd15a 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"No apps found matching \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"All apps"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Apps list"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Touch and hold to move a shortcut."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Double-tap and hold to move a shortcut or use custom actions."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d of %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Home screen %1$d of %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"New home screen page"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Active"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Minimized"</string>
     <string name="folder_opened" msgid="94695026776264709">"Folder opened, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Tap to close folder"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tap to save rename"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> or more items"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Unnamed folder"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"App pair: <xliff:g id="APP1">%1$s</xliff:g> and <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper and style"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Edit Home Screen"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Widgets list closed"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Add to home screen"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Item added to home screen"</string>
     <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
     <string name="undo" msgid="4151576204245173321">"Undo"</string>
     <string name="action_move" msgid="4339390619886385032">"Move item"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Close"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personal"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Work"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Work profile"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Work apps are badged and visible to your IT admin"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Got it"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index a4f88de..7ce9c0c 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"No apps found matching \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"All apps"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Apps list"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Touch &amp; hold to move a shortcut."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Double-tap &amp; hold to move a shortcut or use custom actions."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d of %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Home screen %1$d of %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"New home screen page"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Active"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Minimised"</string>
     <string name="folder_opened" msgid="94695026776264709">"Folder opened, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Tap to close folder"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tap to save rename"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> or more items"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Unnamed folder"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"App pair: <xliff:g id="APP1">%1$s</xliff:g> and <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper and style"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Edit home screen"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Widgets list closed"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Add to home screen"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Item added to home screen"</string>
     <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
     <string name="undo" msgid="4151576204245173321">"Undo"</string>
     <string name="action_move" msgid="4339390619886385032">"Move item"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Close"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personal"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Work"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Work profile"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Work apps are badged and visible to your IT admin"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index a4f88de..7ce9c0c 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"No apps found matching \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"All apps"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Apps list"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Touch &amp; hold to move a shortcut."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Double-tap &amp; hold to move a shortcut or use custom actions."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d of %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Home screen %1$d of %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"New home screen page"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Active"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Minimised"</string>
     <string name="folder_opened" msgid="94695026776264709">"Folder opened, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Tap to close folder"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tap to save rename"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Folder renamed to <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> or more items"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Unnamed folder"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"App pair: <xliff:g id="APP1">%1$s</xliff:g> and <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper and style"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Edit home screen"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Widgets list closed"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Add to home screen"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Item added to home screen"</string>
     <string name="item_removed" msgid="851119963877842327">"Item removed"</string>
     <string name="undo" msgid="4151576204245173321">"Undo"</string>
     <string name="action_move" msgid="4339390619886385032">"Move item"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Close"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personal"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Work"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Work profile"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Work apps are badged and visible to your IT admin"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 0344113..ef5d483 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"No hay apps que coincidan con \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Todas las apps"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Lista de apps"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notificaciones"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Mantén presionado para mover un acceso directo."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Presiona dos veces y mantén presionado para mover un acceso directo o usar acciones personalizadas."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Página %1$d de %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Pantalla principal %1$d de %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nueva página en la pantalla principal"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Activo"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Minimizado"</string>
     <string name="folder_opened" msgid="94695026776264709">"Carpeta abierta, <xliff:g id="WIDTH">%1$d</xliff:g> por <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Presiona para cerrar la carpeta"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Presiona para guardar el cambio de nombre"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"El nombre de la carpeta se cambió a <xliff:g id="NAME">%1$s</xliff:g>."</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elementos"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> o más elementos"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Carpeta sin nombre"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Vinculación de apps: <xliff:g id="APP1">%1$s</xliff:g> y <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Fondo de pantalla y estilo"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Editar pantalla principal"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Se cerró la lista de widgets"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Agregar a pantalla principal"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Mover elemento aquí"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Se agregó el elemento a la pantalla principal."</string>
     <string name="item_removed" msgid="851119963877842327">"Se eliminó el elemento."</string>
     <string name="undo" msgid="4151576204245173321">"Deshacer"</string>
     <string name="action_move" msgid="4339390619886385032">"Mover elemento"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Cerrar"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personal"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Trabajo"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de trabajo"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Las apps de trabajo tienen una insignia y el administrador de TI las puede ver"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Entendido"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index fc34acf..a1ed2ed 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"No se han encontrado aplicaciones que contengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"Aplicación"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Todas las aplicaciones"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Lista de aplicaciones"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notificaciones"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Mantén pulsado un acceso directo para moverlo."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Toca dos veces y mantén pulsado un acceso directo para moverlo o usar acciones personalizadas."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Página %1$d de %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Pantalla de inicio %1$d de %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nueva página de pantalla de inicio"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Carpeta abierta, <xliff:g id="WIDTH">%1$d</xliff:g> por <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Toca para cerrar la carpeta"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Toca para guardar el nuevo nombre"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Se ha cambiado el nombre de la carpeta a <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="SIZE">%2$d</xliff:g> elementos)"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Carpeta: <xliff:g id="NAME">%1$s</xliff:g> (<xliff:g id="SIZE">%2$d</xliff:g> o más elementos)"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Carpeta sin nombre"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Aplicaciones emparejadas: <xliff:g id="APP1">%1$s</xliff:g> y <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Fondo de pantalla y estilo"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Editar pantalla de inicio"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Lista de widgets cerrada"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Añadir a pantalla de inicio"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Mover elemento aquí"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Elemento añadido a la pantalla de inicio"</string>
     <string name="item_removed" msgid="851119963877842327">"Elemento quitado"</string>
     <string name="undo" msgid="4151576204245173321">"Deshacer"</string>
     <string name="action_move" msgid="4339390619886385032">"Mover elemento"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Cerrar"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personal"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Trabajo"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de trabajo"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Las aplicaciones de trabajo tienen una insignia, y tu administrador de TI las puede ver"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Entendido"</string>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 5fc3b82..5300ec8 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -46,7 +46,7 @@
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d lai ja %2$d kõrge"</string>
     <string name="widget_preview_context_description" msgid="9045841361655787574">"Vidin <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Vidin <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d lai ja %3$d kõrge"</string>
-    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Vidina teisaldamiseks avakuval puudutage vidinat pikalt"</string>
+    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Vidina teisaldamiseks avakuval puudutage vidinat pikalt."</string>
     <string name="add_to_home_screen" msgid="9168649446635919791">"Lisa avakuvale"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Vidin <xliff:g id="WIDGET_NAME">%1$s</xliff:g> lisati avakuvale"</string>
     <string name="suggested_widgets_header_title" msgid="1844314680798145222">"Soovitused"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Päringule „<xliff:g id="QUERY">%1$s</xliff:g>” ei vastanud ükski rakendus"</string>
     <string name="label_application" msgid="8531721983832654978">"Rakendus"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Kõik rakendused"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Rakenduste loend"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Märguanded"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Otsetee teisaldamiseks puudutage ja hoidke all."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Otsetee teisaldamiseks või kohandatud toimingute kasutamiseks topeltpuudutage ja hoidke all."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Leht %1$d/%2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Avakuva %1$d/%2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Uus avakuva leht"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Kaust on avatud, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Puudutage kausta sulgemiseks"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Puudutage ümbernimetamise salvestamiseks"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Kausta uus nimi: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Kaust: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> üksust"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Kaust: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> või rohkem üksust"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Nimetu kaust"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Rakendusepaar: <xliff:g id="APP1">%1$s</xliff:g> ja <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Taustapilt ja stiil"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Muuda avaekraani"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Vidinate loend on suletud"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Lisa avakuvale"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Teisalda üksus siia"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Üksus lisati avaekraanile"</string>
     <string name="item_removed" msgid="851119963877842327">"Üksus eemaldati"</string>
     <string name="undo" msgid="4151576204245173321">"Võta tagasi"</string>
     <string name="action_move" msgid="4339390619886385032">"Teisalda üksus"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Sule"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Isiklik"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Töö"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Tööprofiil"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Töörakendustel on märk ja need on teie IT-administraatorile nähtavad"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Selge"</string>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 99f127a..013c88e 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Ez da aurkitu \"<xliff:g id="QUERY">%1$s</xliff:g>\" bilaketaren emaitzarik"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikazioa"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Aplikazio guztiak"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Aplikazioen zerrenda"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Jakinarazpenak"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Eduki sakatuta lasterbide bat mugitzeko."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Sakatu birritan eta eduki sakatuta lasterbide bat mugitzeko edo ekintza pertsonalizatuak erabiltzeko."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%1$d/%2$d orria"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"%1$d/%2$d orri nagusi"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Orri nagusiaren orri berria"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Karpeta ireki da: <xliff:g id="WIDTH">%1$d</xliff:g> × <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Karpeta ixteko, sakatu hau"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Izen berria gordetzeko, sakatu hau"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Karpetari <xliff:g id="NAME">%1$s</xliff:g> izena eman zaio"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"<xliff:g id="NAME">%1$s</xliff:g> karpeta (<xliff:g id="SIZE">%2$d</xliff:g> elementu)"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"<xliff:g id="NAME">%1$s</xliff:g> karpeta (<xliff:g id="SIZE">%2$d</xliff:g> elementu edo gehiago)"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Izenik gabeko karpeta"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Aplikazio parea: <xliff:g id="APP1">%1$s</xliff:g> eta <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Horma-papera eta estiloa"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Editatu orri nagusia"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Itxi da widget-zerrenda"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Gehitu orri nagusian"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Ekarri elementua hona"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Gehitu da elementua orri nagusian"</string>
     <string name="item_removed" msgid="851119963877842327">"Kendu da elementua"</string>
     <string name="undo" msgid="4151576204245173321">"Desegin"</string>
     <string name="action_move" msgid="4339390619886385032">"Mugitu elementua"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Itxi"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Pertsonalak"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Lanekoak"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Laneko profila"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Laneko aplikazioek bereizgarriak dituzte, eta IKT saileko administratzaileak ikus ditzake"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Ados"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index aa6f378..578d35d 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -64,8 +64,8 @@
     <string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"پاک کردن نوشتار از چارگوش جستجو"</string>
     <string name="no_widgets_available" msgid="4337693382501046170">"ابزاره و میان‌بری دردسترس نیست"</string>
     <string name="no_search_results" msgid="3787956167293097509">"هیچ ابزاره یا میان‌بری پیدا نشد"</string>
-    <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"ابزاره‌های شخصی"</string>
-    <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ابزاره‌های کاری"</string>
+    <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"شخصی"</string>
+    <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"کاری"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"مکالمه‌ها"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"یادداشت‌برداری"</string>
     <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"نشان دادن دکمه افزودن"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"هیچ برنامه‌ای در مطابقت با «<xliff:g id="QUERY">%1$s</xliff:g>» پیدا نشد"</string>
     <string name="label_application" msgid="8531721983832654978">"برنامه"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"همه برنامه‌ها"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"فهرست برنامه‌ها"</string>
     <string name="notifications_header" msgid="1404149926117359025">"اعلان‌ها"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"برای جابه‌جا کردن میان‌بر، لمس کنید و نگه دارید."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"برای جابه‌جا کردن میان‌بر یا استفاده از کنش‌های سفارشی، دو تک‌ضرب بزنید و نگه دارید."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"‏صفحه %1$d از %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"‏صفحه اصلی %1$d از %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"صفحه اصلی جدید"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"پوشه باز شده، <xliff:g id="WIDTH">%1$d</xliff:g> در <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"برای بستن پوشه، تک‌ضرب بزنید"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"برای ذخیره تغییر نام، تک‌ضرب بزنید"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"نام پوشه به <xliff:g id="NAME">%1$s</xliff:g> تغییر کرد"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"پوشه: <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="SIZE">%2$d</xliff:g> مورد"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"پوشه: <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="SIZE">%2$d</xliff:g> مورد یا بیشتر"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"پوشه بی‌نام"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"جفت برنامه: <xliff:g id="APP1">%1$s</xliff:g> و <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"کاغذدیواری و سبک"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"ویرایش «صفحه اصلی»"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"فهرست ابزاره‌ها بسته شد"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"افزودن به صفحه اصلی"</string>
     <string name="action_move_here" msgid="2170188780612570250">"انتقال مورد به اینجا"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"مورد به صفحه اصلی اضافه شد"</string>
     <string name="item_removed" msgid="851119963877842327">"مورد حذف شد"</string>
     <string name="undo" msgid="4151576204245173321">"واگرد"</string>
     <string name="action_move" msgid="4339390619886385032">"انتقال مورد"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"بستن"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"شخصی"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"کاری"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"نمایه کاری"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"برنامه‌های کاری نشان‌دار هستند و سرپرست فناوری اطلاعات می‌تواند آن‌ها را ببیند"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"متوجهم"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 6beb593..29d04b2 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"<xliff:g id="QUERY">%1$s</xliff:g> ei palauttanut sovelluksia."</string>
     <string name="label_application" msgid="8531721983832654978">"Sovellus"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Kaikki sovellukset"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Sovellusluettelo"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Ilmoitukset"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Kosketa pitkään, niin voit siirtää pikakuvaketta."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Kaksoisnapauta ja paina pitkään, niin voit siirtää pikakuvaketta tai käyttää muokattuja toimintoja."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Sivu %1$d / %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Aloitusruutu %1$d/%2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Uusi aloitusnäytön sivu"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Kansio avattu, koko <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Sulje kansio koskettamalla."</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tallenna uusi nimi koskettamalla."</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Kansion nimeksi vaihdettiin <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Kansio: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> kohdetta"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Kansio: <xliff:g id="NAME">%1$s</xliff:g>, ainakin <xliff:g id="SIZE">%2$d</xliff:g> kohdetta"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Nimeämätön kansio"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Sovelluspari: <xliff:g id="APP1">%1$s</xliff:g> ja <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Taustakuva ja tyyli"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Muokkaa aloitusnäyttöä"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Widget-luettelo suljettu"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Lisää aloitusnäytölle"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Siirrä kohde tänne"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Kohde lisättiin aloitusnäytölle."</string>
     <string name="item_removed" msgid="851119963877842327">"Kohde poistettiin"</string>
     <string name="undo" msgid="4151576204245173321">"Kumoa"</string>
     <string name="action_move" msgid="4339390619886385032">"Siirrä kohde"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Sulje"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Henkilökohtaiset"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Työsovellukset"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Työprofiili"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Työsovellukset on merkitty sellaisiksi ja näkyvät IT-järjestelmänvalvojille"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Selvä"</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 696d8af..eb9360f 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Aucune appli trouvée correspondant à « <xliff:g id="QUERY">%1$s</xliff:g> »"</string>
     <string name="label_application" msgid="8531721983832654978">"Appli"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Toutes les applis"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Liste des applis"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Maintenez le doigt sur un raccourci pour le déplacer."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Touchez deux fois un raccourci et maintenez le doigt dessus pour le déplacer ou utiliser des actions personnalisées."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d sur %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Écran d\'accueil %1$d sur %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nouvelle page d\'écran d\'accueil"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Dossier ouvert, <xliff:g id="WIDTH">%1$d</xliff:g> par <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Touchez pour fermer le dossier"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Touchez pour enregistrer le nouveau nom"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Nouveau nom du dossier : <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Dossier : <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> élément(s)"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Dossier : <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> éléments ou plus"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Dossier sans nom"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Paire d\'applis : <xliff:g id="APP1">%1$s</xliff:g> et <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Fond d\'écran et style"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Modifier l\'écran d\'accueil"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Liste des widgets fermée"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Ajouter à l\'écran d\'accueil"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Déplacer l\'élément ici"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Élément ajouté à l\'écran d\'accueil"</string>
     <string name="item_removed" msgid="851119963877842327">"Élément retiré"</string>
     <string name="undo" msgid="4151576204245173321">"Annuler"</string>
     <string name="action_move" msgid="4339390619886385032">"Déplacer l\'élément"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Fermer"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personnel"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Travail"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil professionnel"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Les applis professionnelles sont indiquées par un badge et elles sont visibles pour votre administrateur informatique"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index c870afa..fea7ff1 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Aucune application ne correspond à la requête \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"Application"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Toutes les applis"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Liste des applis"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifications"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Appuyez de manière prolongée pour déplacer un raccourci."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Appuyez deux fois et maintenez la pression pour déplacer un raccourci ou utiliser les actions personnalisées."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Page %1$d sur %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Écran d\'accueil %1$d sur %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nouvelle page d\'écran d\'accueil"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Dossier ouvert, <xliff:g id="WIDTH">%1$d</xliff:g> par <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Appuyez pour fermer le dossier."</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Appuyez pour enregistrer le nouveau nom du dossier."</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Nouveau nom du dossier : <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Dossier : <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> éléments"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Dossier : <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> éléments ou plus"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Dossier sans nom"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Paire d\'applications : <xliff:g id="APP1">%1$s</xliff:g> et <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Fond d\'écran et style"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Modifier l\'écran d\'accueil"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"La liste des widgets est fermée"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Ajouter à l\'écran d\'accueil"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Déplacer l\'élément ici"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"L\'élément a bien été ajouté à l\'écran d\'accueil."</string>
     <string name="item_removed" msgid="851119963877842327">"Élément supprimé"</string>
     <string name="undo" msgid="4151576204245173321">"Annuler"</string>
     <string name="action_move" msgid="4339390619886385032">"Déplacer l\'élément"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Fermer"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personnel"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Professionnel"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil professionnel"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Les applis professionnelles sont identifiées par un badge et votre administrateur informatique peut les voir"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 5bcf2fd..513083a 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Non se atoparon aplicacións que coincidan con \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"Aplicación"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Todas as aplicacións"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Lista de aplicacións"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notificacións"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Mantén premido un atallo para movelo."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Toca dúas veces un atallo e manteno premido para movelo ou utiliza accións personalizadas."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Páxina %1$d de %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Pantalla de inicio %1$d de %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nova páxina da pantalla de inicio"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Abriuse o cartafol, <xliff:g id="WIDTH">%1$d</xliff:g> por <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Toca fóra para pechar o cartafol"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Toca fóra para cambiar o nome do cartafol"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"O cartafol cambiou o nome a <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Cartafol: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elementos"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Cartafol: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elementos ou máis"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Cartafol sen nome"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Emparellamento de aplicacións: <xliff:g id="APP1">%1$s</xliff:g> e <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Estilo e fondo de pantalla"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Editar pantalla de inicio"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Pechouse a lista de widgets"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Engadir á pantalla de inicio"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Mover elemento aquí"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Engadiuse o elemento á pantalla de inicio"</string>
     <string name="item_removed" msgid="851119963877842327">"Quitouse o elemento"</string>
     <string name="undo" msgid="4151576204245173321">"Desfacer"</string>
     <string name="action_move" msgid="4339390619886385032">"Mover elemento"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Pechar"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Persoal"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Traballo"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de traballo"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"O administrador de TI pode ver as aplicacións do traballo e engadirlles indicadores"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Entendido"</string>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 1480ea9..d31f291 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -60,7 +60,7 @@
     <string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# શૉર્ટકટ}one{# શૉર્ટકટ}other{# શૉર્ટકટ}}"</string>
     <string name="widgets_and_shortcuts_count" msgid="7209136747878365116">"<xliff:g id="WIDGETS_COUNT">%1$s</xliff:g>, <xliff:g id="SHORTCUTS_COUNT">%2$s</xliff:g>"</string>
     <string name="widget_button_text" msgid="2880537293434387943">"વિજેટ"</string>
-    <string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"શોધ"</string>
+    <string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"શોધો"</string>
     <string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"શોધ બૉક્સમાંથી ટેક્સ્ટ સાફ કરો"</string>
     <string name="no_widgets_available" msgid="4337693382501046170">"વિજેટ અને શૉર્ટકટ ઉપલબ્ધ નથી"</string>
     <string name="no_search_results" msgid="3787956167293097509">"કોઈ વિજેટ અથવા શૉર્ટકટ મળ્યા નથી"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"થી મેળ ખાતી કોઈ ઍપ્લિકેશનો મળી નથી"</string>
     <string name="label_application" msgid="8531721983832654978">"ઍપ"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"બધી ઍપ"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"ઍપની સૂચિ"</string>
     <string name="notifications_header" msgid="1404149926117359025">"નોટિફિકેશન"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"શૉર્ટકટ ખસેડવા ટચ કરીને થોડી વાર દબાવી રાખો."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"શૉર્ટકટ ખસેડવા બે વાર ટૅપ કરીને દબાવી રાખો અથવા કસ્ટમ ક્રિયાઓનો ઉપયોગ કરો."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%2$d માંથી %1$d પૃષ્ઠ"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"%2$d માંથી %1$d હોમ સ્ક્રીન"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"નવું હોમ સ્ક્રીન પૃષ્ઠ"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"<xliff:g id="WIDTH">%1$d</xliff:g> બાય <xliff:g id="HEIGHT">%2$d</xliff:g> નું ફોલ્ડર ખોલ્યું"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"ફોલ્ડર બંધ કરવા માટે ટૅપ કરો"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"નામ બદલવાનું સાચવવા માટે ટૅપ કરો"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"ફોલ્ડરનું નામ બદલીને <xliff:g id="NAME">%1$s</xliff:g> કર્યું"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"ફોલ્ડર: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> આઇટમ"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"ફોલ્ડર: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> કે વધુ આઇટમ"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"અનામાંકિત ફોલ્ડર"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"ઍપની જોડી: <xliff:g id="APP1">%1$s</xliff:g> અને <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"વૉલપેપર અને સ્ટાઇલ"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"હોમ સ્ક્રીનમાં ફેરફાર કરો"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"વિજેટની સૂચિ બંધ કરવામાં આવી છે"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"હોમ સ્ક્રીનમાં ઉમેરો"</string>
     <string name="action_move_here" msgid="2170188780612570250">"આઇટમ અહીં ખસેડો"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"હોમ સ્ક્રીનમાં આઇટમ ઉમેરી"</string>
     <string name="item_removed" msgid="851119963877842327">"આઇટમ કાઢી નાખી"</string>
     <string name="undo" msgid="4151576204245173321">"રદ કરો"</string>
     <string name="action_move" msgid="4339390619886385032">"આઇટમ ખસેડો"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"બંધ કરો"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"વ્યક્તિગત ઍપ"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"ઑફિસની ઍપ"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"ઑફિસની પ્રોફાઇલ"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"ઑફિસની ઍપને બૅજ આપેલા હોય છે અને તમારા IT ઍડમિન તેમને જોઈ શકે છે"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"સમજાઈ ગયું"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 32a2a96..360ef33 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -50,7 +50,7 @@
     <string name="add_to_home_screen" msgid="9168649446635919791">"होम स्क्रीन पर जोड़ें"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> विजेट को होम स्क्रीन पर जोड़ा गया"</string>
     <string name="suggested_widgets_header_title" msgid="1844314680798145222">"सुझाव"</string>
-    <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ज़रूरी ऐप्लिकेशन"</string>
+    <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"अहम जानकारी"</string>
     <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>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" से मिलता-जुलता कोई ऐप्लिकेशन नहीं मिला"</string>
     <string name="label_application" msgid="8531721983832654978">"ऐप्लिकेशन"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"सभी ऐप्लिकेशन"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"ऐप्लिकेशन की सूची"</string>
     <string name="notifications_header" msgid="1404149926117359025">"सूचनाएं"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"किसी शॉर्टकट को एक से दूसरी जगह ले जाने के लिए, उसे दबाकर रखें."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"किसी शॉर्टकट को एक से दूसरी जगह ले जाने के लिए, उस पर दो बार टैप करके दबाकर रखें या पसंद के मुताबिक कार्रवाइयां इस्तेमाल करें."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"पेज %2$d में से %1$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"होम स्क्रीन %2$d में से %1$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"नया होम स्‍क्रीन पेज"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"फ़ोल्डर खोला गया, <xliff:g id="WIDTH">%1$d</xliff:g> गुणा <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"फ़ोल्डर बंद करने के लिए टैप करें"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"नाम बदलना सहेजने के लिए टैप करें"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"फ़ोल्डर का नाम बदलकर <xliff:g id="NAME">%1$s</xliff:g> किया गया"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"फ़ोल्डर: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> आइटम"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"फ़ोल्डर: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> या इससे ज़्यादा आइटम"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"बिना नाम का फ़ोल्डर"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"साथ में इस्तेमाल किए जा सकने वाले ऐप्लिकेशन: <xliff:g id="APP1">%1$s</xliff:g> और <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"वॉलपेपर और स्टाइल"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"होम स्क्रीन में बदलाव करें"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"विजेट की सूची बंद हो गई है"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"होम स्क्रीन पर जोड़ें"</string>
     <string name="action_move_here" msgid="2170188780612570250">"आइटम यहां ले जाएं"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"होम स्क्रीन में आइटम जोड़ा गया"</string>
     <string name="item_removed" msgid="851119963877842327">"आइटम हटाया गया"</string>
     <string name="undo" msgid="4151576204245173321">"पहले जैसा करें"</string>
     <string name="action_move" msgid="4339390619886385032">"आइटम ले जाएं"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"बंद करें"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"निजी ऐप्लिकेशन"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"वर्क ऐप्लिकेशन"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"वर्क प्रोफ़ाइल"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"वर्क ऐप्लिकेशन बैज किए गए हैं. आईटी एडमिन इन्हें देख सकता है"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"ठीक है"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index 726efe8..db82c46 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nema aplikacija podudarnih s upitom \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikacija"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Sve aplikacije"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Popis aplikacija"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Obavijesti"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Dodirnite i zadržite da biste premjestili prečac."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Dvaput dodirnite i zadržite pritisak da biste premjestili prečac ili upotrijebite prilagođene radnje."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Stranica %1$d od %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Početni zaslon %1$d od %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nova stranica početnog zaslona"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Mapa je otvorena, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Dodirnite da biste zatvorili mapu"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Dodirnite da biste spremili promijenjeni naziv"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Mapa je preimenovana u <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Mapa: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> stavke"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Mapa: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ili više stavki"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Neimenovana mapa"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Par aplikacija: <xliff:g id="APP1">%1$s</xliff:g> i <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Pozadina i stil"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Uredi početni zaslon"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Popis widgeta zatvoren"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Dodajte na početni zaslon"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Premjesti stavku ovdje"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Stavka je dodana na početni zaslon"</string>
     <string name="item_removed" msgid="851119963877842327">"Stavka je uklonjena"</string>
     <string name="undo" msgid="4151576204245173321">"Poništi"</string>
     <string name="action_move" msgid="4339390619886385032">"Premještanje stavke"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Zatvori"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Osobno"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Posao"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Poslovni profil"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Poslovne su aplikacije označene i vidljive vašem IT administratoru"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Shvaćam"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index b85d3df..04e43a7 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -54,7 +54,7 @@
     <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Újságok és magazinok"</string>
     <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Szórakozás"</string>
     <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"Közösségi"</string>
-    <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Neked javasolt"</string>
+    <string name="others_widget_recommendation_category_label" msgid="5555987036267226245">"Javaslatok"</string>
     <string name="widget_picker_right_pane_accessibility_title" msgid="1673313931455067502">"A <xliff:g id="SELECTED_HEADER">%1$s</xliff:g>-modulok a jobb, a kereső és a beállítások pedig a bal oldalon találhatók"</string>
     <string name="widgets_count" msgid="6467746476364652096">"{count,plural, =1{# modul}other{# modul}}"</string>
     <string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{# gyorsparancs}other{# gyorsparancs}}"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nem található alkalmazás a(z) „<xliff:g id="QUERY">%1$s</xliff:g>” lekérdezésre"</string>
     <string name="label_application" msgid="8531721983832654978">"Alkalmazás"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Összes alkalmazás"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Alkalmazások listája"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Értesítések"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Tartsa lenyomva a parancsikont az áthelyezéshez."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Parancsikon áthelyezéséhez koppintson duplán, és tartsa nyomva az ujját, vagy használjon egyéni műveleteket."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%2$d/%1$d. oldal"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"%2$d/%1$d. kezdőképernyő"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Új kezdőképernyő oldal"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Mappa megnyitva – szélesség: <xliff:g id="WIDTH">%1$d</xliff:g>; magasság: <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Érintse meg a mappa bezárásához"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Koppintson ide az átnevezés mentéséhez"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"A mappa új neve: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Mappa: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elem"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Mappa: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> vagy több elem"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Névtelen mappa"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Alkalmazáspár: <xliff:g id="APP1">%1$s</xliff:g> és <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Háttérkép és stílus"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Kezdőképernyő szerkesztése"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Widgetlista bezárva"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Hozzáadás a kezdőképernyőhöz"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Elem áthelyezése ide"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Elem hozzáadva a kezdőképernyőhöz"</string>
     <string name="item_removed" msgid="851119963877842327">"Elem eltávolítva"</string>
     <string name="undo" msgid="4151576204245173321">"Mégse"</string>
     <string name="action_move" msgid="4339390619886385032">"Elem mozgatása"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Bezárás"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Személyes"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Munkahelyi"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Munkaprofil"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"A munkahelyi alkalmazások jelvénnyel vannak megjelölve, és ezeket láthatja a rendszergazda"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Értem"</string>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 36e89b4..7d805a6 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"«<xliff:g id="QUERY">%1$s</xliff:g>» հարցմանը համապատասխանող հավելվածներ չեն գտնվել"</string>
     <string name="label_application" msgid="8531721983832654978">"Հավելված"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Բոլոր հավելվածները"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Հավելվածների ցանկ"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Ծանուցումներ"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Հպեք և պահեք՝ դյուրանցում տեղափոխելու համար։"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Կրկնակի հպեք և պահեք՝ դյուրանցում տեղափոխելու համար, կամ օգտվեք հատուկ գործողություններից։"</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Էջ %1$d՝ %2$d-ից"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Հիմնական էկրան %1$d` %2$d-ից"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Հիմնական էկրանի նոր էջ"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Ակտիվ է"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Նվազեցվել է"</string>
     <string name="folder_opened" msgid="94695026776264709">"Պանակը բաց է, <xliff:g id="WIDTH">%1$d</xliff:g>-ից <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Հպեք՝ պանակը փակելու համար"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Հպեք՝ նոր անվանումը պահելու համար"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Պանակը վերանվանվեց <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Պանակ՝ <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> տարր"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Պանակ՝ <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> կամ ավելի տարրեր"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Անանուն պանակ"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Հավելվածների զույգ՝ <xliff:g id="APP1">%1$s</xliff:g> և <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Պաստառ և ոճ"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Փոփոխել հիմնական էկրանը"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Վիջեթների ցանկը փակվեց"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Ավելացնել հիմնական էկրանին"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Տեղափոխել տարրն այստեղ"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Տարրն ավելացվեց հիմնական էկրանին"</string>
     <string name="item_removed" msgid="851119963877842327">"Տարրը հեռացվեց"</string>
     <string name="undo" msgid="4151576204245173321">"Հետարկել"</string>
     <string name="action_move" msgid="4339390619886385032">"Տեղափոխել տարրը"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Փակել"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Անձնական"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Աշխատանքային"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Աշխատանքային պրոֆիլ"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Աշխատանքային հավելվածները հատուկ նշանակ ունեն և տեսանելի են ՏՏ ադմինիստրատորին"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Եղավ"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 0d5a819..28a01ac 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Tidak ditemukan aplikasi yang cocok dengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikasi"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Semua aplikasi"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Daftar aplikasi"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifikasi"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Sentuh lama untuk memindahkan pintasan."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Ketuk dua kali &amp; tahan untuk memindahkan pintasan atau gunakan tindakan khusus."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Halaman %1$d dari %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Layar utama %1$d dari %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Halaman layar utama baru"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Folder dibuka, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Ketuk untuk menutup folder"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Ketuk untuk menyimpan ganti nama"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Folder diganti namanya menjadi <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> item"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> item atau lebih"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Folder tanpa nama"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Pasangan aplikasi: <xliff:g id="APP1">%1$s</xliff:g> dan <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper &amp; gaya"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Edit Layar Utama"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Daftar widget ditutup"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Tambahkan ke layar utama"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Pindahkan item ke sini"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Item ditambahkan ke layar utama"</string>
     <string name="item_removed" msgid="851119963877842327">"Item dihapus"</string>
     <string name="undo" msgid="4151576204245173321">"Urungkan"</string>
     <string name="action_move" msgid="4339390619886385032">"Pindahkan item"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Tutup"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Pribadi"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Kerja"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil kerja"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Aplikasi kerja diberi badge dan terlihat oleh admin IT"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Oke"</string>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index 8f3c604..a87df93 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Ekki fundust forrit sem samsvara „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
     <string name="label_application" msgid="8531721983832654978">"Forrit"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Öll forrit"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Forritalisti"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Tilkynningar"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Haltu fingri á flýtileið til að færa hana."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Ýttu tvisvar og haltu fingri á flýtileið til að færa hana eða notaðu sérsniðnar aðgerðir."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Síða %1$d af %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Heimaskjár %1$d af %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Ný síða á heimaskjá"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Virkt"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Minnkað"</string>
     <string name="folder_opened" msgid="94695026776264709">"Mappa opnuð, <xliff:g id="WIDTH">%1$d</xliff:g> sinnum <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Ýttu til að loka möppunni"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Ýttu til að vista breytt heiti"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Heiti möppu breytt í <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Mappa: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> atriði"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Mappa: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> eða fleiri atriði"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Mappa án heitis"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Forritapar: <xliff:g id="APP1">%1$s</xliff:g> og <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Veggfóður og stíll"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Breyta heimaskjá"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Græjulista lokað"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Bæta á heimaskjá"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Færa atriði hingað"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Atriði bætt á heimaskjáinn"</string>
     <string name="item_removed" msgid="851119963877842327">"Atriði fjarlægt"</string>
     <string name="undo" msgid="4151576204245173321">"Afturkalla"</string>
     <string name="action_move" msgid="4339390619886385032">"Færa atriði"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Loka"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Persónulegt"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Vinna"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Vinnusnið"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Vinnuforrit eru merkt og kerfisstjórinn getur séð þau"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Ég skil"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index c7fd68f..ffe7436 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -46,7 +46,7 @@
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d di larghezza per %2$d di altezza"</string>
     <string name="widget_preview_context_description" msgid="9045841361655787574">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d di larghezza per %3$d di altezza"</string>
-    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Tocca e tieni premuto il widget per spostarlo nella schermata Home"</string>
+    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Tieni premuto il widget per spostarlo nella schermata Home"</string>
     <string name="add_to_home_screen" msgid="9168649446635919791">"Aggiungi alla schermata Home"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widget <xliff:g id="WIDGET_NAME">%1$s</xliff:g> aggiunto alla schermata Home"</string>
     <string name="suggested_widgets_header_title" msgid="1844314680798145222">"Suggerimenti"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nessuna app trovata corrispondente a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Tutte le app"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Elenco di app"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notifiche"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Tocca e tieni premuto per spostare una scorciatoia."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Tocca due volte e tieni premuto per spostare una scorciatoia o per usare le azioni personalizzate."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Pagina %1$d di %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Schermata Home %1$d di %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nuova pagina Schermata Home"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Attiva"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Ridotta a icona"</string>
     <string name="folder_opened" msgid="94695026776264709">"Cartella aperta, <xliff:g id="WIDTH">%1$d</xliff:g> per <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Tocca per chiudere la cartella"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tocca per salvare il nuovo nome"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Nome della cartella sostituito con <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Cartella: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elementi"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Cartella: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> o più elementi"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Cartella senza nome"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Coppia di app: <xliff:g id="APP1">%1$s</xliff:g> and <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Sfondo e stile"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Modifica schermata Home"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Elenco di widget chiuso"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Aggiungi alla schermata Home"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Sposta elemento qui"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Elemento aggiunto alla schermata Home"</string>
     <string name="item_removed" msgid="851119963877842327">"Elemento rimosso"</string>
     <string name="undo" msgid="4151576204245173321">"Annulla"</string>
     <string name="action_move" msgid="4339390619886385032">"Sposta elemento"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Esci"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personali"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Lavoro"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Profilo di lavoro"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Le app di lavoro sono contrassegnate con un badge e visibili all\'amministratore IT"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 6dfbe68..dd496c8 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"לא נמצאו אפליקציות התואמות ל-\"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"אפליקציה"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"כל האפליקציות"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"רשימת האפליקציות"</string>
     <string name="notifications_header" msgid="1404149926117359025">"התראות"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"כדי להעביר קיצור דרך למקום אחר יש לגעת ולא להרפות."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"כדי להעביר קיצור דרך למקום אחר או להשתמש בפעולות מותאמות אישית\' יש ללחוץ פעמיים ולא להרפות."</string>
@@ -92,7 +93,7 @@
     <string name="all_apps_button_personal_label" msgid="1315764287305224468">"רשימת אפליקציות אישיות"</string>
     <string name="all_apps_button_work_label" msgid="7270707118948892488">"רשימת אפליקציות עבודה"</string>
     <string name="remove_drop_target_label" msgid="7812859488053230776">"הסרה"</string>
-    <string name="uninstall_drop_target_label" msgid="4722034217958379417">"להסרת התקנה"</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="uninstall_private_system_shortcut_label" msgid="8423460530441627982">"הסרת האפליקציה"</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"‏דף %1$d מתוך %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"‏מסך הבית %1$d מתוך %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"מסך הבית חדש"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"תיקייה פתוחה, <xliff:g id="WIDTH">%1$d</xliff:g> על <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"יש ללחוץ כדי לסגור את התיקייה"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"יש ללחוץ כדי לשמור שינוי שם"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"שם התיקייה שונה ל-<xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"תיקייה: <xliff:g id="NAME">%1$s</xliff:g>, מספר הפריטים: <xliff:g id="SIZE">%2$d</xliff:g>"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"תיקייה: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> פריטים או יותר"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"תיקייה ללא שם"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"צמד אפליקציות: <xliff:g id="APP1">%1$s</xliff:g> ו-<xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"טפט וסגנון"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"עריכה של מסך הבית"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"רשימת הווידג\'טים נסגרה"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"הוספה למסך הבית"</string>
     <string name="action_move_here" msgid="2170188780612570250">"העברת הפריט לכאן"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"הפריט הועבר אל מסך הבית"</string>
     <string name="item_removed" msgid="851119963877842327">"הפריט הוסר"</string>
     <string name="undo" msgid="4151576204245173321">"ביטול"</string>
     <string name="action_move" msgid="4339390619886385032">"העברת הפריט"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"סגירה"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"אישי"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"עבודה"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"פרופיל עבודה"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"‏האפליקציות לעבודה מתויגות ומוצגות למנהל ה-IT"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"הבנתי"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 9b012c5..9000081 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"「<xliff:g id="QUERY">%1$s</xliff:g>」に一致するアプリは見つかりませんでした"</string>
     <string name="label_application" msgid="8531721983832654978">"アプリ"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"すべてのアプリ"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"アプリ一覧"</string>
     <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"長押ししてショートカットを移動してください。"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"ショートカットをダブルタップして長押ししながら移動するか、カスタム操作を使用してください。"</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%1$d/%2$dページ"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"ホーム画面: %1$d/%2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"新しいホーム画面ページ"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"有効"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"最小化"</string>
     <string name="folder_opened" msgid="94695026776264709">"フォルダが開いています。<xliff:g id="WIDTH">%1$d</xliff:g>x<xliff:g id="HEIGHT">%2$d</xliff:g>の大きさです"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"タップしてフォルダを閉じます"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"タップして変更後の名前を保存します"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"フォルダの名前を「<xliff:g id="NAME">%1$s</xliff:g>」に変更しました"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"フォルダ: <xliff:g id="NAME">%1$s</xliff:g>、<xliff:g id="SIZE">%2$d</xliff:g> 件のアイテム"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"フォルダ: <xliff:g id="NAME">%1$s</xliff:g>、<xliff:g id="SIZE">%2$d</xliff:g> 件以上のアイテム"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"名前のないフォルダ"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"アプリのペア設定: <xliff:g id="APP1">%1$s</xliff:g> と <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"壁紙とスタイル"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"ホーム画面を編集"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"ウィジェット リストを閉じました"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"ホーム画面に追加"</string>
     <string name="action_move_here" msgid="2170188780612570250">"アイテムをここに移動"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"アイテムをホーム画面に追加しました"</string>
     <string name="item_removed" msgid="851119963877842327">"アイテムを削除しました"</string>
     <string name="undo" msgid="4151576204245173321">"元に戻す"</string>
     <string name="action_move" msgid="4339390619886385032">"アイテムを移動"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"閉じる"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"個人用"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"仕事用"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"仕事用プロファイル"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"仕事用アプリはバッジ付きで表示され、IT 管理者に公開されます"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index 955d65f..2fd6b77 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"„<xliff:g id="QUERY">%1$s</xliff:g>“-ის თანხვედრი აპები არ მოიძებნა"</string>
     <string name="label_application" msgid="8531721983832654978">"აპი"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"ყველა აპი"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"აპების სია"</string>
     <string name="notifications_header" msgid="1404149926117359025">"შეტყობინებები"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"შეხებით აირჩიეთ და გეჭიროთ მალსახმობის გადასაადგილებლად."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"ორმაგი შეხებით აირჩიეთ და გეჭიროთ მალსახმობის გადასაადგილებლად ან მორგებული მოქმედებების გამოსაყენებლად."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"გვერდი %1$d %2$d-დან"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"მთავარი ეკრანი %1$d, %2$d-დან"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"მთავარი ეკრანის ახალი გვერდი"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"აქტიური"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"დაპატარავებული"</string>
     <string name="folder_opened" msgid="94695026776264709">"საქაღალდე გახსნილია, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"შეეხეთ საქაღალდის დასახურად"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"შეეხეთ გადარქმეული სახელის შესანახად"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"საქაღალდეს შეეცვალა სახელი „<xliff:g id="NAME">%1$s</xliff:g>“-ად"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"საქაღალდე: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ერთეული"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"საქაღალდე: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ან მეტი ერთეული"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"უსახელო საქაღალდე"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"აპთა წყვილი: <xliff:g id="APP1">%1$s</xliff:g> და <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ფონი და სტილი"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"მთავარი ეკრანის რედაქტირება"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"ვიჯეტების სია დაიხურა"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"მთავარ ეკრანზე დამატება"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ერთეულის გადაადგილება აქ"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"ერთეული დაემატა მთავარ ეკრანს"</string>
     <string name="item_removed" msgid="851119963877842327">"ერთეული წაიშალა"</string>
     <string name="undo" msgid="4151576204245173321">"მოქმედების გაუქმება"</string>
     <string name="action_move" msgid="4339390619886385032">"ერთეულის გადაადგილება"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"დახურვა"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"პირადი"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"სამსახური"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"სამსახურის პროფილი"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"სამსახურის აპები ბეჯით არის მონიშნული და ხილულია თქვენი IT ადმინისტრატორისთვის"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"გასაგებია"</string>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index c50d007..fee3a9a 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" сұрауына сәйкес келетін қолданбалар жоқ"</string>
     <string name="label_application" msgid="8531721983832654978">"Қолданба"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Барлық қолданба"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Қолданбалар тізімі"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Хабарландырулар"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Таңбашаны жылжыту үшін басып тұрыңыз."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Таңбашаны жылжыту үшін екі рет түртіңіз де, ұстап тұрыңыз немесе арнаулы әрекеттерді пайдаланыңыз."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%1$d бет, барлығы %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"%1$d негізгі экран, барлығы %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Жаңа негізгі экран беті"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Қалта ашылды, <xliff:g id="WIDTH">%1$d</xliff:g> және <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Қалтаны жабу үшін түртіңіз"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Қайта атауды сақтау үшін түртіңіз"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Қалта атауы <xliff:g id="NAME">%1$s</xliff:g> болып өзгертілді"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Қалта: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> элемент бар"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Қалта: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> не одан көп элемент бар"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Атауы жоқ қалта"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Қолданбаларды жұптау: <xliff:g id="APP1">%1$s</xliff:g> және <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Тұсқағаз және стиль"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Негізгі экранды өзгерту"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Видджеттер тізімі жабылды"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Негізгі экранға қосу"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Элементті мұнда жылжыту"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Элемент негізгі экранға қосылды"</string>
     <string name="item_removed" msgid="851119963877842327">"Элемент жойылды"</string>
     <string name="undo" msgid="4151576204245173321">"Қайтару"</string>
     <string name="action_move" msgid="4339390619886385032">"Элементті жылжыту"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Жабу"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Жеке"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Жұмыс"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Жұмыс профилі"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Жұмыс қолданбаларының танымбелгілері бар және олар әкімшіңізге көрінеді."</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Түсінікті"</string>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 7a73e69..cc8539e 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"រកមិនឃើញកម្មវិធី​ដែលត្រូវគ្នាជាមួយ \"<xliff:g id="QUERY">%1$s</xliff:g>\" ទេ"</string>
     <string name="label_application" msgid="8531721983832654978">"កម្មវិធី"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"កម្មវិធី​ទាំងអស់"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"បញ្ជី​កម្មវិធី"</string>
     <string name="notifications_header" msgid="1404149926117359025">"ការ​ជូនដំណឹង"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"ចុចឱ្យជាប់​ដើម្បីផ្លាស់ទី​ផ្លូវកាត់​។"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"ចុចពីរដង រួចសង្កត់ឱ្យជាប់ ដើម្បីផ្លាស់ទី​ផ្លូវកាត់ ឬប្រើ​សកម្មភាព​តាមបំណង​។"</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"ទំព័រ %1$d នៃ %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"អេក្រង់​ដើម %1$d នៃ %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"ទំព័រអេក្រង់ដើមថ្មី"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"បាន​បើក​ថត <xliff:g id="WIDTH">%1$d</xliff:g> ដោយ <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"ប៉ះ ដើម្បីបិទថត"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"ប៉ះដើម្បីរក្សាទុកឈ្មោះដែលបានប្តូរ"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"បាន​ប្ដូរ​ឈ្មោះ​ថត​ជា <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"ថត៖ <xliff:g id="NAME">%1$s</xliff:g>, ធាតុ <xliff:g id="SIZE">%2$d</xliff:g>"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"ថត៖ <xliff:g id="NAME">%1$s</xliff:g>, ធាតុ <xliff:g id="SIZE">%2$d</xliff:g> ឬច្រើនជាងនេះ"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"ថត​ដែលគ្មាន​ឈ្មោះ"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"គូកម្មវិធី៖ <xliff:g id="APP1">%1$s</xliff:g> និង <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ផ្ទាំងរូបភាព និងរចនាបថ"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"កែអេក្រង់ដើម"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"បាន​បិទ​បញ្ជីធាតុ​ក្រាហ្វិក"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"បញ្ចូល​ទៅក្នុង​អេក្រង់​ដើម"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ផ្លាស់ធាតុមកទីនេះ"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"ធាតុដែលត្រូវបានបន្ថែមទៅអេក្រង់ដើម"</string>
     <string name="item_removed" msgid="851119963877842327">"បានដកធាតុចេញ"</string>
     <string name="undo" msgid="4151576204245173321">"ត្រឡប់វិញ"</string>
     <string name="action_move" msgid="4339390619886385032">"ផ្លាស់ទីធាតុ"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"បិទ"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"ផ្ទាល់ខ្លួន"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"ការងារ"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"កម្រងព័ត៌មានការងារ"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"កម្មវិធីការងារ​ត្រូវបានដាក់​គ្រឿងសម្គាល់ ហើយ​អ្នកគ្រប់គ្រង​ផ្នែកព័ត៌មានវិទ្យា​របស់អ្នក​អាចមើលឃើញ"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"យល់ហើយ"</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 8c988bf..cd5d59d 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ಹೊಂದಿಕೆಯ ಯಾವುದೇ ಅಪ್ಲಿಕೇಶನ್‌ಗಳು ಕಂಡುಬಂದಿಲ್ಲ"</string>
     <string name="label_application" msgid="8531721983832654978">"ಆ್ಯಪ್"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳು"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"ಆ್ಯಪ್‌ಗಳ ಪಟ್ಟಿ"</string>
     <string name="notifications_header" msgid="1404149926117359025">"ನೋಟಿಫಿಕೇಶನ್‌ಗಳು"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"ಶಾರ್ಟ್‌ಕಟ್ ಸರಿಸಲು ಸ್ಪರ್ಶಿಸಿ ಮತ್ತು ಹಿಡಿದುಕೊಳ್ಳಿ."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"ಶಾರ್ಟ್‌ಕಟ್ ಸರಿಸಲು ಅಥವಾ ಕಸ್ಟಮ್ ಕ್ರಿಯೆಗಳನ್ನು ಬಳಸಲು ಡಬಲ್-ಟ್ಯಾಪ್ ಮಾಡಿ ಮತ್ತು ಹಿಡಿದುಕೊಳ್ಳಿ."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%2$d ರಲ್ಲಿ %1$d ಪುಟ"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"%2$d ರಲ್ಲಿ %1$d ಮುಖಪುಟದ ಸ್ಕ್ರೀನ್"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"ಹೊಸ ಮುಖಪುಟ ಸ್ಕ್ರೀನ್"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"ಸಕ್ರಿಯವಾಗಿದೆ"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"ಮಿನಿಮೈಸ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="folder_opened" msgid="94695026776264709">"ಫೋಲ್ಡರ್ ತೆರೆಯಲಾಗಿದೆ, <xliff:g id="WIDTH">%1$d</xliff:g> ಬೈ <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"ಫೋಲ್ಡರ್‌ ಮುಚ್ಚಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"ಮರುಹೆಸರನ್ನು ಉಳಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"ಫೋಲ್ಡರ್‌ ಅನ್ನು <xliff:g id="NAME">%1$s</xliff:g> ಗೆ ಮರುಹೆಸರಿಸಲಾಗಿದೆ"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"ಫೋಲ್ಡರ್: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ಐಟಂಗಳು"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"ಫೋಲ್ಡರ್: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ಅಥವಾ ಹೆಚ್ಚಿನ ಐಟಂಗಳು"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"ಹೆಸರಿಲ್ಲದ ಫೋಲ್ಡರ್"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"ಆ್ಯಪ್ ಜೋಡಿ: <xliff:g id="APP1">%1$s</xliff:g> ಮತ್ತು <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ವಾಲ್‌ಪೇಪರ್ ಮತ್ತು ಶೈಲಿ"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"ಹೋಮ್ ಸ್ಕ್ರೀನ್ ಅನ್ನು ಎಡಿಟ್ ಮಾಡಿ"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"ವಿಜೆಟ್ ಪಟ್ಟಿಯನ್ನು ಮುಚ್ಚಲಾಗಿದೆ"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಸೇರಿಸಿ"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ಐಟಂ ಇಲ್ಲಿಗೆ ಸರಿಸಿ"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"ಹೋಮ್ ಸ್ಕ್ರೀನ್‌ಗೆ ಐಟಂ ಸೇರಿಸಲಾಗಿದೆ"</string>
     <string name="item_removed" msgid="851119963877842327">"ಐಟಂ ತೆಗೆದುಹಾಕಲಾಗಿದೆ"</string>
     <string name="undo" msgid="4151576204245173321">"ರದ್ದುಮಾಡಿ"</string>
     <string name="action_move" msgid="4339390619886385032">"ಐಟಂ ಸರಿಸಿ"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"ಮುಚ್ಚಿರಿ"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"ವೈಯಕ್ತಿಕ"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"ಕೆಲಸ"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"ಕೆಲಸಕ್ಕೆ ಸಂಬಂಧಿಸಿದ ಆ್ಯಪ್‌ಗಳನ್ನು ಬ್ಯಾಡ್ಜ್ ಮಾಡಲಾಗಿದೆ ಮತ್ತು ಅವುಗಳು ನಿಮ್ಮ IT ನಿರ್ವಾಹಕರಿಗೆ ಗೋಚರಿಸುತ್ತವೆ"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"ಸರಿ"</string>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 9f7217b..e588634 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\'<xliff:g id="QUERY">%1$s</xliff:g>\'과(와) 일치하는 앱이 없습니다."</string>
     <string name="label_application" msgid="8531721983832654978">"앱"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"모든 앱"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"앱 목록"</string>
     <string name="notifications_header" msgid="1404149926117359025">"알림"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"길게 터치하여 바로가기를 이동하세요."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"두 번 탭한 다음 길게 터치하여 바로가기를 이동하거나 맞춤 작업을 사용하세요."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"페이지 %1$d/%2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"홈 화면 %1$d/%2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"새로운 홈 화면 페이지"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"폴더 열림(<xliff:g id="WIDTH">%1$d</xliff:g>X<xliff:g id="HEIGHT">%2$d</xliff:g>)"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"탭하여 폴더 닫기"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"탭하여 변경된 이름 저장"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"폴더 이름 변경: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"폴더: <xliff:g id="NAME">%1$s</xliff:g>, 항목 <xliff:g id="SIZE">%2$d</xliff:g>개"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"폴더: <xliff:g id="NAME">%1$s</xliff:g>, 항목 <xliff:g id="SIZE">%2$d</xliff:g>개 이상"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"이름 없는 폴더"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"앱 페어링: <xliff:g id="APP1">%1$s</xliff:g> 및 <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"배경화면 및 스타일"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"홈 화면 수정"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"위젯 목록 닫힘"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"홈 화면에 추가"</string>
     <string name="action_move_here" msgid="2170188780612570250">"여기에 항목을 이동"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"홈 화면에 항목 추가됨"</string>
     <string name="item_removed" msgid="851119963877842327">"항목 삭제됨"</string>
     <string name="undo" msgid="4151576204245173321">"실행취소"</string>
     <string name="action_move" msgid="4339390619886385032">"항목 이동"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"닫기"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"개인"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"직장"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"직장 프로필"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"직장 앱에는 배지가 있으며, IT 관리자는 직장 앱을 확인할 수 있습니다"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"확인"</string>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index b7ce9d7..fb1675e 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -46,7 +46,7 @@
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Туурасы: %1$d, бийиктиги: %2$d"</string>
     <string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> виджети"</string>
     <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> виджети, кеңдиги %2$d жана бийиктиги %3$d"</string>
-    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Башкы экранга жылдыруу үчүн виджетти коё бербей басып туруңуз"</string>
+    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Вижетти коё бербей басып туруп башкы экранга жылдырыңыз"</string>
     <string name="add_to_home_screen" msgid="9168649446635919791">"Башкы экранга кошуу"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> виджети башкы экранга кошулду"</string>
     <string name="suggested_widgets_header_title" msgid="1844314680798145222">"Сунуштар"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" сурамына дал келген колдонмолор табылган жок"</string>
     <string name="label_application" msgid="8531721983832654978">"Колдонмо"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Бардык колдонмолор"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Колдонмолор тизмеси"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Билдирмелер"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Ыкчам баскычты жылдыруу үчүн коё бербей басып туруңуз."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Ыкчам баскычты жылдыруу үчүн эки жолу таптап, кармап туруңуз же ыңгайлаштырылган аракеттерди колдонуңуз."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%2$d ичинен %1$d барак"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Үй экраны %2$d ичинен %1$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Жаңы башкы экран барагы"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Фолдер ачылды, туурасы <xliff:g id="WIDTH">%1$d</xliff:g>, бийиктиги <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Куржунду жабуу үчүн таптаңыз"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Өзгөртүлгөн аталышын сактоо үчүн таптаңыз"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Фолдердин аты <xliff:g id="NAME">%1$s</xliff:g> деп өзгөртүлдү"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"<xliff:g id="NAME">%1$s</xliff:g> папкасындагы объекттер: <xliff:g id="SIZE">%2$d</xliff:g>"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"<xliff:g id="NAME">%1$s</xliff:g> папкасындагы объекттер: <xliff:g id="SIZE">%2$d</xliff:g> же андан көбүрөөк"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Аталышы жок папка"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Эки колдонмону бир маалда пайдалануу: <xliff:g id="APP1">%1$s</xliff:g> жана <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Тушкагаз жана стиль"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Башкы экранды түзөтүү"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Виджеттердин тизмеси жабык"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Башкы экранга кошуу"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Бул нерсени бул жерге жылдыруу"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Башкы экранга кошулду"</string>
     <string name="item_removed" msgid="851119963877842327">"Жоюлду"</string>
     <string name="undo" msgid="4151576204245173321">"Кайтаруу"</string>
     <string name="action_move" msgid="4339390619886385032">"Муну жылдыруу"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Жабуу"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Жеке колдонмолор"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Жумуш колдонмолору"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Жумуш профили"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Жумуш колдонмолору белгиленип, аларды IT администраторлору көрөт"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Түшүндүм"</string>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 33fff3d..2aaf40b 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"ບໍ່ພົບແອັບທີ່ກົງກັບ \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"ແອັບ"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"ແອັບທັງໝົດ"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"ລາຍຊື່ແອັບ"</string>
     <string name="notifications_header" msgid="1404149926117359025">"ການແຈ້ງເຕືອນ"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"ແຕະຄ້າງໄວ້ເພື່ອຍ້າຍທາງລັດ."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"ແຕະສອງເທື່ອຄ້າງໄວ້ເພື່ອຍ້າຍທາງລັດ ຫຼື ໃຊ້ຄຳສັ່ງກຳນົດເອງ."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"ໜ້າ %1$d ຈາກ %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"ໜ້າຈໍຫຼັກ %1$d ໃນ %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"ໜ້າ​ຂອງ​ໜ້າ​ຈໍ​ຫຼັກ​ໃໝ່"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"ນຳໃຊ້ຢູ່"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"ຫຍໍ້ລົງແລ້ວ"</string>
     <string name="folder_opened" msgid="94695026776264709">"ເປີດໂຟນເດີແລ້ວ, <xliff:g id="WIDTH">%1$d</xliff:g> ຄູນ <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"ແຕະເພື່ອປິດໂຟນເດີ"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"ແຕະເພື່ອບັນທຶກການປ່ຽນຊື່"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"ປ່ຽນຊື່ໂຟນເດີເປັນ <xliff:g id="NAME">%1$s</xliff:g> ແລ້ວ"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"ໂຟນເດີ: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ລາຍການ"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"ໂຟນເດີ: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ຫຼື ລາຍການເພີ່ມເຕີມ"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"ໂຟນເດີທີ່ບໍ່ມີຊື່"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"ຈັບຄູ່ແອັບ: <xliff:g id="APP1">%1$s</xliff:g> ແລະ <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ຮູບພື້ນຫຼັງ ແລະ ຮູບແບບ"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"ແກ້ໄຂໂຮມສະກຣີນ"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"ປິດລາຍຊື່ວິດເຈັດແລ້ວ"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"ເພີ່ມໃສ່ໂຮມສະກຣີນ"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Move item here"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"ເພີ່ມ​ລາຍ​ການ​ໃສ່​ໜ້າ​ຈໍ​ຫຼັກ​ແລ້ວ"</string>
     <string name="item_removed" msgid="851119963877842327">"ເອົາ​ລາຍ​ການ​ອອກ​ໄປ​ແລ້ວ"</string>
     <string name="undo" msgid="4151576204245173321">"ຍົກເລີກ"</string>
     <string name="action_move" msgid="4339390619886385032">"ຍ້າຍ​ລາຍ​ການ"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"ປິດ"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"ສ່ວນຕົວ"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"ວຽກ"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"ໂປຣໄຟລ໌ບ່ອນເຮັດວຽກ"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"ແອັບບ່ອນເຮັດວຽກແມ່ນຖືກຕິດປ້າຍ ແລະ ສະແດງໃຫ້ຜູ້ເບິ່ງແຍງໄອທີຂອງທ່ານເຫັນ"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"ເຂົ້າໃຈແລ້ວ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 0d0f5db..aeba444 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -28,7 +28,7 @@
     <string name="shortcut_not_available" msgid="2536503539825726397">"Sparčiojo klavišo negalima naudoti"</string>
     <string name="home_screen" msgid="5629429142036709174">"Pagrindinis"</string>
     <string name="set_default_home_app" msgid="5808906607627586381">"Nustatykite „<xliff:g id="LAUNCHER_NAME">%1$s</xliff:g>“ kaip numatytąją pagrindinę programą skiltyje „Nustatymai“"</string>
-    <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Išskaidyto ekrano režimas"</string>
+    <string name="recent_task_option_split_screen" msgid="6690461455618725183">"Išskaidytas ekranas"</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>
     <string name="new_window_option_taskbar" msgid="6448780542727767211">"Naujas langas"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nerasta jokių užklausą „<xliff:g id="QUERY">%1$s</xliff:g>“ atitinkančių programų"</string>
     <string name="label_application" msgid="8531721983832654978">"Programa"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Visos programos"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Programų sąrašas"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Pranešimai"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Dukart pal. ir palaik., kad perk. spart. klavišą."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Dukart palieskite ir palaikykite, kad perkeltumėte spartųjį klavišą ar naudotumėte tinkintus veiksmus."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%1$d psl. iš %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"%1$d pagrindinis ekranas iš %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Naujas pagrindinio ekrano puslapis"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Aktyvi"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Sumažinta"</string>
     <string name="folder_opened" msgid="94695026776264709">"Atidarytas aplankas, <xliff:g id="WIDTH">%1$d</xliff:g> ir <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Palieskite, kad uždarytumėte aplanką"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Palieskite, kad išsaugotumėte pakeistą pavadinimą"</string>
@@ -123,10 +126,11 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Aplankas pervardytas kaip „<xliff:g id="NAME">%1$s</xliff:g>“"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Aplankas: „<xliff:g id="NAME">%1$s</xliff:g>“, elementų: <xliff:g id="SIZE">%2$d</xliff:g>"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Aplankas: „<xliff:g id="NAME">%1$s</xliff:g>“, elementų: <xliff:g id="SIZE">%2$d</xliff:g> ar daugiau"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Aplankas be pavadinimo"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Programų pora: „<xliff:g id="APP1">%1$s</xliff:g>“ ir „<xliff:g id="APP2">%2$s</xliff:g>“"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Ekrano fonas ir stilius"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Redaguoti pagrindinį ekraną"</string>
-    <string name="settings_button_text" msgid="8873672322605444408">"„Home“ nustatymai"</string>
+    <string name="settings_button_text" msgid="8873672322605444408">"Pagrindinio ekrano nustatymai"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Išjungė administratorius"</string>
     <string name="allow_rotation_title" msgid="7222049633713050106">"Leisti pasukti pagrindinį ekraną"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Kai telefonas pasukamas"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Valdiklių sąrašas uždarytas"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Pridėti prie pagrind. ekrano"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Perkelti elementą čia"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Elementas pridėtas prie pagrindinio ekrano"</string>
     <string name="item_removed" msgid="851119963877842327">"Elementas perkeltas"</string>
     <string name="undo" msgid="4151576204245173321">"Anuliuoti"</string>
     <string name="action_move" msgid="4339390619886385032">"Perkelti elementą"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Uždaryti"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Asmeninės"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Darbo"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Darbo profilis"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Darbo programos yra pažymėtos ženkleliu ir matomos IT administratoriui"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Supratau"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index c86c6bb..0ba127d 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Vaicājumam “<xliff:g id="QUERY">%1$s</xliff:g>” neatbilda neviena lietotne"</string>
     <string name="label_application" msgid="8531721983832654978">"Lietotne"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Visas lietotnes"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Lietotņu saraksts"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Paziņojumi"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Lai pārvietotu saīsni, pieskarieties un turiet."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Lai pārvietotu saīsni, uz tās veiciet dubultskārienu un turiet. Varat arī veikt pielāgotas darbības."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%1$d. lapa no %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Sākuma ekrāns: %1$d no %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Jauna sākuma ekrāna lapa"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Atvērta mape: <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Pieskarieties, lai aizvērtu mapi."</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Pieskarieties, lai saglabātu jauno nosaukumu."</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Mape pārdēvēta par: <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Mape <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> vienumi"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Mape <xliff:g id="NAME">%1$s</xliff:g>, vienumu skaits mapē: vismaz <xliff:g id="SIZE">%2$d</xliff:g>"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Mape bez nosaukuma"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Lietotņu pāris: <xliff:g id="APP1">%1$s</xliff:g> un <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Fona tapete un stils"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Rediģēt sākuma ekrānu"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Logrīku saraksts aizvērts"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Pievienot sākuma ekrānam"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Pārvietot vienumu šeit"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Vienums pievienots sākuma ekrānam"</string>
     <string name="item_removed" msgid="851119963877842327">"Vienums noņemts"</string>
     <string name="undo" msgid="4151576204245173321">"Atsaukt"</string>
     <string name="action_move" msgid="4339390619886385032">"Pārvietot vienumu"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Aizvērt"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personīgās lietotnes"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Darba lietotnes"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Darba profils"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Darba lietotnēm ir pievienota emblēma, un tās ir redzamas jūsu IT administratoram"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Labi"</string>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index cb5f15a..ba119f7 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Не се најдени апликации што одговараат на „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
     <string name="label_application" msgid="8531721983832654978">"Апликација"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Сите апликации"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Список со апликации"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Известувања"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Допрете и задржете за да преместите кратенка."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Допрете двапати и задржете за да преместите кратенка или користете приспособени дејства."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Страница %1$d од %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Екран на почетна страница %1$d од %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Нова страница на почетен екран"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Отворена е папка, <xliff:g id="WIDTH">%1$d</xliff:g> на <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Допрете за да ја затворите папката"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Допрете за да го зачувате преименувањето"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Папката е преименувана во <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Папка: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ставки"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Папка: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> или повеќе ставки"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Неименувана папка"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Пар апликации: <xliff:g id="APP1">%1$s</xliff:g> и <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Тапет и стил"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Изменете го почетниот екран"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Списокот со виџети е затворен"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Додај на почетниот екран"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Премести ја ставката овде"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Ставката е додадена на почетниот екран"</string>
     <string name="item_removed" msgid="851119963877842327">"Ставката е отстранета"</string>
     <string name="undo" msgid="4151576204245173321">"Врати"</string>
     <string name="action_move" msgid="4339390619886385032">"Премести ја ставката"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Затвори"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Лично"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"За работа"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Работен профил"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Работните апликации имаат значка и се видливи за IT-администраторот"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Сфатив"</string>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 2c82585..2a2e050 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" എന്നതുമായി പൊരുത്തപ്പെടുന്ന ആപ്പുകളൊന്നും കണ്ടെത്തിയില്ല"</string>
     <string name="label_application" msgid="8531721983832654978">"ആപ്പ്"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"എല്ലാ ആപ്പുകളും"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"ആപ്പുകളുടെ ലിസ്റ്റ്"</string>
     <string name="notifications_header" msgid="1404149926117359025">"അറിയിപ്പുകൾ"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"കുറുക്കുവഴി നീക്കാൻ സ്‌പർശിച്ച് പിടിക്കുക."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"കുറുക്കുവഴി നീക്കാൻ ഡബിൾ ടാപ്പ് ചെയ്യൂ, ഹോൾഡ് ചെയ്യൂ അല്ലെങ്കിൽ ഇഷ്‌ടാനുസൃത പ്രവർത്തനങ്ങൾ ഉപയോഗിക്കൂ."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"പേജ് %1$d / %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"ഹോം സ്‌ക്രീൻ %1$d / %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"പുതിയ ഹോം സ്ക്രീൻ പേജ്"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"ഫോൾഡർ തുറന്നു, <xliff:g id="WIDTH">%1$d</xliff:g> / <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"ഫോൾഡർ അടയ്ക്കുന്നതിന് ടാപ്പുചെയ്യുക"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"പേരുമാറ്റം സംരക്ഷിക്കുന്നതിന് ടാപ്പുചെയ്യുക"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"ഫോൾഡറിന്റെ പേര് <xliff:g id="NAME">%1$s</xliff:g> എന്നായി മാറ്റി"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"ഫോൾഡർ: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ഇനങ്ങൾ"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"ഫോൾഡർ: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> അല്ലെങ്കിൽ അതിലധികം ഇനങ്ങൾ"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"പേരിടാത്ത ഫോൾഡർ"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"ആപ്പ് ജോടി: <xliff:g id="APP1">%1$s</xliff:g>, <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"വാൾപേപ്പറും സ്‌റ്റൈലും"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"ഹോം സ്‌ക്രീൻ എഡിറ്റ് ചെയ്യുക"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"വിജറ്റുകളുടെ ലിസ്‌റ്റ് അവസാനിപ്പിച്ചു"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"ഹോം സ്‌ക്രീനിലേക്ക് ചേർക്കുക"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ഇനം ഇവിടേക്ക് നീക്കുക"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"ഹോം സ്‌ക്രീനിൽ ഇനം ചേർത്തു"</string>
     <string name="item_removed" msgid="851119963877842327">"ഇനം നീക്കംചെയ്‌തു"</string>
     <string name="undo" msgid="4151576204245173321">"പഴയപടിയാക്കുക"</string>
     <string name="action_move" msgid="4339390619886385032">"ഇനം നീക്കുക"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"അടയ്ക്കൂ"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"വ്യക്തിപരം"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Work"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"ഔദ്യോഗിക പ്രൊഫൈൽ"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"ഔദ്യോഗിക ആപ്പുകൾക്ക് ബാഡ്‌ജ് നൽകിയിരിക്കുന്നു, അവ നിങ്ങളുടെ ഐടി അഡ്‌മിന് കാണാനുമാകും"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"മനസ്സിലായി"</string>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 434a731..ffe1b2c 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"-д тохирох апп олдсонгүй"</string>
     <string name="label_application" msgid="8531721983832654978">"Апп"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Бүх апп"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Аппын жагсаалт"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Мэдэгдэл"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Товчлолыг зөөхийн тулд хүрээд, удаан дарна уу."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Товчлолыг зөөх эсвэл захиалгат үйлдлийг ашиглахын тулд хоёр товшоод, удаан дарна уу."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%2$d-н %1$d хуудас"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"%2$d-н Нүүр дэлгэц %1$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Шинэ үндсэн нүүр хуудас"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"<xliff:g id="WIDTH">%1$d</xliff:g> <xliff:g id="HEIGHT">%2$d</xliff:g> фолдер нээгдэв"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Фолдерийг хаахын тулд дарна уу"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Шинэ нэрийг хадгалахын тулд дарна уу."</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Фолдерын нэр <xliff:g id="NAME">%1$s</xliff:g> болов"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Фолдер: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> зүйл"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Фолдер: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> эсвэл үүнээс олон зүйл"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Нэргүй фолдер"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Апп хослуулалт: <xliff:g id="APP1">%1$s</xliff:g> болон <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Дэлгэцийн зураг, загвар"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Үндсэн нүүрийг засах"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Жижиг хэрэгслийн жагсаалтыг хаасан"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Үндсэн нүүрэнд нэмэх"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Энд байршуулах"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Нүүр дэлгэцэнд нэмсэн зүйл"</string>
     <string name="item_removed" msgid="851119963877842327">"Зүйлийг устгалаа"</string>
     <string name="undo" msgid="4151576204245173321">"Болих"</string>
     <string name="action_move" msgid="4339390619886385032">"Зөөх"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Хаах"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Хувийн"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Ажил"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Ажлын профайл"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Ажлын аппуудыг тэмдэглэсэн бөгөөд танай IT админд харагдана"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Ойлголоо"</string>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index c872cc6..b249150 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" शी जुळणारे कोणतेही अ‍ॅप्स आढळले नाहीत"</string>
     <string name="label_application" msgid="8531721983832654978">"ॲप"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"सर्व अ‍ॅप्स"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"अ‍ॅप्स सूची"</string>
     <string name="notifications_header" msgid="1404149926117359025">"सूचना"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"शॉर्टकट हलवण्यासाठी स्पर्श करा आणि धरून ठेवा."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"शॉर्टकट हलवण्यासाठी किंवा कस्टम कृती वापरण्यासाठी दोनदा टॅप करा आणि धरून ठेवा."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%2$d पैकी %1$d पेज"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"%2$d पैकी %1$d मुख्य स्क्रीन"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"नवीन होम स्क्रीन पेज"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"फोल्डर उघडले, <xliff:g id="WIDTH">%1$d</xliff:g> बाय <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"फोल्डर बंद करण्यासाठी टॅप करा"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"पुनर्नामित करणे सेव्ह करण्यासाठी टॅप करा"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"फोल्डरचे नाव बदलून <xliff:g id="NAME">%1$s</xliff:g> असे ठेवले"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"फोल्डर: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> आयटम"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"फोल्डर: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> किंवा त्याहून अधिक आयटम"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"नाव नसलेले फोल्डर"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"ॲपची जोडी: <xliff:g id="APP1">%1$s</xliff:g> आणि <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"वॉलपेपर आणि शैली"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"होम स्क्रीन संपादित करा"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"विजेट सूची बंद केली"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"होम स्क्रीनवर जोडा"</string>
     <string name="action_move_here" msgid="2170188780612570250">"आयटम येथे हलवा"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"आयटम मुख्य स्क्रीनवर जोडला"</string>
     <string name="item_removed" msgid="851119963877842327">"आयटम काढून टाकला"</string>
     <string name="undo" msgid="4151576204245173321">"पहिल्यासारखे करा"</string>
     <string name="action_move" msgid="4339390619886385032">"आयटम हलवा"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"बंद करा"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"वैयक्तिक"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"कार्य"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"कार्य प्रोफाइल"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"कार्य ॲप्स ही बॅज केलेली असून तुमच्या आयटी ॲडमिनला दृश्यमान आहेत"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"समजले"</string>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index f5dca93..3585ec7 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Tiada apl yang ditemui sepadan dengan \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"Apl"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Semua apl"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Senarai apl"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Pemberitahuan"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Sentuh &amp; tahan untuk menggerakkan pintasan."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Ketik dua kali &amp; tahan untuk menggerakkan pintasan atau menggunakan tindakan tersuai."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Halaman %1$d daripada %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Skrin Laman Utama %1$d daripada %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Halaman skrin utama baharu"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Aktif"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Minimumkan"</string>
     <string name="folder_opened" msgid="94695026776264709">"Folder dibuka, <xliff:g id="WIDTH">%1$d</xliff:g> kali <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Ketik untuk menutup folder"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Ketik untuk menyimpan penamaan semula"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Folder dinamakan semula kepada <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> item"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> atau lebih banyak item"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Folder tidak bernama"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Gandingan apl: <xliff:g id="APP1">%1$s</xliff:g> dan <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Hiasan latar &amp; gaya"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Edit Skrin Utama"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Senarai widget ditutup"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Tambahkan pada skrin utama"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Alihkan item ke sini"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Item ditambahkan pada skrin utama"</string>
     <string name="item_removed" msgid="851119963877842327">"Item dialih keluar"</string>
     <string name="undo" msgid="4151576204245173321">"Buat asal"</string>
     <string name="action_move" msgid="4339390619886385032">"Alihkan Item"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Tutup"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Peribadi"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Kerja"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil kerja"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Apl kerja mempunyai lencana dan kelihatan kepada pentadbir IT anda"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 24f4435..0116834 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" နှင့်ကိုက်ညီသည့် အပ်ပ်များကို မတွေ့ပါ"</string>
     <string name="label_application" msgid="8531721983832654978">"အက်ပ်"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"အက်ပ်အားလုံး"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"အက်ပ်စာရင်း"</string>
     <string name="notifications_header" msgid="1404149926117359025">"အကြောင်းကြားချက်များ"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"ဖြတ်လမ်းလင့်ခ်ကို ရွှေ့ရန် နှစ်ချက်တို့ပြီး ဖိထားပါ။"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"ဖြတ်လမ်းလင့်ခ်ကို ရွှေ့ရန် (သို့) စိတ်ကြိုက်လုပ်ဆောင်ချက်များကို သုံးရန် နှစ်ချက်တို့ပြီး ဖိထားပါ။"</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"စာမျက်နှာ %1$d မှ %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"ပင်မစာမျက်နှာ %1$d မှ %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"ပင်မမျက်နှာပြင် စာမျက်နှာသစ်"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"ဖွင့်ထားသောအကန့်, <xliff:g id="WIDTH">%1$d</xliff:g> နှင့် <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"ဖိုင်တွဲကို ပိတ်ရန် တို့ပါ"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"အမည်ပြောင်းခြင်းကို သိမ်းရန် တို့ပါ"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"ပြောင်းလဲလိုက်သော အကန့်အမည် <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"ဖိုင်တွဲ - <xliff:g id="NAME">%1$s</xliff:g>၊ <xliff:g id="SIZE">%2$d</xliff:g> ဖိုင်များ"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"ဖိုင်တွဲ - <xliff:g id="NAME">%1$s</xliff:g>၊ <xliff:g id="SIZE">%2$d</xliff:g> သို့မဟုတ် နောက်ထပ်ဖိုင်များ"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"အမည်ပေးမထားသောဖိုင်တွဲ"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"အက်ပ်တွဲချိတ်ခြင်း- <xliff:g id="APP1">%1$s</xliff:g> နှင့် <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"နောက်ခံနှင့် ပုံစံ"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"ပင်မစာမျက်နှာ တည်းဖြတ်ရန်"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"ဝိဂျက်စာရင်းကို ပိတ်ထားသည်"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"ပင်မစာမျက်နှာတွင် ထည့်ရန်"</string>
     <string name="action_move_here" msgid="2170188780612570250">"၎င်းအား ဤသို့ ရွှေ့ပါ"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"ပင်မ ဖန်မျက်နှာပြင်သို့ ထည့်ပြီး၏"</string>
     <string name="item_removed" msgid="851119963877842327">"ဖယ်ရှားပြီးပြီ"</string>
     <string name="undo" msgid="4151576204245173321">"နောက်ပြန်ရန်"</string>
     <string name="action_move" msgid="4339390619886385032">"၎င်းအား ရွှေ့ပါ"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"ပိတ်ရန်"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"ကိုယ်ပိုင်"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"အလုပ်"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"အလုပ်ပရိုဖိုင်"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"အလုပ်သုံးအက်ပ်များကို တံဆိပ်တပ်ထားပြီး သင်၏ IT စီမံခန့်ခွဲသူက မြင်နိုင်ပါသည်"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"ရပါပြီ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index a79740d..47c6abb 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Fant ingen apper som samsvarer med «<xliff:g id="QUERY">%1$s</xliff:g>»"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Alle apper"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Appliste"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Varsler"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Trykk og hold for å flytte en snarvei."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Dobbelttrykk og hold for å flytte en snarvei eller bruke tilpassede handlinger."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Side %1$d av %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Startside %1$d av %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Ny side på startskjermen"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Mappen er åpnet – <xliff:g id="WIDTH">%1$d</xliff:g> ganger <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Trykk for å lukke mappen"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Trykk for å lagre det nye navnet"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Mappen heter nå <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Mappe: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elementer"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Mappe: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> eller flere elementer"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Mappe uten navn"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Apptilkobling: <xliff:g id="APP1">%1$s</xliff:g> og <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Bakgrunn og stil"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Endre startsiden"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Modullisten er lukket"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Legg til på startskjermen"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Flytt elementet hit"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Elementet er lagt til på startskjermen"</string>
     <string name="item_removed" msgid="851119963877842327">"Elementet er fjernet"</string>
     <string name="undo" msgid="4151576204245173321">"Angre"</string>
     <string name="action_move" msgid="4339390619886385032">"Flytt elementet"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Lukk"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personlig"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Jobb"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Jobbprofil"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Jobbapper er merket og synlige for IT-administratoren"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Greit"</string>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index f35b943..e451926 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -82,7 +82,8 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" सँग मिल्दो कुनै एप भेटिएन"</string>
     <string name="label_application" msgid="8531721983832654978">"एप"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"सबै एप"</string>
-    <string name="notifications_header" msgid="1404149926117359025">"सूचनाहरू"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"एपहरूको सूची"</string>
+    <string name="notifications_header" msgid="1404149926117359025">"नोटिफिकेसनहरू"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"कुनै सर्टकट सार्न डबल ट्याप गरेर छोइराख्नुहोस्।"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"कुनै सर्टकट सार्न वा आफ्नो रोजाइका कारबाही प्रयोग गर्न डबल ट्याप गरेर छोइराख्नुहोस्।"</string>
     <string name="out_of_space" msgid="6455557115204099579">"यो होम स्क्रिनमा ठाउँ छैन"</string>
@@ -96,7 +97,7 @@
     <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>
+    <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="bubble" msgid="3072951361014076670">"बबल"</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"पृष्ठ %2$d को %1$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"होम स्क्रिन %1$d को %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"नयाँ होम स्क्रिन पृष्ठ"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"फोल्डर खुल्यो <xliff:g id="WIDTH">%1$d</xliff:g> बाट <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"फोल्डरलाई बन्द गर्न ट्याप गर्नुहोस्"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"पुनःनामाकरणलाई सुरक्षित गर्न ट्याप गर्नुहोस्"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"फोल्डर <xliff:g id="NAME">%1$s</xliff:g> मा पुनःनामाकरण गरियो।"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"फोल्डर: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> वस्तुहरू"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"फोल्डर: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> वा सोभन्दा बढी वस्तुहरू"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"नामरहित फोल्डर"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"एप पेयर: <xliff:g id="APP1">%1$s</xliff:g> र <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"वालपेपर तथा शैली"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"होम स्क्रिन बदल्नुहोस्"</string>
@@ -133,7 +139,7 @@
     <string name="landscape_mode_title" msgid="5138814555934843926">"ल्यान्डस्केप मोड"</string>
     <string name="landscape_mode_desc" msgid="7372569859592816793">"फोनमा ल्यान्डस्केप मोड अन गर्नुहोस्"</string>
     <string name="notification_dots_title" msgid="9062440428204120317">"नोटिफिकेसन डट"</string>
-    <string name="notification_dots_desc_on" msgid="1679848116452218908">"सक्रिय"</string>
+    <string name="notification_dots_desc_on" msgid="1679848116452218908">"अन छ"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"निष्क्रिय"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"सूचनासम्बन्धी पहुँच आवश्यक हुन्छ"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"नोटिफिकेसन डट देखाउन <xliff:g id="NAME">%1$s</xliff:g> को एपसम्बन्धी सूचनाहरूलाई अन गर्नुहोस्"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"विजेटहरूको सूची बन्द गरियो"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"होम स्क्रिनमा राख्नुहोस्"</string>
     <string name="action_move_here" msgid="2170188780612570250">"वस्तु यहाँ सार्नुहोस्"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"वस्तु गृह स्क्रिनमा थपियो"</string>
     <string name="item_removed" msgid="851119963877842327">"वस्तु हटाइयो"</string>
     <string name="undo" msgid="4151576204245173321">"अन्डू गर्नुहोस्"</string>
     <string name="action_move" msgid="4339390619886385032">"वस्तु सार्नुहोस्"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"बन्द गर्नुहोस्"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"व्यक्तिगत"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"कामसम्बन्धी"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"कार्य प्रोफाइल"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"कामसम्बन्धी एपहरूमा ब्याज अङ्कित हुन्छ र तपाईंका IT एड्मिन ती एप हेर्न सक्छन्"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"बुझेँ"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 5083976..75793b0 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Er zijn geen apps gevonden die overeenkomen met \'<xliff:g id="QUERY">%1$s</xliff:g>\'"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Alle apps"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Lijst met apps"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Meldingen"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Tik en houd vast om een snelkoppeling te verplaatsen."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Dubbeltik en houd vast om een snelkoppeling te verplaatsen of aangepaste acties te gebruiken."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Pagina %1$d van %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Startscherm %1$d van %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nieuwe startschermpagina"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Actief"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Geminimaliseerd"</string>
     <string name="folder_opened" msgid="94695026776264709">"Map geopend, <xliff:g id="WIDTH">%1$d</xliff:g> bij <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Tik om de map te sluiten"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tik om de gewijzigde naam op te slaan"</string>
@@ -123,10 +126,11 @@
     <string name="folder_renamed" msgid="1794088362165669656">"De naam van de map is gewijzigd in <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Map: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> items"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Map: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> of meer items"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Naamloze map"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"App-paar: <xliff:g id="APP1">%1$s</xliff:g> en <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Achtergrond en stijl"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Startscherm bewerken"</string>
-    <string name="settings_button_text" msgid="8873672322605444408">"Instellingen start"</string>
+    <string name="settings_button_text" msgid="8873672322605444408">"Instellingen Start"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Uitgezet door je beheerder"</string>
     <string name="allow_rotation_title" msgid="7222049633713050106">"Draaien van startscherm toestaan"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Als de telefoon gedraaid is"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Lijst met widgets gesloten"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Toevoegen aan startscherm"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Item hier naartoe verplaatsen"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Item toegevoegd aan startscherm"</string>
     <string name="item_removed" msgid="851119963877842327">"Item verwijderd"</string>
     <string name="undo" msgid="4151576204245173321">"Ongedaan maken"</string>
     <string name="action_move" msgid="4339390619886385032">"Item verplaatsen"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Sluiten"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Privé"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Werk"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Werkprofiel"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Werk-apps hebben badges en zijn zichtbaar voor je IT-beheerder"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index a26a7c5..98ccaf1 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -60,10 +60,10 @@
     <string name="shortcuts_count" msgid="8471715556199592381">"{count,plural, =1{#ଟି ସର୍ଟକଟ୍}other{#ଟି ସର୍ଟକଟ୍}}"</string>
     <string name="widgets_and_shortcuts_count" msgid="7209136747878365116">"<xliff:g id="WIDGETS_COUNT">%1$s</xliff:g>, <xliff:g id="SHORTCUTS_COUNT">%2$s</xliff:g>"</string>
     <string name="widget_button_text" msgid="2880537293434387943">"ୱିଜେଟ୍‌"</string>
-    <string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"ସନ୍ଧାନ କରନ୍ତୁ"</string>
+    <string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"ସର୍ଚ୍ଚ କରନ୍ତୁ"</string>
     <string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"ସନ୍ଧାନ ବାକ୍ସରୁ ଟେକ୍ସଟ୍ ଖାଲି କରନ୍ତୁ"</string>
     <string name="no_widgets_available" msgid="4337693382501046170">"ୱିଜେଟ୍ ଏବଂ ସର୍ଟକଟଗୁଡ଼ିକ ଉପଲବ୍ଧ ନାହିଁ"</string>
-    <string name="no_search_results" msgid="3787956167293097509">"କୌଣସି ୱିଜେଟ୍ କିମ୍ବା ସର୍ଟକଟ୍ ମିଳିଲା ନାହିଁ"</string>
+    <string name="no_search_results" msgid="3787956167293097509">"କୌଣସି ୱିଜେଟ କିମ୍ବା ସର୍ଟକଟ ମିଳିଲା ନାହିଁ"</string>
     <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"ବ୍ୟକ୍ତିଗତ"</string>
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ୱାର୍କ"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"ବାର୍ତ୍ତାଳାପଗୁଡ଼ିକ"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ସହିତ ମେଳ ହେଉଥିବା କୌଣସି ଆପ୍‌ ମିଳିଲା ନାହିଁ"</string>
     <string name="label_application" msgid="8531721983832654978">"ଆପ୍"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"ସବୁ ଆପ"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"ଆପ୍ସ ତାଲିକା"</string>
     <string name="notifications_header" msgid="1404149926117359025">"ବିଜ୍ଞପ୍ତି"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"ଏକ ସର୍ଟକଟକୁ ମୁଭ୍ କରିବା ପାଇଁ ସ୍ପର୍ଶ କରି ଧରି ରଖନ୍ତୁ।"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"ଏକ ସର୍ଟକଟକୁ ମୁଭ୍ କରିବା ପାଇଁ ଦୁଇଥର-ଟାପ୍ କରି ଧରି ରଖନ୍ତୁ କିମ୍ବା କଷ୍ଟମ୍ କାର୍ଯ୍ୟଗୁଡ଼ିକୁ ବ୍ୟବହାର କରନ୍ତୁ।"</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"ମୋଟ %2$dରୁ %1$d ନମ୍ବର ପୃଷ୍ଠା"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"%2$dରୁ %1$d ହୋମ ସ୍କ୍ରିନ"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"ନୂଆ ହୋମ ସ୍କ୍ରିନ ପେଜ"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"<xliff:g id="HEIGHT">%2$d</xliff:g> / <xliff:g id="WIDTH">%1$d</xliff:g>ର ଫୋଲ୍ଡର ଖୋଲାଗଲା"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"ଫୋଲ୍ଡର୍‌ ବନ୍ଦ କରିବାକୁ ଟାପ୍‌ କରନ୍ତୁ"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"ନାମ ବଦଳାଇବା ସେଭ୍ କରିବାକୁ ଟାପ୍‌ କରନ୍ତୁ"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"ଫୋଲ୍ଡରର ନାମ <xliff:g id="NAME">%1$s</xliff:g>କୁ ବଦଳାଗଲା"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"ଫୋଲ୍ଡର୍: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ଆଇଟମଗୁଡ଼ିକ"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"ଫୋଲ୍ଡର୍: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> କିମ୍ବା ଅଧିକ ଆଇଟମ୍"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"ବେନାମୀ ଫୋଲ୍ଡର"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"ଆପ ପେୟାର: <xliff:g id="APP1">%1$s</xliff:g> ଏବଂ <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ୱାଲପେପର ଏବଂ ଷ୍ଟାଇଲ"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"ହୋମ ସ୍କ୍ରିନକୁ ଏଡିଟ କରନ୍ତୁ"</string>
@@ -160,9 +166,8 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"ୱିଜେଟ୍ ତାଲିକା ବନ୍ଦ ହୋଇଛି"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"ହୋମ ସ୍କ୍ରିନରେ ଯୋଗ କରନ୍ତୁ"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ଆଇଟମ୍‌କୁ ଏଠାକୁ ଘୁଞ୍ଚାନ୍ତୁ"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"ହୋମ ସ୍କ୍ରିନରେ ଆଇଟମ ଯୋଗ କରାଗଲା"</string>
     <string name="item_removed" msgid="851119963877842327">"ଆଇଟମକୁ କାଢ଼ି ଦିଆଯାଇଛି"</string>
-    <string name="undo" msgid="4151576204245173321">"ପୂର୍ବବତ କରନ୍ତୁ"</string>
+    <string name="undo" msgid="4151576204245173321">"ଅନଡୁ କରନ୍ତୁ"</string>
     <string name="action_move" msgid="4339390619886385032">"ଆଇଟମ୍‌ ଘୁଞ୍ଚାନ୍ତୁ"</string>
     <string name="move_to_empty_cell_description" msgid="5254852678218206889">"<xliff:g id="STRING">%3$s</xliff:g>ରେ ଧାଡି <xliff:g id="NUMBER_0">%1$s</xliff:g> ସ୍ତମ୍ଭ <xliff:g id="NUMBER_1">%2$s</xliff:g>କୁ ମୁଭ କରନ୍ତୁ"</string>
     <string name="move_to_position" msgid="6750008980455459790">"<xliff:g id="NUMBER">%1$s</xliff:g> ସ୍ଥିତିକୁ ନିଅନ୍ତୁ"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"ବନ୍ଦ କରନ୍ତୁ"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"ବ୍ୟକ୍ତିଗତ"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"ୱାର୍କ"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"ୱର୍କ ପ୍ରୋଫାଇଲ୍‌"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"ୱାର୍କ ଆପ୍ସକୁ ବେଜ କରାଯାଇଛି ଏବଂ ସେଗୁଡ଼ିକ ଆପଣଙ୍କ IT ଆଡମିନଙ୍କୁ ଦେଖାଯାଉଛି"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"ବୁଝିଗଲି"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index feda5b5..3904473 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -50,7 +50,7 @@
     <string name="add_to_home_screen" msgid="9168649446635919791">"ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> ਵਿਜੇਟ ਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕੀਤਾ ਗਿਆ"</string>
     <string name="suggested_widgets_header_title" msgid="1844314680798145222">"ਸੁਝਾਅ"</string>
-    <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ਲੋੜੀਂਦੀਆਂ"</string>
+    <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"ਲੋੜੀਂਦੀਆਂ ਐਪਾਂ ਦੇ ਵਿਜੇਟ"</string>
     <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>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ਨਾਲ ਮੇਲ ਖਾਂਦੀਆਂ ਕੋਈ ਐਪਾਂ ਨਹੀਂ ਮਿਲੀਆਂ"</string>
     <string name="label_application" msgid="8531721983832654978">"ਐਪ"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"ਸਾਰੀਆਂ ਐਪਾਂ"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"ਐਪਾਂ ਦੀ ਸੂਚੀ"</string>
     <string name="notifications_header" msgid="1404149926117359025">"ਸੂਚਨਾਵਾਂ"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"ਕਿਸੇ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਲਿਜਾਉਣ ਲਈ ਸਪੱਰਸ਼ ਕਰਕੇ ਦਬਾਈ ਰੱਖੋ।"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"ਕਿਸੇ ਸ਼ਾਰਟਕੱਟ ਨੂੰ ਲਿਜਾਉਣ ਲਈ ਡਬਲ ਟੈਪ ਕਰਕੇ ਦਬਾਈ ਰੱਖੋ ਜਾਂ ਵਿਉਂਤੀਆਂ ਕਾਰਵਾਈਆਂ ਵਰਤੋ।"</string>
@@ -93,7 +94,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>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"ਸਫ਼ਾ %2$d ਦਾ %1$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"ਹੋਮ ਸਕ੍ਰੀਨ %2$d ਦੀ %1$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"ਨਵਾਂ ਹੋਮ ਸਕ੍ਰੀਨ ਸਫ਼ਾ"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"ਫੋਲਡਰ ਖੋਲ੍ਹਿਆ, <xliff:g id="WIDTH">%1$d</xliff:g> ਬਾਇ <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"ਫੋਲਡਰ ਬੰਦ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"ਬਦਲੇ ਗਏ ਨਾਮ ਨੂੰ ਰੱਖਿਅਤ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"ਫੋਲਡਰ ਨੂੰ <xliff:g id="NAME">%1$s</xliff:g> ਮੁੜ ਨਾਮ ਦਿੱਤਾ ਗਿਆ"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"ਫੋਲਡਰ: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ਆਈਟਮਾਂ"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"ਫੋਲਡਰ: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ਜਾਂ ਹੋਰ ਆਈਟਮਾਂ"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"ਬੇਨਾਮ ਫੋਲਡਰ"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"ਐਪ ਜੋੜਾਬੱਧ: <xliff:g id="APP1">%1$s</xliff:g> ਅਤੇ <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"ਵਾਲਪੇਪਰ ਅਤੇ ਸਟਾਈਲ"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"ਹੋਮ ਸਕ੍ਰੀਨ ਦਾ ਸੰਪਾਦਨ ਕਰੋ"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"ਵਿਜੇਟਾਂ ਦੀ ਸੂਚੀ ਬੰਦ ਕੀਤੀ ਗਈ"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"ਹੋਮ ਸਕ੍ਰੀਨ \'ਤੇ ਸ਼ਾਮਲ ਕਰੋ"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ਆਈਟਮ ਨੂੰ ਇੱਥੇ ਮੂਵ ਕਰੋ"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"ਆਈਟਮ ਨੂੰ ਹੋਮ ਸਕ੍ਰੀਨ ਵਿੱਚ ਜੋੜਿਆ ਗਿਆ"</string>
     <string name="item_removed" msgid="851119963877842327">"ਆਈਟਮ ਹਟਾਈ ਗਈ"</string>
     <string name="undo" msgid="4151576204245173321">"ਅਣਕੀਤਾ ਕਰੋ"</string>
     <string name="action_move" msgid="4339390619886385032">"ਆਈਟਮ ਨੂੰ ਮੂਵ ਕਰੋ"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"ਬੰਦ ਕਰੋ"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"ਨਿੱਜੀ"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"ਕੰਮ ਸੰਬੰਧੀ"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"ਕਾਰਜ ਪ੍ਰੋਫਾਈਲ"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"ਕੰਮ ਸੰਬੰਧੀ ਐਪਾਂ ਨੂੰ ਬੈਜ ਕੀਤਾ ਗਿਆ ਹੈ ਅਤੇ ਇਹ ਤੁਹਾਡੇ ਆਈ.ਟੀ. ਪ੍ਰਸ਼ਾਸਕ ਨੂੰ ਦਿਸਣਗੀਆਂ"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"ਸਮਝ ਲਿਆ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index bf1299d..f7f71f4 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -46,7 +46,7 @@
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"Szerokość %1$d, wysokość %2$d"</string>
     <string name="widget_preview_context_description" msgid="9045841361655787574">"Widżet <xliff:g id="WIDGET_NAME">%1$s</xliff:g>"</string>
     <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"Widżet <xliff:g id="WIDGET_NAME">%1$s</xliff:g>, %2$d (szerokość), %3$d (wysokość)"</string>
-    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Aby poruszać widżetem po ekranie głównym, kliknij go i przytrzymaj"</string>
+    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Aby przesunąć widżet na ekranie głównym, kliknij go i przytrzymaj"</string>
     <string name="add_to_home_screen" msgid="9168649446635919791">"Dodaj do ekranu głównego"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Widżet <xliff:g id="WIDGET_NAME">%1$s</xliff:g> został dodany do ekranu głównego"</string>
     <string name="suggested_widgets_header_title" msgid="1844314680798145222">"Sugestie"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nie znaleziono aplikacji pasujących do zapytania „<xliff:g id="QUERY">%1$s</xliff:g>”"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikacja"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Wszystkie aplikacje"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Lista aplikacji"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Powiadomienia"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Naciśnij i przytrzymaj, aby przenieść skrót."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Naciśnij dwukrotnie i przytrzymaj, aby przenieść skrót lub użyć działań niestandardowych."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Strona %1$d z %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Ekran główny %1$d z %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nowa strona ekranu głównego"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Folder otwarty, <xliff:g id="WIDTH">%1$d</xliff:g> na <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Kliknij, by zamknąć folder"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Kliknij, by zapisać nową nazwę"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Nazwa folderu zmieniona na <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elementy"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, liczba elementów: <xliff:g id="SIZE">%2$d</xliff:g> lub więcej"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Folder bez nazwy"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Para aplikacji: <xliff:g id="APP1">%1$s</xliff:g> oraz <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Tapeta i styl"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Edytuj ekran główny"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Lista widgetów zamknięta"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Dodaj do ekranu głównego"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Przenieś element tutaj"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Element został dodany do ekranu głównego"</string>
     <string name="item_removed" msgid="851119963877842327">"Element został usunięty"</string>
     <string name="undo" msgid="4151576204245173321">"Cofnij"</string>
     <string name="action_move" msgid="4339390619886385032">"Przenieś element"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Zamknij"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Osobiste"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Służbowe"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil służbowy"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Aplikacje służbowe mają plakietki i są widoczne dla administratora IT"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 17af437..24235d1 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -64,7 +64,7 @@
     <string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"Limpe o texto da caixa de pesquisa"</string>
     <string name="no_widgets_available" msgid="4337693382501046170">"Os widgets e os atalhos não estão disponíveis"</string>
     <string name="no_search_results" msgid="3787956167293097509">"Nenhum widget ou atalho encontrado"</string>
-    <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Pessoais"</string>
+    <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Pessoal"</string>
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Trabalho"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Conversas"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Tomar notas"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nenhuma app correspondente a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"Aplicação"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Todas as apps"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Lista de apps"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notificações"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Toque sem soltar para mover um atalho."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Toque duas vezes sem soltar para mover um atalho ou utilizar ações personalizadas."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Página %1$d de %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Ecrã principal %1$d de %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nova página do ecrã principal"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Ativa"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Minimizada"</string>
     <string name="folder_opened" msgid="94695026776264709">"Pasta aberta, <xliff:g id="WIDTH">%1$d</xliff:g> por <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Tocar para fechar a pasta"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tocar para guardar o nome novo"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Nome de pasta alterado para <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Pasta: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> itens"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Pasta: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ou mais itens"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Pasta sem nome"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Par de apps: <xliff:g id="APP1">%1$s</xliff:g> e <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Imagem fundo/estilo"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Editar ecrã principal"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Lista de widgets fechada."</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Adicionar ao ecrã principal"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Mover o item para aqui"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Item adicionado ao ecrã principal"</string>
     <string name="item_removed" msgid="851119963877842327">"Item removido"</string>
     <string name="undo" msgid="4151576204245173321">"Anular"</string>
     <string name="action_move" msgid="4339390619886385032">"Mover item"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Fechar"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Pessoal"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Trabalho"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de trabalho"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"As apps de trabalho têm um emblema e estão visíveis para o seu administrador de TI"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 10132c9..d0e1178 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nenhum app encontrado que corresponda a \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Todos os apps"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Lista de apps"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notificações"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Toque e mantenha a tela pressionada para mover um atalho."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Toque duas vezes e mantenha a tela pressionada para mover um atalho ou usar ações personalizadas."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Página %1$d de %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Tela inicial %1$d de %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nova página na tela inicial"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Pasta aberta, <xliff:g id="WIDTH">%1$d</xliff:g> por <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Toque para fechar a pasta"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Toque para salvar o novo nome"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Pasta renomeada para <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Pasta: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> itens"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Pasta: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ou mais itens"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Pasta sem nome"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Par de apps: <xliff:g id="APP1">%1$s</xliff:g> e <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Plano de fundo e estilo"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Editar tela inicial"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Lista de widgets fechada"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Adicionar à tela inicial"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Mover item para cá"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Item adicionado à tela inicial"</string>
     <string name="item_removed" msgid="851119963877842327">"Item removido"</string>
     <string name="undo" msgid="4151576204245173321">"Desfazer"</string>
     <string name="action_move" msgid="4339390619886385032">"Mover item"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Fechar"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Pessoais"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Trabalho"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Perfil de trabalho"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Os apps de trabalho são identificados e ficam visíveis para o adm. de TI"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Ok"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 5763edb..9022d6f 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nu s-a găsit nicio aplicație pentru „<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"Aplicație"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Toate aplicațiile"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Listă de aplicații"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Notificări"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Atinge și ține apăsat ca să muți comanda rapidă."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Atinge de două ori și ține apăsat pentru a muta o comandă rapidă sau folosește acțiuni personalizate."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Pagina %1$d din %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Ecranul de pornire %1$d din %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Pagină nouă pe ecranul de pornire"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Dosar deschis, <xliff:g id="WIDTH">%1$d</xliff:g> pe <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Atinge pentru a închide dosarul"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Atinge pentru a salva noul nume"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Dosar redenumit <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Dosar: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> elemente"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Dosar: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> sau mai multe elemente"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Dosar fără nume"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Pereche de aplicații: <xliff:g id="APP1">%1$s</xliff:g> și <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Imagine de fundal și stil"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Editează ecranul de pornire"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Lista de widgeturi este închisă"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Adaugă pe ecranul de pornire"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Mută elementul aici"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Element adăugat pe ecranul de pornire"</string>
     <string name="item_removed" msgid="851119963877842327">"Element eliminat"</string>
     <string name="undo" msgid="4151576204245173321">"Anulează"</string>
     <string name="action_move" msgid="4339390619886385032">"Mută elementul"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Închide"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personale"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Profesionale"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Profil de serviciu"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Aplicațiile pentru lucru sunt marcate și vizibile pentru administratorul IT"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index d9f0b40..a0230e2 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -49,7 +49,7 @@
     <string name="add_item_request_drag_hint" msgid="8730547755622776606">"Нажмите на виджет и удерживайте его, чтобы переместить в нужное место на главном экране."</string>
     <string name="add_to_home_screen" msgid="9168649446635919791">"Добавить на главный экран"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"Виджет \"<xliff:g id="WIDGET_NAME">%1$s</xliff:g>\" добавлен на главный экран"</string>
-    <string name="suggested_widgets_header_title" msgid="1844314680798145222">"Рекомендованные"</string>
+    <string name="suggested_widgets_header_title" msgid="1844314680798145222">"Рекомендации"</string>
     <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"Основное"</string>
     <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"Новости и журналы"</string>
     <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"Развлечения"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"По запросу \"<xliff:g id="QUERY">%1$s</xliff:g>\" ничего не найдено"</string>
     <string name="label_application" msgid="8531721983832654978">"Приложение"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Все приложения"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Список приложений"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Уведомления"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Нажмите и удерживайте для переноса ярлыка."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Чтобы использовать специальные действия или перенести ярлык, нажмите на него дважды и удерживайте."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Стр. %1$d из %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Главный экран %1$d из %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Новый экран"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Активно"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Свернуто"</string>
     <string name="folder_opened" msgid="94695026776264709">"Папка открыта, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Нажмите, чтобы закрыть папку"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Нажмите, чтобы подтвердить переименование"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Папка переименована в \"<xliff:g id="NAME">%1$s</xliff:g>\""</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Папка \"<xliff:g id="NAME">%1$s</xliff:g>\" (объектов: <xliff:g id="SIZE">%2$d</xliff:g>)"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Папка \"<xliff:g id="NAME">%1$s</xliff:g>\" (объектов: <xliff:g id="SIZE">%2$d</xliff:g> или больше)"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Папка без названия"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Одновременное использование двух приложений: <xliff:g id="APP1">%1$s</xliff:g> и <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Обои и стиль"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Изменить главный экран"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Список виджетов закрыт"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Добавить на главный экран"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Переместить элемент сюда"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Элемент добавлен на главный экран"</string>
     <string name="item_removed" msgid="851119963877842327">"Объект убран."</string>
     <string name="undo" msgid="4151576204245173321">"Отменить"</string>
     <string name="action_move" msgid="4339390619886385032">"Переместить элемент"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Закрыть"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Личные"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Рабочие"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Рабочий профиль"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"У рабочих приложений есть специальный значок. Они видны системному администратору."</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"ОК"</string>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 756d6fe..7e64c0d 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" සමග ගැළපෙන යෙදුම් හමු නොවිණි"</string>
     <string name="label_application" msgid="8531721983832654978">"යෙදුම"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"සියලු යෙදුම්"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"යෙදුම් ලැයිස්තුව"</string>
     <string name="notifications_header" msgid="1404149926117359025">"දැනුම්දීම්"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"කෙටි මගක් ගෙන යාමට ස්පර්ශ කර අල්ලාගෙන සිටින්න."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"කෙටි මගක් ගෙන යාමට හෝ අභිරුචි ක්‍රියා භාවිත කිරීමට දෙවරක් තට්ටු කර අල්ලා ගෙන සිටින්න."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%2$d හි %1$d පිටුව"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"මුල් පිටු තිරය %2$d හි %1$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"නව මුල් පිටුව"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"ෆෝල්ඩරය විවෘත විය, <xliff:g id="WIDTH">%1$d</xliff:g> හි <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"ෆෝල්ඩරය වැසීමට තට්ටු කරන්න"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"යළි නම් කිරීම සුරැකීමට තට්ටු කරන්න"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"<xliff:g id="NAME">%1$s</xliff:g> වෙත ෆෝල්ඩරය නැවත නම් කෙරිණි"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"ෆෝල්ඩරය: <xliff:g id="NAME">%1$s</xliff:g>, අයිතම <xliff:g id="SIZE">%2$d</xliff:g>"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"ෆෝල්ඩර: <xliff:g id="NAME">%1$s</xliff:g>, අයිතම <xliff:g id="SIZE">%2$d</xliff:g>ක් හෝ වැඩි ගණනක්"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"නම් නොකළ ෆෝල්ඩරය"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"යෙදුම් යුගල: <xliff:g id="APP1">%1$s</xliff:g> සහ <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"වෝල්පේපරය සහ මෝස්තරය"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"මුල් තිරය සංස්කරණය කරන්න"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"විජට් ලැයිස්තුව වසා ඇත"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"මුල් තිරය වෙත එක් කරන්න"</string>
     <string name="action_move_here" msgid="2170188780612570250">"මෙතනට අයිතමය ගෙන එන්න"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"අයිතමය මුල් තිරය වෙත එකතු කරන ලදි"</string>
     <string name="item_removed" msgid="851119963877842327">"අයිතමය ඉවත් කරන ලදි"</string>
     <string name="undo" msgid="4151576204245173321">"අස් කරන්න"</string>
     <string name="action_move" msgid="4339390619886385032">"අයිතමය ගෙනයන්න"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"වසන්න"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"පුද්ගලික"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"කාර්යාලය"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"කාර්යාල පැතිකඩ"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"කාර්යාල යෙදුම්වලට ලාංඡන යොදා ඇති අතර ඔබගේ IT පරිපාලකට දෘශ්‍යමාන වේ"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"තේරුණා"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 5fc0c41..ba174ad 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nenašli sa žiadne aplikácie zodpovedajúce dopytu <xliff:g id="QUERY">%1$s</xliff:g>"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikácia"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Všetky aplikácie"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Zoznam aplikácií"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Upozornenia"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Pridržaním presuňte skratku."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Dvojitým klepnutím a pridržaním presuňte odkaz alebo použite vlastné akcie."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Stránka %1$d z %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Plocha %1$d z %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nová stránka plochy"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Aktívne"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Minimalizované"</string>
     <string name="folder_opened" msgid="94695026776264709">"Otvorený priečinok, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Priečinok zavriete klepnutím"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Nový názov uložíte klepnutím"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Priečinok bol premenovaný na <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Priečinok: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> položky"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Priečinok: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> alebo viac položiek"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Nepomenovaný priečinok"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Pár aplikácií: <xliff:g id="APP1">%1$s</xliff:g> a <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Tapeta a štýl"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Upraviť plochu"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Zoznam miniaplikácií je zavretý"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Pridať na plochu"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Presunúť položku sem"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Položka bola pridaná na plochu"</string>
     <string name="item_removed" msgid="851119963877842327">"Položka bola odstránená"</string>
     <string name="undo" msgid="4151576204245173321">"Späť"</string>
     <string name="action_move" msgid="4339390619886385032">"Presunúť položku"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Zavrieť"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Osobné"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Pracovné"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Pracovný profil"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Pracovné aplikácie majú odznak a zobrazujú sa správcovi IT"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Dobre"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 5ab0106..9fabdcf 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Ni aplikacij, ki bi ustrezale poizvedbi »<xliff:g id="QUERY">%1$s</xliff:g>«"</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikacija"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Vse aplikacije"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Seznam aplikacij"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Obvestila"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Pridržite bližnjico, da jo premaknete."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Dvakrat se dotaknite bližnjice in jo pridržite, da jo premaknete, ali pa uporabite dejanja po meri."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Stran %1$d od %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Začetni zaslon %1$d od %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Nova stran na začetnem zaslonu"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Aktivno"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Minimirano"</string>
     <string name="folder_opened" msgid="94695026776264709">"Mapa je odprta, <xliff:g id="WIDTH">%1$d</xliff:g> krat <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Dotaknite se, da zaprete mapo"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Dotaknite se, da shranite preimenovanje"</string>
@@ -123,10 +126,11 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Mapa je preimenovana v <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Mapa: <xliff:g id="NAME">%1$s</xliff:g>, št. elementov: <xliff:g id="SIZE">%2$d</xliff:g>"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Mapa: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ali več elementov"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Neimenovana mapa"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Par aplikacij: <xliff:g id="APP1">%1$s</xliff:g> in <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Zaslonsko ozadje in slog"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Urejanje začetnega zaslona"</string>
-    <string name="settings_button_text" msgid="8873672322605444408">"Domače nastavitve"</string>
+    <string name="settings_button_text" msgid="8873672322605444408">"Začetni zaslon"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Onemogočil skrbnik."</string>
     <string name="allow_rotation_title" msgid="7222049633713050106">"Dovoli sukanje začetnega zaslona"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Ko se telefon zasuka"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Seznam pripomočkov se je zaprl"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Dodajanje na začetni zaslon"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Premik elementa sem"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Element je bil dodan na začetni zaslon"</string>
     <string name="item_removed" msgid="851119963877842327">"Element je bil odstranjen."</string>
     <string name="undo" msgid="4151576204245173321">"Razveljavi"</string>
     <string name="action_move" msgid="4339390619886385032">"Premik elementa"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Zapri"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Osebno"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Delo"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Delovni profil"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Delovne aplikacije so označene z značko in vidne skrbniku za IT."</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Razumem"</string>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index ab42407..ac47b0a 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Nuk u gjet asnjë aplikacion që përputhet me \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"Aplikacioni"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Të gjitha aplikacionet"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Lista e aplikacioneve"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Njoftimet"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Prek dhe mbaj shtypur një shkurtore për ta zhvendosur."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Trokit dy herë dhe mbaje shtypur një shkurtore për ta zhvendosur atë ose për të përdorur veprimet e personalizuara."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Faqja: %1$d nga gjithsej %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Ekrani bazë: %1$d nga gjithsej %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Faqja e ekranit të ri kryesor"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Dosja u hap, <xliff:g id="WIDTH">%1$d</xliff:g> me <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Trokit për të mbyllur dosjen"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Trokit për të ruajtur riemërtimin"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Dosja u riemërtua në <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Dosja: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> artikuj"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Dosja: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ose më shumë artikuj"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Dosje pa emër"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Çifti i aplikacioneve: <xliff:g id="APP1">%1$s</xliff:g> dhe <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Imazhi i sfondit dhe stili"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Modifiko ekranin bazë"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Lista e miniaplikacioneve u mbyll"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Shto në ekranin bazë"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Zhvendose artikullin këtu"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Artikulli u shtua tek ekrani bazë"</string>
     <string name="item_removed" msgid="851119963877842327">"Artikulli u hoq"</string>
     <string name="undo" msgid="4151576204245173321">"Zhbëj"</string>
     <string name="action_move" msgid="4339390619886385032">"Zhvendose artikullin"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Mbyll"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personale"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Punë"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Profili i punës"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Aplikacionet e punës janë të shënuara dhe të dukshme për administratorin e teknologjisë së informacionit"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"E kuptova"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 2b2fac8..fea7de5 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Није пронађена ниједна апликација за „<xliff:g id="QUERY">%1$s</xliff:g>“"</string>
     <string name="label_application" msgid="8531721983832654978">"Апликација"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Све апликације"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Листа апликација"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Обавештења"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Додирните и задржите ради померања пречице."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Двапут додирните и задржите да бисте померали пречицу или користите прилагођене радње."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%1$d. страница од %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"%1$d. почетни екран од %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Нова страница почетног екрана"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"Активно"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"Смањено"</string>
     <string name="folder_opened" msgid="94695026776264709">"Фолдер је отворен, <xliff:g id="WIDTH">%1$d</xliff:g> пута <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Додирните да бисте затворили фолдер"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Додирните да бисте сачували преименовање"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Фолдер је преименован у <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Фолдер: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ставке"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Фолдер: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> или више ставки"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Неименовани фолдер"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Пар апликација: <xliff:g id="APP1">%1$s</xliff:g> и <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Позадина и стил"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Измени почетни екран"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Листа виџета је затворена"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Додајте на почетни екран"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Премести ставку овде"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Ставка је додата на почетни екран"</string>
     <string name="item_removed" msgid="851119963877842327">"Ставка је уклоњена"</string>
     <string name="undo" msgid="4151576204245173321">"Опозови"</string>
     <string name="action_move" msgid="4339390619886385032">"Премести ставку"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Затвори"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Лично"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Посао"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Пословни профил"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Пословне апликације су означене значком и ИТ администратор може да их види"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Важи"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 74e35ac..7c676e9 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -64,8 +64,8 @@
     <string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"Rensa texten från sökrutan"</string>
     <string name="no_widgets_available" msgid="4337693382501046170">"Widgetar och genvägar är inte tillgängliga"</string>
     <string name="no_search_results" msgid="3787956167293097509">"Inga widgetar eller genvägar hittades"</string>
-    <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Privat"</string>
-    <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Arbete"</string>
+    <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"Personligt"</string>
+    <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"Jobb"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"Konversationer"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"Anteckna"</string>
     <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"Visa knappen Lägg till"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Inga appar som matchar <xliff:g id="QUERY">%1$s</xliff:g> hittades"</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Alla appar"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Applista"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Aviseringar"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Tryck länge för att flytta en genväg."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Tryck snabbt två gånger och håll kvar för att flytta en genväg eller använda anpassade åtgärder."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Sidan %1$d av %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Startskärmen %1$d av %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Ny sida på startskärmen"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Mappen är öppen, <xliff:g id="WIDTH">%1$d</xliff:g> gånger <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Tryck för att stänga mappen"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Tryck för att spara namnändringen"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Mappen har bytt namn till <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Mapp: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> objekt"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Mapp: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> eller fler objekt"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Namnlös mapp"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Appar som ska användas tillsammans: <xliff:g id="APP1">%1$s</xliff:g> och <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Bakgrund och utseende"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Redigera startskärm"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Widgetslistan har stängts"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Lägg till på startskärmen"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Flytta objekt hit"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Objektet har lagts till på startskärmen"</string>
     <string name="item_removed" msgid="851119963877842327">"Objektet har tagits bort"</string>
     <string name="undo" msgid="4151576204245173321">"Ångra"</string>
     <string name="action_move" msgid="4339390619886385032">"Flytta objekt"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Stäng"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Privat"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Arbete"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Jobbprofil"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Jobbappar är märkta och synliga för IT-administratören"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index d696410..06ba981 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Haikupata programu zozote zinazolingana na \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"Programu"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Programu zote"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Orodha ya programu"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Arifa"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Gusa na ushikilie ili usogeze njia ya mkato."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Gusa mara mbili na ushikilie ili usogeze njia ya mkato au utumie vitendo maalum."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Ukurasa%1$d wa %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Skrini ya mwanzo %1$d ya %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Ukurasa mpya wa skrini ya kwanza"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Folda imefunguliwa, <xliff:g id="WIDTH">%1$d</xliff:g> kwa <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Gusa ili ufunge folda"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Gusa ili ubadilishe jina"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Folda imebadilishwa jina kuwa <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Folda: <xliff:g id="NAME">%1$s</xliff:g>, vipengee <xliff:g id="SIZE">%2$d</xliff:g>"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Folda: <xliff:g id="NAME">%1$s</xliff:g>, vipengee <xliff:g id="SIZE">%2$d</xliff:g> au zaidi"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Folda isiyo na jina"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Jozi ya programu: <xliff:g id="APP1">%1$s</xliff:g> na <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Mandhari na mtindo"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Badilisha Skrini ya Kwanza"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Orodha ya wijeti imefungwa"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Weka kwenye skrini ya kwanza"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Hamishia kipengee hapa"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Kipengee kimeongezwa kwenye skrini ya kwanza"</string>
     <string name="item_removed" msgid="851119963877842327">"Kipengee kimeondolewa"</string>
     <string name="undo" msgid="4151576204245173321">"Tendua"</string>
     <string name="action_move" msgid="4339390619886385032">"Hamisha kipengee"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Funga"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Binafsi"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Kazini"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Wasifu wa kazini"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Programu za kazini zina beji na msimamizi wako wa TEHAMA anaziona"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Nimeelewa"</string>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 0b94252..4bb86da 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" உடன் பொருந்தும் ஆப்ஸ் இல்லை"</string>
     <string name="label_application" msgid="8531721983832654978">"ஆப்ஸ்"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"அனைத்து ஆப்ஸும்"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"ஆப்ஸ் பட்டியல்"</string>
     <string name="notifications_header" msgid="1404149926117359025">"அறிவிப்புகள்"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"ஷார்ட்கட்டை நகர்த்தத் தொட்டுப் பிடிக்கவும்."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"ஷார்ட்கட்டை நகர்த்த இருமுறை தட்டிப் பிடிக்கவும் அல்லது பிரத்தியேகச் செயல்களைப் பயன்படுத்தவும்."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"பக்கம் %1$d / %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"முகப்புத் திரை %1$d of %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"புதிய முகப்புத் திரை பக்கம்"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"திறக்கப்பட்ட ஃபோல்டர், <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"ஃபோல்டரை மூட, தட்டவும்"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"மாற்றிய பெயரைச் சேமிக்க, தட்டவும்"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"ஃபோல்டர் <xliff:g id="NAME">%1$s</xliff:g> என மறுபெயரிடப்பட்டது"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"ஃபோல்டர்: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ஃபைல்கள்"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"ஃபோல்டர்: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> அல்லது அதற்கு அதிகமான ஃபைல்கள்"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"பெயரிடப்படாத ஃபோல்டர்"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"ஆப்ஸ் ஜோடி: <xliff:g id="APP1">%1$s</xliff:g> மற்றும் <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"வால்பேப்பர் &amp; ஸ்டைல்"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"முகப்புத் திரையில் மாற்று"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"விட்ஜெட்கள் பட்டியல் மூடப்பட்டது"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"முகப்புத் திரையில் சேர்"</string>
     <string name="action_move_here" msgid="2170188780612570250">"இங்கு நகர்த்து"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"முகப்புத் திரையில் சேர்க்கப்பட்டது"</string>
     <string name="item_removed" msgid="851119963877842327">"அகற்றப்பட்டது"</string>
     <string name="undo" msgid="4151576204245173321">"செயல்தவிர்"</string>
     <string name="action_move" msgid="4339390619886385032">"நகர்த்து"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"மூடும் பட்டன்"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"தனிப்பட்டவை"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"பணி"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"பணிக் கணக்கு"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"பணி ஆப்ஸில் பேட்ஜ் இடப்பட்டிருக்கும், உங்கள் IT நிர்வாகியால் பணி ஆப்ஸைப் பார்க்க முடியும்"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"முடிந்தது"</string>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index bf9df6a..cd4f456 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -46,14 +46,14 @@
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d వెడల్పు X %2$d ఎత్తు"</string>
     <string name="widget_preview_context_description" msgid="9045841361655787574">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> విడ్జెట్"</string>
     <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"<xliff:g id="WIDGET_NAME">%1$s</xliff:g> విడ్జెట్, %2$d వెడల్పు %3$d ఎత్తు ఉండాలి"</string>
-    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"విడ్జెట్‌ను మొదటి స్క్రీన్‌లో తిప్పడానికి దాన్ని తాకి, &amp; నొక్కి పట్టుకోండి"</string>
+    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"విడ్జెట్‌ను మొదటి స్క్రీన్‌లో అటు, ఇటు కదపడానికి దాన్ని తాకి, నొక్కి పట్టుకోండి"</string>
     <string name="add_to_home_screen" msgid="9168649446635919791">"మొదటి స్క్రీన్‌కు జోడించండి"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"మొదటి స్క్రీన్‌కు <xliff:g id="WIDGET_NAME">%1$s</xliff:g> విడ్జెట్ జోడించబడింది"</string>
     <string name="suggested_widgets_header_title" msgid="1844314680798145222">"సూచనలు"</string>
     <string name="productivity_widget_recommendation_category_label" msgid="3811812719618323750">"నిత్యావసరాలు"</string>
     <string name="news_widget_recommendation_category_label" msgid="6756167867113741310">"వార్తలు &amp; మ్యాగజైన్లు"</string>
     <string name="entertainment_widget_recommendation_category_label" msgid="3973107268630717874">"వినోదం"</string>
-    <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"సామాజికం"</string>
+    <string name="social_widget_recommendation_category_label" msgid="689147679536384717">"సోషల్ మీడియా"</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{# విడ్జెట్}other{# విడ్జెట్‌లు}}"</string>
@@ -64,8 +64,8 @@
     <string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"సెర్చ్ బాక్స్ నుండి టెక్స్ట్‌ను క్లియర్ చేయండి"</string>
     <string name="no_widgets_available" msgid="4337693382501046170">"విడ్జెట్‌లు, షార్ట్‌కట్‌లు అందుబాటులో లేవు"</string>
     <string name="no_search_results" msgid="3787956167293097509">"విడ్జెట్‌లు లేదా షార్ట్‌కట్‌లు కనుగొనబడలేదు"</string>
-    <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"వ్యక్తిగత గ్యాడ్జెట్స్"</string>
-    <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"ఆఫీస్"</string>
+    <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"వ్యక్తిగత విడ్జెట్స్"</string>
+    <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"వర్క్ విడ్జెట్స్"</string>
     <string name="widget_category_conversations" msgid="8894438636213590446">"సంభాషణలు"</string>
     <string name="widget_category_note_taking" msgid="3469689394504266039">"నోట్-టేకింగ్"</string>
     <string name="widget_cell_tap_to_show_add_button_label" msgid="4354194214317043581">"యాడ్‌ చేసే (జోడించే) బటన్‌ను చూపండి"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\"కి మ్యాచ్ అయ్యే అప్లికేషన్‌లేవీ కనుగొనబడలేదు"</string>
     <string name="label_application" msgid="8531721983832654978">"యాప్"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"అన్ని యాప్‌లు"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"యాప్‌ల లిస్ట్"</string>
     <string name="notifications_header" msgid="1404149926117359025">"నోటిఫికేషన్‌లు"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"షార్ట్‌కట్‌ను తరలించడానికి తాకి &amp; నొక్కి ఉంచు."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"షార్ట్‌కట్‌ను తరలించడానికి లేదా అనుకూల చర్యలను ఉపయోగించడానికి రెండుసార్లు నొక్కండి &amp; హోల్డ్ చేయండి."</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%2$dలో %1$dవ పేజీ"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"%2$dలో %1$dవ హోమ్ స్క్రీన్"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"కొత్త హోమ్ స్క్రీన్ పేజీ"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"యాక్టివ్"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"కుదించబడింది"</string>
     <string name="folder_opened" msgid="94695026776264709">"ఫోల్డర్ తెరవబడింది, <xliff:g id="WIDTH">%1$d</xliff:g> X <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"ఫోల్డర్‌ను మూసివేయడానికి నొక్కండి"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"పేరు మార్పును సేవ్ చేయడానికి నొక్కండి"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"ఫోల్డర్ పేరు <xliff:g id="NAME">%1$s</xliff:g>గా మార్చబడింది"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"ఫోల్డర్: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> ఐటెమ్‌లు"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"ఫోల్డర్: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> లేదా అంతకంటే ఎక్కువ ఐటెమ్‌లు"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"పేరు లేని ఫోల్డర్"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"యాప్ పెయిర్: <xliff:g id="APP1">%1$s</xliff:g>, <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"వాల్‌పేపర్ &amp; స్టయిల్"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"మొదటి స్క్రీన్‌ను ఎడిట్ చేయండి"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"విడ్జెట్‌ల లిస్ట్‌ మూసివేయబడింది"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"మొదటి స్క్రీన్‌కు జోడించండి"</string>
     <string name="action_move_here" msgid="2170188780612570250">"అంశాన్ని ఇక్కడికి తరలించు"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"అంశం హోమ్‌స్క్రీన్‌కి జోడించబడింది"</string>
     <string name="item_removed" msgid="851119963877842327">"ఐటెమ్ తీసివేయబడింది"</string>
     <string name="undo" msgid="4151576204245173321">"చర్య రద్దు"</string>
     <string name="action_move" msgid="4339390619886385032">"అంశాన్ని తరలించు"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"మూసివేస్తుంది"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"వ్యక్తిగతం"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"వర్క్"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"కార్యాలయ ప్రొఫైల్"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"వర్క్ యాప్‌లకు బ్యాడ్జ్ ఉంటుంది, అవి మీ IT అడ్మిన్‌కు కనిపిస్తాయి"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"అర్థమైంది"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 08944ed..1aa4a97 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"ไม่พบแอปที่ตรงกับ \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"แอป"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"แอปทั้งหมด"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"รายชื่อแอป"</string>
     <string name="notifications_header" msgid="1404149926117359025">"การแจ้งเตือน"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"แตะค้างไว้เพื่อย้ายทางลัด"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"แตะสองครั้งค้างไว้เพื่อย้ายทางลัดหรือใช้การดำเนินการที่กำหนดเอง"</string>
@@ -116,6 +117,8 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"หน้า %1$d จาก %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"หน้าจอหลัก %1$d จาก %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"หน้าใหม่ในหน้าจอหลัก"</string>
+    <string name="app_running_state_description" msgid="5645053189564740904">"ใช้งานอยู่"</string>
+    <string name="app_minimized_state_description" msgid="710740620044902509">"ลดขนาดเล็กสุด"</string>
     <string name="folder_opened" msgid="94695026776264709">"เปิดโฟลเดอร์ <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"แตะเพื่อปิดโฟลเดอร์"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"แตะเพื่อบันทึกการเปลี่ยนชื่อ"</string>
@@ -123,6 +126,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"เปลี่ยนชื่อโฟลเดอร์เป็น <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"โฟลเดอร์: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> รายการ"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"โฟลเดอร์: <xliff:g id="NAME">%1$s</xliff:g>, อย่างน้อย <xliff:g id="SIZE">%2$d</xliff:g> รายการ"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"โฟลเดอร์ที่ไม่มีชื่อ"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"คู่แอป: <xliff:g id="APP1">%1$s</xliff:g> และ <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"วอลเปเปอร์และสไตล์"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"แก้ไขหน้าจอหลัก"</string>
@@ -160,7 +164,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"ปิดรายการวิดเจ็ตแล้ว"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"เพิ่มลงในหน้าจอหลัก"</string>
     <string name="action_move_here" msgid="2170188780612570250">"ย้ายรายการมาที่นี่"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"เพิ่มรายการไปยังหน้าจอหลักแล้ว"</string>
     <string name="item_removed" msgid="851119963877842327">"นำรายการออกแล้ว"</string>
     <string name="undo" msgid="4151576204245173321">"เลิกทำ"</string>
     <string name="action_move" msgid="4339390619886385032">"ย้ายรายการ"</string>
@@ -185,6 +188,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"ปิด"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"ส่วนตัว"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"งาน"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"โปรไฟล์งาน"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"แอปงานจะติดป้ายไว้และผู้ดูแลระบบไอทีจะมองเห็น"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"รับทราบ"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index b504adc..b8a0df4 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Walang nahanap na app na tumutugma sa \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"App"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Lahat ng app"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Listahan ng mga app"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Mga Notification"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Pindutin nang matagal para ilipat ang shortcut."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"I-double tap at pindutin nang matagal para ilipat ang shortcut o gumamit ng mga custom na pagkilos."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Pahina %1$d ng %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Home screen %1$d ng %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Bagong page ng home screen"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Binuksan ang folder, <xliff:g id="WIDTH">%1$d</xliff:g> by <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"I-tap upang isara ang folder"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"I-tap upang i-save ang bagong pangalan"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Pinalitan ang pangalan ng folder ng <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> (na) item"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> o higit pang item"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Walang pangalang folder"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Pares ng app: <xliff:g id="APP1">%1$s</xliff:g> at <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper &amp; istilo"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"I-edit ang Home Screen"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Nakasara ang listahan ng mga widget"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Idagdag sa home screen"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Ilipat ang item dito"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Naidagdag sa home screen ang item"</string>
     <string name="item_removed" msgid="851119963877842327">"Naalis na ang item"</string>
     <string name="undo" msgid="4151576204245173321">"I-undo"</string>
     <string name="action_move" msgid="4339390619886385032">"Ilipat ang item"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Isara"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Personal"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Trabaho"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Profile sa trabaho"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"May badge at nakikita ng iyong IT admin ang mga app para sa trabaho"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 094b597..5bd7c52 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -31,7 +31,7 @@
     <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>
-    <string name="new_window_option_taskbar" msgid="6448780542727767211">"Yeni Pencere"</string>
+    <string name="new_window_option_taskbar" msgid="6448780542727767211">"Yeni pencere"</string>
     <string name="manage_windows_option_taskbar" msgid="2294109489960654212">"Pencereleri yönet"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"Uygulama çiftini kaydedin"</string>
     <string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" ile eşleşen uygulama bulunamadı"</string>
     <string name="label_application" msgid="8531721983832654978">"Uygulama"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Tüm uygulamalar"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Uygulama listesi"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Bildirimler"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Kısayolu taşımak için dokunup basılı tutun."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Kısayolu taşımak veya özel işlemleri kullanmak için iki kez dokunup basılı tutun."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Sayfa %1$d / %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Ana ekran %1$d / %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Yeni ana ekran sayfası"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Klasör açıldı, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Klasörü kapatmak için dokunun"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Yeni adın kaydedilmesi için dokunun"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Klasörün adı <xliff:g id="NAME">%1$s</xliff:g> olarak değiştirildi"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Klasör: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> öğe"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Klasör: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> veya daha fazla öğe"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Adsız klasör"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Uygulama çifti: <xliff:g id="APP1">%1$s</xliff:g> ve <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Duvar kağıdı ve stil"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Ana ekranı düzenleyin"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Widget listesi kapalı"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Ana ekrana ekle"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Öğeyi buraya taşı"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Öğe ana ekrana eklendi"</string>
     <string name="item_removed" msgid="851119963877842327">"Öğe silindi"</string>
     <string name="undo" msgid="4151576204245173321">"Geri al"</string>
     <string name="action_move" msgid="4339390619886385032">"Öğeyi taşı"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Kapat"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Kişisel"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"İş"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"İş profili"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"İş uygulamaları rozetle işaretlenmiş olup BT yöneticisi tarafından görülebilir"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Anladım"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index f90998d..798c567 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Немає додатків для запиту \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"Додаток"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Усі додатки"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Список додатків"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Сповіщення"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Натисніть і втримуйте, щоб перемістити ярлик."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Двічі натисніть і втримуйте ярлик, щоб перемістити його або виконати інші дії."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Сторінка %1$d з %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Головний екран %1$d з %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Нова сторінка головного екрана"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Папку відкрито (<xliff:g id="WIDTH">%1$d</xliff:g> х <xliff:g id="HEIGHT">%2$d</xliff:g>)"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Торкніться, щоб закрити папку"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Торкніться, щоб зберегти зміни"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Папку перейменовано на <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Папка \"<xliff:g id="NAME">%1$s</xliff:g>\", елементів: <xliff:g id="SIZE">%2$d</xliff:g>"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Папка \"<xliff:g id="NAME">%1$s</xliff:g>\", елементів: <xliff:g id="SIZE">%2$d</xliff:g> або більше"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Папка без назви"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Одночасне використання двох додатків: <xliff:g id="APP1">%1$s</xliff:g> і <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Оформлення й стиль"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Редагувати головний екран"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Список віджектів закрито"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Додати на головний екран"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Перемістити елемент сюди"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Елемент додано на головний екран"</string>
     <string name="item_removed" msgid="851119963877842327">"Елемент вилучено"</string>
     <string name="undo" msgid="4151576204245173321">"Відмінити"</string>
     <string name="action_move" msgid="4339390619886385032">"Перемістити елемент"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Закрити"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Особисті додатки"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Робочі додатки"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Робочий профіль"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Робочі додатки мають спеціальну позначку. Їх бачить системний адміністратор."</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index 1a90602..ec5129d 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -32,7 +32,7 @@
     <string name="split_app_info_accessibility" msgid="5475288491241414932">"‏%1$s کے لیے ایپ کی معلومات"</string>
     <string name="split_app_usage_settings" msgid="7214375263347964093">"‏%1$s کیلئے استعمال کی ترتیبات"</string>
     <string name="new_window_option_taskbar" msgid="6448780542727767211">"نئی ونڈو"</string>
-    <string name="manage_windows_option_taskbar" msgid="2294109489960654212">"‏‫Windows کا نظم کریں"</string>
+    <string name="manage_windows_option_taskbar" msgid="2294109489960654212">"ونڈوز کا نظم کریں"</string>
     <string name="save_app_pair" msgid="5647523853662686243">"ایپس کے جوڑے کو محفوظ کریں"</string>
     <string name="app_pair_default_title" msgid="4045241727446873529">"<xliff:g id="APP1">%1$s</xliff:g> | <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="app_pair_unlaunchable_at_screen_size" msgid="3446551575502685376">"ایپس کا یہ جوڑا اس آلے پر تعاون یافتہ نہیں ہے"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"\"<xliff:g id="QUERY">%1$s</xliff:g>\" سے مماثل کوئی ایپس نہیں ملیں"</string>
     <string name="label_application" msgid="8531721983832654978">"ایپ"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"سبھی ایپس"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"ایپس کی فہرست"</string>
     <string name="notifications_header" msgid="1404149926117359025">"اطلاعات"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"شارٹ کٹ منتقل کرنے کیلیے ٹچ کریں اور پکڑ کر رکھیں۔"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"شارٹ کٹ کو منتقل کرنے یا حسب ضرورت کارروائیاں استعمال کرنے کے لیے دوبار تھپتھپائیں اور پکڑ کر رکھیں۔"</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"‏صفحہ ‎%1$d از ‎%2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"‏ہوم اسکرین ‎%1$d از ‎%2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"نیا ہوم اسکرین صفحہ"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"فولڈر کھولا گیا، <xliff:g id="WIDTH">%1$d</xliff:g> × <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"فولڈر کو بند کرنے کیلئے تھپتھپائیں"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"نام کی تبدیلی محفوظ کرنے کیلئے تھپتھپائیں"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"فولڈر کا نام تبدیل کر کے <xliff:g id="NAME">%1$s</xliff:g> کر دیا گیا"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"فولڈر: <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="SIZE">%2$d</xliff:g> آئٹمز"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"فولڈر: <xliff:g id="NAME">%1$s</xliff:g>، <xliff:g id="SIZE">%2$d</xliff:g> یا مزید آئٹمز"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"بلا نام فولڈر"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"ایپس کا جوڑا: <xliff:g id="APP1">%1$s</xliff:g> اور <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"وال پیپر اور طرز"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"ہوم اسکرین میں ترمیم کریں"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"ویجیٹس کی فہرست بند کر دی گئی"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"ہوم اسکرین میں شامل کریں"</string>
     <string name="action_move_here" msgid="2170188780612570250">"آئٹم یہاں منتقل کریں"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"آئٹم کو ہوم اسکرین میں شامل کر دیا گیا"</string>
     <string name="item_removed" msgid="851119963877842327">"آئٹم ہٹا دیا گیا"</string>
     <string name="undo" msgid="4151576204245173321">"کالعدم کریں"</string>
     <string name="action_move" msgid="4339390619886385032">"آئٹم منتقل کریں"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"بند کریں"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"ذاتی"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"دفتری"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"دفتری پروفائل"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"‏ورک ایپس پر بَیج لگا ہوتا ہے اور آپ کا IT منتظم انہیں دیکھ سکتا ہے"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"سمجھ آ گئی"</string>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index 62fede8..48068d6 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"“<xliff:g id="QUERY">%1$s</xliff:g>” bilan mos hech qanday ilova topilmadi"</string>
     <string name="label_application" msgid="8531721983832654978">"Ilova"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Barcha ilovalar"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Ilovalar roʻyxati"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Bildirishnomalar"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Yorliqni bosib turgan holatda suring."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Ikki marta bosing va yorliqni bosib turgan holatda suring yoki maxsus amaldan foydalaning."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"%2$ddan %1$d ta sahifa"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Uy ekrani %2$ddan %1$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Yangi bosh ekran sahifasi"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Jild ochildi, <xliff:g id="WIDTH">%1$d</xliff:g> ga <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Jildni yopish uchun ustiga bosing"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"O‘zgarishni saqlash uchun ustiga bosing"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Jild nomi <xliff:g id="NAME">%1$s</xliff:g>ga o‘zgartirildi"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Jild: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> fayllar"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Jild: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> va undan ortiq fayllar"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Nomsiz jild"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Ilovani juftlash: <xliff:g id="APP1">%1$s</xliff:g> va <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Fon rasmi va uslubi"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Bosh ekranni tahrirlash"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Vidjetlar ro‘yxati yopildi"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Bosh ekranga chiqarish"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Obyektni bu yerga ko‘chirish"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Obyekt bosh ekranga qo‘shildi"</string>
     <string name="item_removed" msgid="851119963877842327">"Element olib tashlandi"</string>
     <string name="undo" msgid="4151576204245173321">"Qaytarish"</string>
     <string name="action_move" msgid="4339390619886385032">"Obyektni ko‘chirib o‘tkazish"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Yopish"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Shaxsiy"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Ish"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Ish profili"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Ishga oid ilovalarning maxsus belgisi bor hamda ular administratoringizga koʻrinadi"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 8e5f75c..5b88099 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Không tìm thấy ứng dụng nào phù hợp với \"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"Ứng dụng"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Tất cả ứng dụng"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Danh sách ứng dụng"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Thông báo"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Chạm và giữ để di chuyển một lối tắt."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Nhấn đúp và giữ để di chuyển một lối tắt hoặc sử dụng các thao tác tùy chỉnh."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Trang %1$d / %2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Màn hình chính %1$d / %2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Trang màn hình chính mới"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Đã mở thư mục, <xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Nhấn để đóng thư mục"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Nhấn để lưu đổi tên"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Đã đổi tên thư mục thành <xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Thư mục: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> mục"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Thư mục: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> mục trở lên"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Thư mục chưa đặt tên"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Cặp ứng dụng: <xliff:g id="APP1">%1$s</xliff:g> và <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Hình nền và phong cách"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Chỉnh sửa Màn hình chính"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Đã đóng danh sách tiện ích"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Thêm vào màn hình chính"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Di chuyển mục vào đây"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Đã thêm mục vào màn hình chính"</string>
     <string name="item_removed" msgid="851119963877842327">"Đã xóa mục"</string>
     <string name="undo" msgid="4151576204245173321">"Hủy"</string>
     <string name="action_move" msgid="4339390619886385032">"Di chuyển mục"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Đóng"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Cá nhân"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Công việc"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Hồ sơ công việc"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Các ứng dụng công việc được gắn huy hiệu và quản trị viên CNTT có thể nhìn thấy"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"OK"</string>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index edc5646..7b51d1d 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -46,7 +46,7 @@
     <string name="widget_accessible_dims_format" msgid="3640149169885301790">"宽 %1$d,高 %2$d"</string>
     <string name="widget_preview_context_description" msgid="9045841361655787574">"“<xliff:g id="WIDGET_NAME">%1$s</xliff:g>”微件"</string>
     <string name="widget_preview_name_and_dims_content_description" msgid="8489038126122831595">"“<xliff:g id="WIDGET_NAME">%1$s</xliff:g>”微件,宽 %2$d,高 %3$d"</string>
-    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"轻触并按住此微件即可在主屏幕上随意移动"</string>
+    <string name="add_item_request_drag_hint" msgid="8730547755622776606">"轻触并按住此微件即可将其拖拽到主屏幕上任意位置"</string>
     <string name="add_to_home_screen" msgid="9168649446635919791">"添加到主屏幕"</string>
     <string name="added_to_home_screen_accessibility_text" msgid="4451545765448884415">"已将“<xliff:g id="WIDGET_NAME">%1$s</xliff:g>”微件添加到主屏幕"</string>
     <string name="suggested_widgets_header_title" msgid="1844314680798145222">"建议"</string>
@@ -62,7 +62,7 @@
     <string name="widget_button_text" msgid="2880537293434387943">"微件"</string>
     <string name="widgets_full_sheet_search_bar_hint" msgid="8484659090860596457">"搜索"</string>
     <string name="widgets_full_sheet_cancel_button_description" msgid="5766167035728653605">"清除搜索框中的文字"</string>
-    <string name="no_widgets_available" msgid="4337693382501046170">"无法使用微件和快捷方式"</string>
+    <string name="no_widgets_available" msgid="4337693382501046170">"没有可用的微件和快捷方式"</string>
     <string name="no_search_results" msgid="3787956167293097509">"未找到任何微件或快捷方式"</string>
     <string name="widgets_full_sheet_personal_tab" msgid="2743540105607120182">"个人"</string>
     <string name="widgets_full_sheet_work_tab" msgid="3767150027110633765">"工作"</string>
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"未找到与“<xliff:g id="QUERY">%1$s</xliff:g>”相符的应用"</string>
     <string name="label_application" msgid="8531721983832654978">"应用"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"所有应用"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"应用列表"</string>
     <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"轻触并按住快捷方式即可移动该快捷方式。"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"点按两次并按住快捷方式即可移动该快捷方式或使用自定义操作。"</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"第%1$d页,共%2$d页"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"主屏幕:第%1$d屏,共%2$d屏"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"主屏幕新页面"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"文件夹已打开,大小为<xliff:g id="WIDTH">%1$d</xliff:g>×<xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"点按可关闭文件夹"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"点按可保存新名称"</string>
@@ -123,8 +128,9 @@
     <string name="folder_renamed" msgid="1794088362165669656">"已将文件夹重命名为“<xliff:g id="NAME">%1$s</xliff:g>”"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"文件夹:<xliff:g id="NAME">%1$s</xliff:g>,<xliff:g id="SIZE">%2$d</xliff:g> 个项目"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"文件夹:<xliff:g id="NAME">%1$s</xliff:g>,<xliff:g id="SIZE">%2$d</xliff:g> 个或更多项目"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"未命名文件夹"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"应用对:“<xliff:g id="APP1">%1$s</xliff:g>”和“<xliff:g id="APP2">%2$s</xliff:g>”"</string>
-    <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"壁纸与个性化"</string>
+    <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"壁纸与风格"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"修改主屏幕"</string>
     <string name="settings_button_text" msgid="8873672322605444408">"主屏幕设置"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"已被您的管理员停用"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"微件列表已关闭"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"添加到主屏幕"</string>
     <string name="action_move_here" msgid="2170188780612570250">"将项目移至此处"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"已将项目添加到主屏幕"</string>
     <string name="item_removed" msgid="851119963877842327">"项目已移除"</string>
     <string name="undo" msgid="4151576204245173321">"撤消"</string>
     <string name="action_move" msgid="4339390619886385032">"移动项目"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"关闭"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"个人"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"工作"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"工作资料"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"工作应用都有相应的标志,且您的 IT 管理员可以看到它们"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"知道了"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 7f4ac59..0dd1801 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"找不到與「<xliff:g id="QUERY">%1$s</xliff:g>」相符的應用程式"</string>
     <string name="label_application" msgid="8531721983832654978">"應用程式"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"所有應用程式"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"應用程式清單"</string>
     <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"輕觸並按住即可移動捷徑。"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"㩒兩下之後㩒住,就可以郁捷徑或者用自訂操作。"</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"第 %1$d 頁,共 %2$d 頁"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"主畫面 %1$d,共 %2$d 個"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"新主畫面頁面"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"資料夾已開啟 (<xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>)"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"輕按即可關閉資料夾"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"輕按即可儲存新名稱"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"資料夾已重新命名為「<xliff:g id="NAME">%1$s</xliff:g>」"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"資料夾:<xliff:g id="NAME">%1$s</xliff:g>,<xliff:g id="SIZE">%2$d</xliff:g> 個項目"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"資料夾:<xliff:g id="NAME">%1$s</xliff:g>,<xliff:g id="SIZE">%2$d</xliff:g> 個或以上的項目"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"未命名的資料夾"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"應用程式配對:<xliff:g id="APP1">%1$s</xliff:g> 和 <xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"桌布和樣式"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"編輯主畫面"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"已經關閉嘅小工具清單"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"加去主畫面"</string>
     <string name="action_move_here" msgid="2170188780612570250">"移動項目至這裡"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"已將項目加入至主畫面"</string>
     <string name="item_removed" msgid="851119963877842327">"項目已移除"</string>
     <string name="undo" msgid="4151576204245173321">"復原"</string>
     <string name="action_move" msgid="4339390619886385032">"移動項目"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"關閉"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"個人"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"工作"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"工作設定檔"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"工作應用程式均加有標誌。你的 IT 管理員可以看到這些應用程式"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"知道了"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index f527a2a..424939b 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"找不到與「<xliff:g id="QUERY">%1$s</xliff:g>」相符的應用程式"</string>
     <string name="label_application" msgid="8531721983832654978">"應用程式"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"所有應用程式"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"應用程式清單"</string>
     <string name="notifications_header" msgid="1404149926117359025">"通知"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"按住即可移動捷徑。"</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"輕觸兩下並按住即可移動捷徑或使用自訂操作。"</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"第 %1$d 頁,共 %2$d 頁"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"主畫面:第 %1$d 頁,共 %2$d 頁"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"新的主畫面頁面"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"資料夾已開啟 (<xliff:g id="WIDTH">%1$d</xliff:g> x <xliff:g id="HEIGHT">%2$d</xliff:g>)"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"輕觸即可關閉資料夾"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"輕觸即可儲存新名稱"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"已將資料夾重新命名為「<xliff:g id="NAME">%1$s</xliff:g>」"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"資料夾:<xliff:g id="NAME">%1$s</xliff:g>,<xliff:g id="SIZE">%2$d</xliff:g> 個項目"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"資料夾:<xliff:g id="NAME">%1$s</xliff:g>,<xliff:g id="SIZE">%2$d</xliff:g> 個以上的項目"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"未命名的資料夾"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"應用程式配對:「<xliff:g id="APP1">%1$s</xliff:g>」與「<xliff:g id="APP2">%2$s</xliff:g>」"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"桌布和樣式"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"編輯主畫面"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"已關閉小工具清單"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"新增至主畫面"</string>
     <string name="action_move_here" msgid="2170188780612570250">"將項目移至這裡"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"已將項目新增到主畫面"</string>
     <string name="item_removed" msgid="851119963877842327">"已移除項目"</string>
     <string name="undo" msgid="4151576204245173321">"復原"</string>
     <string name="action_move" msgid="4339390619886385032">"移動項目"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"關閉"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"個人"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"工作"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"工作資料夾"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"工作應用程式會加上標記,且你的 IT 管理員可以看到這類應用程式"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"我知道了"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 3c3a75c..918b7ca 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -82,6 +82,7 @@
     <string name="all_apps_no_search_results" msgid="3200346862396363786">"Azikho izinhlelo zokusebenza ezitholiwe ezifana ne-\"<xliff:g id="QUERY">%1$s</xliff:g>\""</string>
     <string name="label_application" msgid="8531721983832654978">"Uhlelo lokusebenza"</string>
     <string name="all_apps_label" msgid="5015784846527570951">"Wonke ama-app"</string>
+    <string name="all_apps_list_label" msgid="5106226764073070906">"Uhlu lwama-app"</string>
     <string name="notifications_header" msgid="1404149926117359025">"Izaziso"</string>
     <string name="long_press_shortcut_to_add" msgid="5405328730817637737">"Thinta uphinde ubambe ukuze uhambise isinqamuleli."</string>
     <string name="long_accessible_way_to_add_shortcut" msgid="2199537273817090740">"Thepha kabili uphinde ubambe ukuze uhambise isinqamuleli noma usebenzise izenzo ezingokwezifiso."</string>
@@ -116,6 +117,10 @@
     <string name="default_scroll_format" msgid="7475544710230993317">"Ikhasi elingu-%1$d kwangu-%2$d"</string>
     <string name="workspace_scroll_format" msgid="8458889198184077399">"Isikrini sasekhaya esingu-%1$d se-%2$d"</string>
     <string name="workspace_new_page" msgid="257366611030256142">"Ikhasi elisha lesikrini sasekhaya"</string>
+    <!-- no translation found for app_running_state_description (5645053189564740904) -->
+    <skip />
+    <!-- no translation found for app_minimized_state_description (710740620044902509) -->
+    <skip />
     <string name="folder_opened" msgid="94695026776264709">"Ifolda ivuliwe, <xliff:g id="WIDTH">%1$d</xliff:g> nge-<xliff:g id="HEIGHT">%2$d</xliff:g>"</string>
     <string name="folder_tap_to_close" msgid="4625795376335528256">"Thepa ukuze uvale ifolda"</string>
     <string name="folder_tap_to_rename" msgid="4017685068016979677">"Thepha ukuze ulondoloze ukuqamba kabusha"</string>
@@ -123,6 +128,7 @@
     <string name="folder_renamed" msgid="1794088362165669656">"Ifolda iqanjwe kabusha ngo-<xliff:g id="NAME">%1$s</xliff:g>"</string>
     <string name="folder_name_format_exact" msgid="8626242716117004803">"Ifolda: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> izinto"</string>
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Ifolda: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> noma izinto eziningi"</string>
+    <string name="unnamed_folder" msgid="2420192029474044442">"Ifolda engenagama"</string>
     <string name="app_pair_name_format" msgid="8134106404716224054">"Ama-app abhangqwayo: I-<xliff:g id="APP1">%1$s</xliff:g> ne-<xliff:g id="APP2">%2$s</xliff:g>"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Isithombe sangemuva nesitayela"</string>
     <string name="edit_home_screen" msgid="8947858375782098427">"Hlela Isikrini Sasekhaya"</string>
@@ -160,7 +166,6 @@
     <string name="widgets_list_closed" msgid="6141506579418771922">"Uhlu lwamawijethi luvaliwe"</string>
     <string name="action_add_to_workspace" msgid="215894119683164916">"Faka kusikrini sasekhaya"</string>
     <string name="action_move_here" msgid="2170188780612570250">"Hambisa into lapha"</string>
-    <string name="item_added_to_workspace" msgid="4211073925752213539">"Into ingezwe kusikrini sasekhaya"</string>
     <string name="item_removed" msgid="851119963877842327">"Into isusiwe"</string>
     <string name="undo" msgid="4151576204245173321">"Susa"</string>
     <string name="action_move" msgid="4339390619886385032">"Hambisa into"</string>
@@ -185,6 +190,10 @@
     <string name="accessibility_close" msgid="2277148124685870734">"Vala"</string>
     <string name="all_apps_personal_tab" msgid="4190252696685155002">"Okomuntu siqu"</string>
     <string name="all_apps_work_tab" msgid="4884822796154055118">"Umsebenzi"</string>
+    <!-- no translation found for all_apps_personal_tab_content_description (6286808898381807242) -->
+    <skip />
+    <!-- no translation found for all_apps_work_tab_content_description (3835637212347968316) -->
+    <skip />
     <string name="work_profile_toggle_label" msgid="3081029915775481146">"Iphrofayela yomsebenzi"</string>
     <string name="work_profile_edu_work_apps" msgid="7895468576497746520">"Ama-app omsebenzi anebheji futhi ayabonakala kumphathi wakho we-IT"</string>
     <string name="work_profile_edu_accept" msgid="6069788082535149071">"Ngiyezwa"</string>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 698877a..a22f943 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -163,8 +163,8 @@
     </declare-styleable>
 
     <declare-styleable name="GridSize">
-        <attr name="minDeviceWidthDp" format="float"/>
-        <attr name="minDeviceHeightDp" format="float"/>
+        <attr name="minDeviceWidthPx" format="float"/>
+        <attr name="minDeviceHeightPx" format="float"/>
         <attr name="numGridRows" format="integer"/>
         <attr name="numGridColumns" format="integer"/>
         <attr name="dbFile" />
@@ -174,7 +174,8 @@
 
     <declare-styleable name="GridDisplayOption">
         <attr name="name" format="string" />
-        <attr name="title" />
+        <attr name="gridTitle" format="string" />
+        <attr name="gridIconId" format="reference"/>
 
         <attr name="numRows" format="integer" />
         <attr name="numColumns" format="integer" />
@@ -267,8 +268,13 @@
         <attr name="allAppsCellSpecsTwoPanelId" format="reference" />
         <!-- defaults to false, if not specified -->
         <attr name="isFixedLandscape" format="boolean" />
-        <!-- defaults to false, if not specified -->
-        <attr name="isOldGrid" format="boolean" />
+        <!-- By default all grid types are enabled -->
+        <attr name="gridType" format="integer">
+            <!-- Enable on phone only -->
+            <flag name="one_grid" value="1" />
+            <!-- Enable on tablets only -->
+            <flag name="non_one_grid" value="2" />
+        </attr>
 
         <!-- By default all categories are enabled -->
         <attr name="deviceCategory" format="integer">
diff --git a/res/values/config.xml b/res/values/config.xml
index f6f3c95..d65580c 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -67,8 +67,6 @@
     <string name="main_process_initializer_class" translatable="false"></string>
     <string name="app_launch_tracker_class" translatable="false"></string>
     <string name="test_information_handler_class" translatable="false"></string>
-    <string name="model_delegate_class" translatable="false"></string>
-    <string name="window_manager_proxy_class" translatable="false"></string>
     <string name="secondary_display_predictions_class" translatable="false"></string>
     <string name="widget_holder_factory_class" translatable="false"></string>
     <string name="taskbar_search_session_controller_class" translatable="false"></string>
@@ -76,17 +74,12 @@
     <string name="taskbar_view_callbacks_factory_class" translatable="false"></string>
     <string name="launcher_restore_event_logger_class" translatable="false"></string>
     <string name="taskbar_edu_tooltip_controller_class" translatable="false"></string>
-    <!--  Used for determining category of a widget presented in widget recommendations. -->
-    <string name="widget_recommendation_category_provider_class" translatable="false"></string>
 
     <!-- Default packages -->
     <string name="wallpaper_picker_package" translatable="false"></string>
     <string name="local_colors_extraction_class" translatable="false"></string>
     <string name="search_session_manager_class" translatable="false"></string>
 
-    <!-- Filters for widgets displayed in the widget picker  -->
-    <string name="widgets_filter_data_provider_class" translatable="false"></string>
-
     <!-- Scalable Grid configuration -->
     <!-- This is a float because it is converted to dp later in DeviceProfile -->
     <dimen name="hotseat_bar_bottom_space_default">48</dimen>
@@ -118,6 +111,12 @@
     <item name="swipe_up_rect_y_damping_ratio" type="dimen" format="float">0.95</item>
     <item name="swipe_up_rect_y_stiffness" type="dimen" format="float">400</item>
 
+    <!-- Expressive Dismiss -->
+    <item name="expressive_dismiss_task_trans_y_damping_ratio" type="dimen" format="float">0.6</item>
+    <item name="expressive_dismiss_task_trans_y_stiffness" type="dimen" format="float">900</item>
+    <item name="expressive_dismiss_task_trans_x_damping_ratio" type="dimen" format="float">0.8</item>
+    <item name="expressive_dismiss_task_trans_x_stiffness" type="dimen" format="float">900</item>
+
     <!-- Taskbar -->
     <!-- This is a float because it is converted to dp later in DeviceProfile -->
     <item name="taskbar_icon_size" type="dimen" format="float">0</item>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 21aabfa..a15c130 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -23,6 +23,11 @@
     <dimen name="dynamic_grid_left_right_margin">8dp</dimen>
     <!-- Minimum amount of next page visible in spring loaded mode -->
     <dimen name="dynamic_grid_spring_loaded_min_next_space_visible">48dp</dimen>
+    <item name="aspect_ratio_portrait" format="float" type="dimen">1.0</item>
+    <!-- 1.05 was the constant we found to validate square-ish devices
+    to load portrait orientation specs for responsive grids -->
+    <item name="aspect_ratio_portrait_and_square" format="float" type="dimen">1.05</item>
+    <item name="aspect_ratio_landscape" format="float" type="dimen">10</item>
 
     <dimen name="dynamic_grid_cell_border_spacing">16dp</dimen>
     <dimen name="cell_layout_padding">10.77dp</dimen>
@@ -191,7 +196,7 @@
 
     <!-- Widget tray -->
     <dimen name="widget_cell_vertical_padding">8dp</dimen>
-    <dimen name="widget_cell_horizontal_padding">8dp</dimen>
+    <dimen name="widget_cell_horizontal_padding">4dp</dimen>
     <dimen name="widget_cell_title_font_size">14sp</dimen>
     <integer name="widget_cell_title_font_weight">500</integer>
     <dimen name="widget_cell_title_line_height">20sp</dimen>
@@ -307,6 +312,7 @@
 
     <!-- Folders -->
     <dimen name="page_indicator_dot_size">6dp</dimen>
+    <dimen name="page_indicator_gap_width">4dp</dimen>
     <dimen name="page_indicator_size">10dp</dimen>
 
 
@@ -439,7 +445,6 @@
     <dimen name="taskbar_running_app_indicator_height">0dp</dimen>
     <dimen name="taskbar_running_app_indicator_width">0dp</dimen>
     <dimen name="taskbar_running_app_indicator_top_margin">0dp</dimen>
-    <dimen name="taskbar_minimized_app_indicator_width">0dp</dimen>
 
     <!-- Transient taskbar (placeholders to compile in Launcher3 without Quickstep) -->
     <dimen name="transient_taskbar_padding">0dp</dimen>
@@ -480,6 +485,7 @@
     <dimen name="task_thumbnail_icon_drawable_size_grid">0dp</dimen>
     <dimen name="task_thumbnail_icon_menu_drawable_touch_size">0dp</dimen>
     <dimen name="task_menu_edge_padding">0dp</dimen>
+    <dimen name="task_dismiss_max_undershoot">0dp</dimen>
     <dimen name="overview_task_margin">0dp</dimen>
     <dimen name="overview_actions_height">0dp</dimen>
     <dimen name="overview_actions_button_spacing">0dp</dimen>
@@ -488,8 +494,6 @@
     <dimen name="overview_grid_side_margin">0dp</dimen>
     <dimen name="overview_grid_row_spacing">0dp</dimen>
     <dimen name="overview_page_spacing">0dp</dimen>
-    <dimen name="overview_top_margin_grid_only">0dp</dimen>
-    <dimen name="overview_bottom_margin_grid_only">0dp</dimen>
 
     <dimen name="split_placeholder_size">72dp</dimen>
     <dimen name="split_placeholder_inset">16dp</dimen>
diff --git a/res/values/id.xml b/res/values/id.xml
index 67692d8..78b8308 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -79,6 +79,7 @@
 
     <item type="id" name="saved_clip_children_tag_id" />
     <item type="id" name="saved_clip_to_padding_tag_id" />
+    <item type="id" name="perform_a11y_action_on_launcher_state_normal_tag" />
 
     <item type="id" name="saved_floating_widget_foreground" />
     <item type="id" name="saved_floating_widget_background" />
diff --git a/res/values/strings.xml b/res/values/strings.xml
index cdfbefe..cc740a5 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -190,6 +190,8 @@
     <!-- Label for the header text of the All Apps section in All Apps view, used to separate Predicted Apps and Actions section from All Apps section. [CHAR_LIMIT=50] -->
     <string name="all_apps_label">All apps</string>
 
+    <string name="all_apps_list_label">Apps list</string>
+
     <!-- Popup items -->
     <!-- Text to display as the header above notifications. [CHAR_LIMIT=30] -->
     <string name="notifications_header">Notifications</string>
@@ -212,6 +214,7 @@
     <string name="all_apps_button_personal_label">Personal apps list</string>
     <string name="all_apps_button_work_label">Work apps list</string>
 
+    <!-- System shortcuts -->
     <!-- Label for remove drop target (from the homescreen only).
          May appear next to uninstall_drop_target_label [CHAR_LIMIT=20] -->
     <string name="remove_drop_target_label">Remove</string>
@@ -288,6 +291,9 @@
     <!-- Description for a new page on homescreen[CHAR_LIMIT=none] -->
     <string name="workspace_new_page">New home screen page</string>
 
+    <string name="app_running_state_description">Active</string>
+    <string name="app_minimized_state_description">Minimized</string>
+
     <!-- Folder accessibility -->
     <!-- The format string for when a folder is opened, speaks the dimensions -->
     <string name="folder_opened">Folder opened, <xliff:g id="width" example="5">%1$d</xliff:g> by <xliff:g id="height" example="3">%2$d</xliff:g></string>
@@ -303,6 +309,8 @@
     <string name="folder_name_format_exact">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g>, <xliff:g id="size" example="2">%2$d</xliff:g> items</string>
     <!-- Folder name format when folder has 4 or more items shown in preview-->
     <string name="folder_name_format_overflow">Folder: <xliff:g id="name" example="Games">%1$s</xliff:g>, <xliff:g id="size" example="2">%2$d</xliff:g> or more items</string>
+    <!-- Accessibility announement for unnamed folders -->
+    <string name="unnamed_folder">Unnamed folder</string>
 
     <!-- App pair accessibility -->
     <!-- App pair name -->
@@ -401,9 +409,6 @@
     <!-- Accessibility action to move item to the current location. [CHAR_LIMIT=30] -->
     <string name="action_move_here">Move item here</string>
 
-    <!-- Accessibility confirmation for item added to workspace. -->
-    <string name="item_added_to_workspace">Item added to home screen</string>
-
     <!-- Accessibility confirmation for item removed. [CHAR_LIMIT=50]-->
     <string name="item_removed">Item removed</string>
 
@@ -472,9 +477,12 @@
 
     <!-- Label of tab to indicate personal apps -->
     <string name="all_apps_personal_tab">Personal</string>
-
     <!-- Label of tab to indicate work apps -->
     <string name="all_apps_work_tab">Work</string>
+    <!-- Content description of personal tab to indicate personal apps -->
+    <string name="all_apps_personal_tab_content_description">Personal apps tab</string>
+    <!-- Content description of work tab to indicate work apps -->
+    <string name="all_apps_work_tab_content_description">Work apps tab</string>
 
     <!-- This string is in the work profile tab when a user has All Apps open on their phone. This is a label for a toggle to turn the work profile on and off. "Work profile" means a separate profile on a user's phone that's specifically for their work apps and managed by their company. "Work" is used as an adjective.-->
     <string name="work_profile_toggle_label">Work profile</string>
diff --git a/res/xml/default_workspace_7x3.xml b/res/xml/default_workspace_7x3.xml
new file mode 100644
index 0000000..d17491e
--- /dev/null
+++ b/res/xml/default_workspace_7x3.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<!-- Google-specific version of Launcher3/res/xml/default_workspace.xml -->
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
+
+    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+    <!-- Dialer Messaging Play Chrome Camera -->
+    <favorite
+        launcher:container="-101"
+        launcher:screen="0"
+        launcher:x="0"
+        launcher:y="0"
+        launcher:className="com.google.android.dialer.extensions.GoogleDialtactsActivity"
+        launcher:packageName="com.google.android.dialer" />
+
+    <favorite
+        launcher:container="-101"
+        launcher:screen="1"
+        launcher:x="1"
+        launcher:y="0"
+        launcher:className="com.google.android.apps.messaging.ui.ConversationListActivity"
+        launcher:packageName="com.google.android.apps.messaging" />
+
+    <favorite
+        launcher:container="-101"
+        launcher:screen="2"
+        launcher:x="2"
+        launcher:y="0"
+        launcher:className="com.android.vending.AssetBrowserActivity"
+        launcher:packageName="com.android.vending" />
+
+    <favorite
+        launcher:container="-101"
+        launcher:screen="3"
+        launcher:x="3"
+        launcher:y="0"
+        launcher:className="com.google.android.apps.chrome.Main"
+        launcher:packageName="com.android.chrome" />
+
+    <!-- Resolve camera intent if GoogleCamera is not available e.g. on emulator -->
+    <resolve
+        launcher:container="-101"
+        launcher:screen="4"
+        launcher:x="4"
+        launcher:y="0" >
+        <favorite
+            launcher:className="com.android.camera.CameraLauncher"
+            launcher:packageName="com.google.android.GoogleCamera" />
+        <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
+        <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
+    </resolve>
+
+    <!-- Bottom row -->
+    <!-- [space] [space] [space] [space] [space] -->
+
+</favorites>
diff --git a/res/xml/default_workspace_8x3.xml b/res/xml/default_workspace_8x3.xml
new file mode 100644
index 0000000..d17491e
--- /dev/null
+++ b/res/xml/default_workspace_8x3.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<!-- Google-specific version of Launcher3/res/xml/default_workspace.xml -->
+<favorites xmlns:launcher="http://schemas.android.com/apk/res-auto/com.android.launcher3" >
+
+    <!-- Hotseat (We use the screen as the position of the item in the hotseat) -->
+    <!-- Dialer Messaging Play Chrome Camera -->
+    <favorite
+        launcher:container="-101"
+        launcher:screen="0"
+        launcher:x="0"
+        launcher:y="0"
+        launcher:className="com.google.android.dialer.extensions.GoogleDialtactsActivity"
+        launcher:packageName="com.google.android.dialer" />
+
+    <favorite
+        launcher:container="-101"
+        launcher:screen="1"
+        launcher:x="1"
+        launcher:y="0"
+        launcher:className="com.google.android.apps.messaging.ui.ConversationListActivity"
+        launcher:packageName="com.google.android.apps.messaging" />
+
+    <favorite
+        launcher:container="-101"
+        launcher:screen="2"
+        launcher:x="2"
+        launcher:y="0"
+        launcher:className="com.android.vending.AssetBrowserActivity"
+        launcher:packageName="com.android.vending" />
+
+    <favorite
+        launcher:container="-101"
+        launcher:screen="3"
+        launcher:x="3"
+        launcher:y="0"
+        launcher:className="com.google.android.apps.chrome.Main"
+        launcher:packageName="com.android.chrome" />
+
+    <!-- Resolve camera intent if GoogleCamera is not available e.g. on emulator -->
+    <resolve
+        launcher:container="-101"
+        launcher:screen="4"
+        launcher:x="4"
+        launcher:y="0" >
+        <favorite
+            launcher:className="com.android.camera.CameraLauncher"
+            launcher:packageName="com.google.android.GoogleCamera" />
+        <favorite launcher:uri="#Intent;action=android.media.action.STILL_IMAGE_CAMERA;end" />
+        <favorite launcher:uri="#Intent;action=android.intent.action.CAMERA_BUTTON;end" />
+    </resolve>
+
+    <!-- Bottom row -->
+    <!-- [space] [space] [space] [space] [space] -->
+
+</favorites>
diff --git a/res/xml/device_profiles.xml b/res/xml/device_profiles.xml
index 1d0dbff..5fac66f 100644
--- a/res/xml/device_profiles.xml
+++ b/res/xml/device_profiles.xml
@@ -207,4 +207,45 @@
 
     </grid-option>
 
+    <grid-option
+        launcher:name="fixed_landscape_mode"
+        launcher:numFolderRows="2"
+        launcher:numFolderColumns="3"
+        launcher:numFolderRowsLandscape="2"
+        launcher:folderStyle="@style/FolderStyleDefault"
+        launcher:numHotseatIcons="4"
+        launcher:numExtendedHotseatIcons="8"
+        launcher:numAllAppsColumns="8"
+        launcher:numExtendedAllAppsColumns="8"
+        launcher:workspaceSpecsId="@xml/spec_handheld_workspace_3_row"
+        launcher:allAppsSpecsId="@xml/spec_handheld_all_apps_3_row"
+        launcher:folderSpecsId="@xml/spec_handheld_folder_3_row"
+        launcher:hotseatSpecsId="@xml/spec_handheld_hotseat_3_row"
+        launcher:workspaceCellSpecsId="@xml/spec_handheld_workspace_cell_3_row"
+        launcher:allAppsCellSpecsId="@xml/spec_all_apps_cell_match_workspace"
+        launcher:isScalable="true"
+        launcher:inlineQsb="landscape"
+        launcher:devicePaddingId="@xml/paddings_3_row"
+        launcher:gridSizeSpecsId="@xml/spec_col_count_3_row"
+        launcher:isFixedLandscape="true"
+        launcher:defaultLayoutId="@xml/default_workspace_8x3"
+        launcher:deviceCategory="phone">
+
+        <display-option
+            launcher:name="Fixed Landscape"
+            launcher:minWidthDps="387"
+            launcher:minHeightDps="750"
+            launcher:minCellHeight="108"
+            launcher:minCellWidth="61"
+            launcher:borderSpace="16"
+            launcher:horizontalMargin="22"
+            launcher:iconImageSize="57"
+            launcher:iconSizeLandscape="59"
+            launcher:iconTextSize="12"
+            launcher:allAppsBorderSpace="16"
+            launcher:allAppsCellHeight="104"
+            launcher:canBeDefault="true" />
+
+    </grid-option>
+
 </profiles>
\ No newline at end of file
diff --git a/res/xml/folder_shapes.xml b/res/xml/folder_shapes.xml
deleted file mode 100644
index e60d333..0000000
--- a/res/xml/folder_shapes.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2019 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.
--->
-<shapes xmlns:launcher="http://schemas.android.com/apk/res-auto" >
-
-    <Circle launcher:folderIconRadius="1" />
-
-    <!-- Default icon for AOSP -->
-    <RoundedSquare launcher:folderIconRadius="0.16" />
-
-    <!-- Rounded icon from RRO -->
-    <RoundedSquare launcher:folderIconRadius="0.6" />
-
-    <!-- Square icon -->
-    <RoundedSquare launcher:folderIconRadius="0" />
-
-    <TearDrop launcher:folderIconRadius="0.3" />
-    <Squircle launcher:folderIconRadius="0.2" />
-
-</shapes>
\ No newline at end of file
diff --git a/res/xml/paddings_3_row.xml b/res/xml/paddings_3_row.xml
new file mode 100644
index 0000000..147c9d1
--- /dev/null
+++ b/res/xml/paddings_3_row.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<device-paddings xmlns:launcher="http://schemas.android.com/apk/res-auto" >
+
+    <device-padding
+        launcher:maxEmptySpace="100dp">
+        <workspaceTopPadding
+            launcher:a="0"
+            launcher:b="0"/>
+        <workspaceBottomPadding
+            launcher:a="0"
+            launcher:b="0"/>
+        <hotseatBottomPadding
+            launcher:a="0"
+            launcher:b="0"/>
+    </device-padding>
+
+    <device-padding
+        launcher:maxEmptySpace="160dp">
+        <workspaceTopPadding
+            launcher:a="0"
+            launcher:b="0"/>
+        <workspaceBottomPadding
+            launcher:a="0"
+            launcher:b="0"/>
+        <hotseatBottomPadding
+            launcher:a="0"
+            launcher:b="0"/>
+    </device-padding>
+
+    <device-padding
+        launcher:maxEmptySpace="9999dp">
+        <workspaceTopPadding
+            launcher:a="0"
+            launcher:b="0"/>
+        <workspaceBottomPadding
+            launcher:a="0"
+            launcher:b="0"/>
+        <hotseatBottomPadding
+            launcher:a="0"
+            launcher:b="0"/>
+    </device-padding>
+</device-paddings>
\ No newline at end of file
diff --git a/res/xml/spec_all_apps_cell_match_workspace.xml b/res/xml/spec_all_apps_cell_match_workspace.xml
new file mode 100644
index 0000000..5843490
--- /dev/null
+++ b/res/xml/spec_all_apps_cell_match_workspace.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<cellSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <specs launcher:maxAspectRatio="@dimen/aspect_ratio_landscape">
+        <cellSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <iconDrawablePadding launcher:matchWorkspace="true" />
+            <iconSize launcher:matchWorkspace="true" />
+            <iconTextSize launcher:matchWorkspace="true" />
+        </cellSpec>
+    </specs>
+</cellSpecs>
\ No newline at end of file
diff --git a/res/xml/spec_col_count_3_row.xml b/res/xml/spec_col_count_3_row.xml
new file mode 100644
index 0000000..bbde542
--- /dev/null
+++ b/res/xml/spec_col_count_3_row.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2025 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<GridSizeSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <GridSize
+        launcher:numGridColumns="7"
+        launcher:numGridRows="3"
+        launcher:dbFile="launcher_7_by_3.db"
+        launcher:defaultLayoutId="@xml/default_workspace_7x3"
+        launcher:minDeviceWidthPx="0"
+        launcher:minDeviceHeightPx="0"
+        />
+    <GridSize
+        launcher:numGridColumns="8"
+        launcher:numGridRows="3"
+        launcher:dbFile="launcher_8_by_3.db"
+        launcher:defaultLayoutId="@xml/default_workspace_8x3"
+        launcher:minDeviceWidthPx="0"
+        launcher:minDeviceHeightPx="2093"
+        />
+</GridSizeSpecs>
\ No newline at end of file
diff --git a/res/xml/spec_handheld_all_apps_3_row.xml b/res/xml/spec_handheld_all_apps_3_row.xml
new file mode 100644
index 0000000..00b3310
--- /dev/null
+++ b/res/xml/spec_handheld_all_apps_3_row.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<allAppsSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <!-- landscape -->
+    <specs launcher:maxAspectRatio="@dimen/aspect_ratio_landscape">
+        <allAppsSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="0dp" />
+            <endPadding launcher:fixedSize="0dp" />
+            <gutter launcher:matchWorkspace="true" />
+            <cellSize launcher:matchWorkspace="true" />
+        </allAppsSpec>
+
+        <allAppsSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:ofRemainderSpace="0.5" />
+            <endPadding launcher:ofRemainderSpace="0.5" />
+            <gutter launcher:matchWorkspace="true" />
+            <cellSize launcher:matchWorkspace="true" />
+        </allAppsSpec>
+    </specs>
+</allAppsSpecs>
diff --git a/res/xml/spec_handheld_folder_3_row.xml b/res/xml/spec_handheld_folder_3_row.xml
new file mode 100644
index 0000000..2614d96
--- /dev/null
+++ b/res/xml/spec_handheld_folder_3_row.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Handheld folders 7x3 -->
+<folderSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <!-- landscape -->
+    <specs launcher:maxAspectRatio="@dimen/aspect_ratio_landscape">
+        <folderSpec launcher:dimensionType="width" launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <endPadding launcher:fixedSize="16dp" />
+            <gutter launcher:fixedSize="16dp" />
+            <cellSize launcher:fixedSize="66dp" />
+        </folderSpec>
+        <folderSpec launcher:dimensionType="height" launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:fixedSize="16dp" />
+            <!-- mapped to footer height size -->
+            <endPadding launcher:fixedSize="48dp" />
+            <gutter launcher:fixedSize="0dp" />
+            <cellSize launcher:fixedSize="80dp" />
+        </folderSpec>
+    </specs>
+</folderSpecs>
diff --git a/res/xml/spec_handheld_hotseat_3_row.xml b/res/xml/spec_handheld_hotseat_3_row.xml
new file mode 100644
index 0000000..bd47c90
--- /dev/null
+++ b/res/xml/spec_handheld_hotseat_3_row.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<hotseatSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <!-- landscape -->
+    <specs launcher:maxAspectRatio="@dimen/aspect_ratio_landscape">
+        <hotseatSpec
+            launcher:maxAvailableSize="9999dp"
+            launcher:dimensionType="width">
+            <hotseatQsbSpace launcher:fixedSize="0dp" />
+            <edgePadding launcher:fixedSize="0dp" />
+        </hotseatSpec>
+    </specs>
+</hotseatSpecs>
\ No newline at end of file
diff --git a/res/xml/spec_handheld_workspace_3_row.xml b/res/xml/spec_handheld_workspace_3_row.xml
new file mode 100644
index 0000000..8e024d3
--- /dev/null
+++ b/res/xml/spec_handheld_workspace_3_row.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <!-- landscape -->
+    <specs launcher:maxAspectRatio="@dimen/aspect_ratio_landscape">
+        <workspaceSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:ofAvailableSpace="0.01" />
+            <endPadding launcher:ofAvailableSpace="0.055" />
+            <gutter launcher:ofAvailableSpace="0.02" />
+            <cellSize launcher:ofRemainderSpace="1" />
+        </workspaceSpec>
+        <workspaceSpec
+            launcher:dimensionType="width"
+            launcher:maxAvailableSize="9999dp">
+            <startPadding launcher:ofAvailableSpace="0.0660867583" />
+            <endPadding launcher:ofAvailableSpace="0.0660867583" />
+            <gutter launcher:ofAvailableSpace="0.0125" />
+            <cellSize launcher:ofRemainderSpace="1" />
+        </workspaceSpec>
+    </specs>
+</workspaceSpecs>
diff --git a/res/xml/spec_handheld_workspace_cell_3_row.xml b/res/xml/spec_handheld_workspace_cell_3_row.xml
new file mode 100644
index 0000000..607dbb9
--- /dev/null
+++ b/res/xml/spec_handheld_workspace_cell_3_row.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<cellSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+    <!-- portrait TODO: remove portrait specs-->
+    <specs launcher:maxAspectRatio="@dimen/aspect_ratio_portrait">
+        <cellSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <iconDrawablePadding launcher:fixedSize="0dp" />
+            <iconSize launcher:fixedSize="@dimen/iconSize52dp" />
+            <iconTextSize launcher:fixedSize="12sp" />
+        </cellSpec>
+    </specs>
+    <!-- landscape -->
+    <specs launcher:maxAspectRatio="@dimen/aspect_ratio_landscape">
+        <cellSpec
+            launcher:dimensionType="height"
+            launcher:maxAvailableSize="9999dp">
+            <iconDrawablePadding launcher:fixedSize="4dp" />
+            <iconSize launcher:fixedSize="@dimen/iconSize52dp" />
+            <iconTextSize launcher:fixedSize="12sp" />
+        </cellSpec>
+    </specs>
+</cellSpecs>
\ No newline at end of file
diff --git a/shared/Android.bp b/shared/Android.bp
new file mode 100644
index 0000000..6d2f407
--- /dev/null
+++ b/shared/Android.bp
@@ -0,0 +1,31 @@
+// 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 {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Shared between tests and launcher
+android_library {
+    name: "launcher-testing-shared",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    resource_dirs: [],
+    manifest: "AndroidManifest.xml",
+    sdk_version: "current",
+    min_sdk_version: min_launcher3_sdk_version,
+}
diff --git a/tests/multivalentTests/shared/AndroidManifest.xml b/shared/AndroidManifest.xml
similarity index 100%
rename from tests/multivalentTests/shared/AndroidManifest.xml
rename to shared/AndroidManifest.xml
diff --git a/tests/shared/com/android/launcher3/testing/OWNERS b/shared/OWNERS
similarity index 100%
rename from tests/shared/com/android/launcher3/testing/OWNERS
rename to shared/OWNERS
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/ResourceUtils.java b/shared/src/com/android/launcher3/testing/shared/ResourceUtils.java
similarity index 100%
rename from tests/multivalentTests/shared/com/android/launcher3/testing/shared/ResourceUtils.java
rename to shared/src/com/android/launcher3/testing/shared/ResourceUtils.java
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/shared/src/com/android/launcher3/testing/shared/TestProtocol.java
similarity index 96%
rename from tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
rename to shared/src/com/android/launcher3/testing/shared/TestProtocol.java
index 825b52b..cdeab95 100644
--- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/shared/src/com/android/launcher3/testing/shared/TestProtocol.java
@@ -76,8 +76,9 @@
         }
     }
 
-    public static final String TEST_INFO_REQUEST_FIELD = "request";
     public static final String TEST_INFO_RESPONSE_FIELD = "response";
+    public static final String TEST_INFO_PARAM_INDEX = "index";
+    public static final String TEST_INFO_PARAM_CELL_SPAN = "cell-span";
 
     public static final String REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT =
             "home-to-overview-swipe-height";
@@ -124,6 +125,8 @@
             "is-predictive-back-swipe-enabled";
     public static final String REQUEST_ENABLE_TASKBAR_NAVBAR_UNIFICATION =
             "enable-taskbar-navbar-unification";
+    public static final String REQUEST_TASKBAR_SHOWN_ON_HOME =
+            "taskbar-shown-on-home";
     public static final String REQUEST_NUM_ALL_APPS_COLUMNS = "num-all-apps-columns";
     public static final String REQUEST_IS_TWO_PANELS = "is-two-panel";
     public static final String REQUEST_CELL_LAYOUT_BOARDER_HEIGHT = "cell-layout-boarder-height";
@@ -168,7 +171,7 @@
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
     public static final String ICON_MISSING = "b/282963545";
     public static final String REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW = "enable-grid-only-overview";
-    public static final String REQUEST_FLAG_ENABLE_APP_PAIRS = "enable-app-pairs";
+    public static final String REQUEST_IS_RECENTS_WINDOW_ENABLED = "recents-window-enabled";
 
     public static final String REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED =
             "unstash-bubble-bar-if-stashed";
diff --git a/src/android/os/BinderUtils.kt b/src/android/os/BinderUtils.kt
new file mode 100644
index 0000000..0536283
--- /dev/null
+++ b/src/android/os/BinderUtils.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os
+
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.WeakCleanupSet
+import com.android.launcher3.util.WeakCleanupSet.OnOwnerDestroyedCallback
+
+/** Utility methods related to Binder */
+object BinderUtils {
+
+    /** Creates a binder wrapper which is tied to the [lifecycle] */
+    @JvmStatic
+    fun <T : Binder> T.wrapLifecycle(cleanupSet: WeakCleanupSet): Binder =
+        LifecycleBinderWrapper(this, cleanupSet)
+
+    private class LifecycleBinderWrapper<T : Binder>(
+        private var realBinder: T?,
+        cleanupSet: WeakCleanupSet,
+    ) : Binder(realBinder?.interfaceDescriptor), OnOwnerDestroyedCallback {
+
+        init {
+            MAIN_EXECUTOR.execute { cleanupSet.addOnOwnerDestroyedCallback(this) }
+        }
+
+        override fun queryLocalInterface(descriptor: String): IInterface? =
+            realBinder?.queryLocalInterface(descriptor)
+
+        override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean =
+            realBinder?.transact(code, data, reply, flags)
+                ?: throw RemoteException("Original binder cleaned up")
+
+        override fun onOwnerDestroyed() {
+            realBinder = null
+        }
+    }
+}
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 76c0f90..b90200b 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -75,7 +75,8 @@
             TYPE_ADD_TO_HOME_CONFIRMATION,
             TYPE_TASKBAR_OVERLAY_PROXY,
             TYPE_TASKBAR_PINNING_POPUP,
-            TYPE_PIN_IME_POPUP
+            TYPE_PIN_IME_POPUP,
+            TYPE_ONE_GRID_MIGRATION_EDU,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface FloatingViewType {}
@@ -104,6 +105,7 @@
     public static final int TYPE_TASKBAR_OVERLAY_PROXY = 1 << 20;
     public static final int TYPE_TASKBAR_PINNING_POPUP = 1 << 21;
     public static final int TYPE_PIN_IME_POPUP = 1 << 22;
+    public static final int TYPE_ONE_GRID_MIGRATION_EDU = 1 << 23;
 
     public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
             | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
@@ -112,19 +114,20 @@
             | TYPE_ICON_SURFACE | TYPE_DRAG_DROP_POPUP | TYPE_PIN_WIDGET_FROM_EXTERNAL_POPUP
             | TYPE_TASKBAR_EDUCATION_DIALOG | TYPE_TASKBAR_ALL_APPS | TYPE_OPTIONS_POPUP_DIALOG
             | TYPE_ADD_TO_HOME_CONFIRMATION | TYPE_TASKBAR_OVERLAY_PROXY
-            | TYPE_TASKBAR_PINNING_POPUP | TYPE_PIN_IME_POPUP;
+            | TYPE_TASKBAR_PINNING_POPUP | TYPE_PIN_IME_POPUP | TYPE_ONE_GRID_MIGRATION_EDU;
 
     // Type of popups which should be kept open during launcher rebind
     public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
             | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
             | TYPE_ALL_APPS_EDU | TYPE_ICON_SURFACE | TYPE_TASKBAR_EDUCATION_DIALOG
             | TYPE_TASKBAR_ALL_APPS | TYPE_OPTIONS_POPUP_DIALOG | TYPE_TASKBAR_OVERLAY_PROXY
-            | TYPE_PIN_IME_POPUP;
+            | TYPE_PIN_IME_POPUP | TYPE_ONE_GRID_MIGRATION_EDU;
 
     /** Type of popups that should get exclusive accessibility focus. */
     public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER
             & ~TYPE_ALL_APPS_EDU & ~TYPE_TASKBAR_ALL_APPS & ~TYPE_PIN_IME_POPUP
-            & ~TYPE_WIDGET_RESIZE_FRAME;
+            & ~TYPE_WIDGET_RESIZE_FRAME & ~TYPE_ONE_GRID_MIGRATION_EDU & ~TYPE_ON_BOARD_POPUP
+            & ~TYPE_TASKBAR_OVERLAY_PROXY;
 
     // These view all have particular operation associated with swipe down interaction.
     public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET |
diff --git a/src/com/android/launcher3/AppFilter.java b/src/com/android/launcher3/AppFilter.java
index 3db456c..8ee7053 100644
--- a/src/com/android/launcher3/AppFilter.java
+++ b/src/com/android/launcher3/AppFilter.java
@@ -3,10 +3,14 @@
 import android.content.ComponentName;
 import android.content.Context;
 
+import com.android.launcher3.dagger.ApplicationContext;
+
 import java.util.Arrays;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import javax.inject.Inject;
+
 /**
  * Utility class to filter out components from various lists
  */
@@ -14,7 +18,8 @@
 
     private final Set<ComponentName> mFilteredComponents;
 
-    public AppFilter(Context context) {
+    @Inject
+    public AppFilter(@ApplicationContext Context context) {
         mFilteredComponents = Arrays.stream(
                 context.getResources().getStringArray(R.array.filtered_components))
                 .map(ComponentName::unflattenFromString)
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 175d6ec..468cee8 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -261,6 +261,13 @@
         return count;
     }
 
+    private void addProfileId(XmlPullParser parser) {
+        Long profileId = mUserTypeToSerial.get(getAttributeValue(parser, ATTR_USER_TYPE));
+        if (profileId != null) {
+            mValues.put(Favorites.PROFILE_ID, profileId);
+        }
+    }
+
     /**
      * Parses container and screenId attribute from the current tag, and puts it in the out.
      * @param out array of size 2.
@@ -305,10 +312,6 @@
                 convertToDistanceFromEnd(getAttributeValue(parser, ATTR_X), mColumnCount));
         mValues.put(Favorites.CELLY,
                 convertToDistanceFromEnd(getAttributeValue(parser, ATTR_Y), mRowCount));
-        Long profileId = mUserTypeToSerial.get(getAttributeValue(parser, ATTR_USER_TYPE));
-        if (profileId != null) {
-            mValues.put(Favorites.PROFILE_ID, profileId);
-        }
 
         TagParser tagParser = tagParserMap.get(parser.getName());
         if (tagParser == null) {
@@ -382,7 +385,7 @@
         public int parseAndAdd(XmlPullParser parser) {
             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
             final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
-
+            addProfileId(parser);
             if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
                 ActivityInfo info;
                 try {
@@ -431,6 +434,7 @@
         public int parseAndAdd(XmlPullParser parser) {
             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
             final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
+            addProfileId(parser);
             if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
                 if (LOGD) Log.d(TAG, "Skipping invalid <favorite> with no component");
                 return -1;
@@ -452,7 +456,7 @@
         public int parseAndAdd(XmlPullParser parser) {
             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
             final String shortcutId = getAttributeValue(parser, ATTR_SHORTCUT_ID);
-
+            addProfileId(parser);
             try {
                 LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
                 launcherApps.pinShortcuts(packageName, Collections.singletonList(shortcutId),
@@ -482,13 +486,13 @@
         public ComponentName getComponentName(XmlPullParser parser) {
             final String packageName = getAttributeValue(parser, ATTR_PACKAGE_NAME);
             final String className = getAttributeValue(parser, ATTR_CLASS_NAME);
+            addProfileId(parser);
             if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(className)) {
                 return null;
             }
             return new ComponentName(packageName, className);
         }
 
-
         @Override
         public int parseAndAdd(XmlPullParser parser)
                 throws XmlPullParserException, IOException {
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 2e75261..2426a61 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
 import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
 import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_FULLSCREEN_TASK;
@@ -29,17 +30,33 @@
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.util.Log;
+import android.view.ActionMode;
+import android.view.View;
 import android.window.OnBackInvokedDispatcher;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.savedstate.SavedStateRegistry;
+import androidx.savedstate.SavedStateRegistryController;
 
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.logging.StatsLogManager;
+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.ActivityOptionsWrapper;
+import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
+import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.LifecycleHelper;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.ViewCache;
+import com.android.launcher3.util.WeakCleanupSet;
+import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.ScrimView;
 
@@ -52,7 +69,8 @@
 /**
  * Launcher BaseActivity
  */
-public abstract class BaseActivity extends Activity implements ActivityContext {
+public abstract class BaseActivity extends Activity implements ActivityContext,
+        DisplayInfoChangeListener {
 
     private static final String TAG = "BaseActivity";
     static final boolean DEBUG = false;
@@ -85,11 +103,15 @@
     private final ArrayList<MultiWindowModeChangedListener> mMultiWindowModeChangedListeners =
             new ArrayList<>();
 
+    private final SavedStateRegistryController mSavedStateRegistryController =
+            SavedStateRegistryController.create(this);
+    private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
+    private final WeakCleanupSet mCleanupSet = new WeakCleanupSet(this);
+
     protected DeviceProfile mDeviceProfile;
     protected SystemUiController mSystemUiController;
     private StatsLogManager mStatsLogManager;
 
-
     public static final int ACTIVITY_STATE_STARTED = 1 << 0;
     public static final int ACTIVITY_STATE_RESUMED = 1 << 1;
 
@@ -126,6 +148,10 @@
     public @interface ActivityFlags {
     }
 
+    // When starting an action mode, setting this tag will cause the action mode to be cancelled
+    // automatically when user interacts with the launcher.
+    public static final Object AUTO_CANCEL_ACTION_MODE = new Object();
+
     /** Returns a human-readable string for the specified {@link ActivityFlags}. */
     public static String getActivityStateString(@ActivityFlags int flags) {
         StringJoiner result = new StringJoiner("|");
@@ -160,6 +186,14 @@
     private final RunnableList[] mEventCallbacks =
             {new RunnableList(), new RunnableList(), new RunnableList(), new RunnableList()};
 
+    private ActionMode mCurrentActionMode;
+
+    public BaseActivity() {
+        mSavedStateRegistryController.performAttach();
+        registerActivityLifecycleCallbacks(
+                new LifecycleHelper(this, mSavedStateRegistryController, mLifecycleRegistry));
+    }
+
     @Override
     public ViewCache getViewCache() {
         return mViewCache;
@@ -206,6 +240,7 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         registerBackDispatcher();
+        DisplayController.INSTANCE.get(this).addChangeListener(this);
     }
 
     @Override
@@ -253,6 +288,7 @@
     protected void onDestroy() {
         super.onDestroy();
         mEventCallbacks[EVENT_DESTROYED].executeAllAndClear();
+        DisplayController.INSTANCE.get(this).removeChangeListener(this);
     }
 
     @Override
@@ -403,13 +439,83 @@
         writer.println(prefix + "mForceInvisible: " + mForceInvisible);
     }
 
+
+    @Override
+    public void onActionModeStarted(ActionMode mode) {
+        super.onActionModeStarted(mode);
+        mCurrentActionMode = mode;
+    }
+
+    @Override
+    public void onActionModeFinished(ActionMode mode) {
+        super.onActionModeFinished(mode);
+        mCurrentActionMode = null;
+    }
+
+    protected boolean isInAutoCancelActionMode() {
+        return mCurrentActionMode != null && AUTO_CANCEL_ACTION_MODE == mCurrentActionMode.getTag();
+    }
+
+    @Override
+    public boolean finishAutoCancelActionMode() {
+        if (isInAutoCancelActionMode()) {
+            mCurrentActionMode.finish();
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    @NonNull
+    public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
+        ActivityOptionsWrapper wrapper = ActivityContext.super.getActivityLaunchOptions(v, item);
+        addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy);
+        return wrapper;
+    }
+
+    @Override
+    public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) {
+        ActivityOptionsWrapper wrapper =
+                ActivityContext.super.makeDefaultActivityOptions(splashScreenStyle);
+        addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy);
+        return wrapper;
+    }
+
+    protected WindowBounds getMultiWindowDisplaySize() {
+        return WindowBounds.fromWindowMetrics(getWindowManager().getCurrentWindowMetrics());
+    }
+
+    @Override
+    public void onDisplayInfoChanged(Context context, Info info, int flags) {
+        if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.isVerticalBarLayout()) {
+            reapplyUi();
+        }
+    }
+
+    protected void reapplyUi() {}
+
+    @NonNull
+    @Override
+    public SavedStateRegistry getSavedStateRegistry() {
+        return mSavedStateRegistryController.getSavedStateRegistry();
+    }
+
+    @NonNull
+    @Override
+    public Lifecycle getLifecycle() {
+        return mLifecycleRegistry;
+    }
+
+    @Override
+    public WeakCleanupSet getOwnerCleanupSet() {
+        return mCleanupSet;
+    }
+
     public static <T extends BaseActivity> T fromContext(Context context) {
         if (context instanceof BaseActivity) {
             return (T) context;
-        } else if (context instanceof ActivityContextDelegate) {
-            return (T) ((ActivityContextDelegate) context).mDelegate;
-        } else if (context instanceof ContextWrapper) {
-            return fromContext(((ContextWrapper) context).getBaseContext());
+        } else if (context instanceof ContextWrapper cw) {
+            return fromContext(cw.getBaseContext());
         } else {
             throw new IllegalArgumentException("Cannot find BaseActivity in parent tree");
         }
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
deleted file mode 100644
index 3b93cf4..0000000
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3;
-
-import static com.android.launcher3.util.DisplayController.CHANGE_ROTATION;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.view.ActionMode;
-import android.view.View;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.util.ActivityOptionsWrapper;
-import com.android.launcher3.util.DisplayController;
-import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
-import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.OnColorHintListener;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.WallpaperColorHints;
-import com.android.launcher3.util.WindowBounds;
-
-/**
- * Extension of BaseActivity allowing support for drag-n-drop
- */
-@SuppressWarnings("NewApi")
-public abstract class BaseDraggingActivity extends BaseActivity
-        implements OnColorHintListener, DisplayInfoChangeListener {
-
-    // When starting an action mode, setting this tag will cause the action mode to be cancelled
-    // automatically when user interacts with the launcher.
-    public static final Object AUTO_CANCEL_ACTION_MODE = new Object();
-
-    private ActionMode mCurrentActionMode;
-
-    private int mThemeRes = R.style.AppTheme;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        DisplayController.INSTANCE.get(this).addChangeListener(this);
-
-        // Update theme
-        WallpaperColorHints.get(this).registerOnColorHintsChangedListener(this);
-        int themeRes = Themes.getActivityThemeRes(this);
-        if (themeRes != mThemeRes) {
-            mThemeRes = themeRes;
-            setTheme(themeRes);
-        }
-    }
-
-    @MainThread
-    @Override
-    public void onColorHintsChanged(int colorHints) {
-        updateTheme();
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        updateTheme();
-    }
-
-    private void updateTheme() {
-        if (mThemeRes != Themes.getActivityThemeRes(this)) {
-            recreateToUpdateTheme();
-        }
-    }
-
-    protected void recreateToUpdateTheme() {
-        recreate();
-    }
-
-    @Override
-    public void onActionModeStarted(ActionMode mode) {
-        super.onActionModeStarted(mode);
-        mCurrentActionMode = mode;
-    }
-
-    @Override
-    public void onActionModeFinished(ActionMode mode) {
-        super.onActionModeFinished(mode);
-        mCurrentActionMode = null;
-    }
-
-    protected boolean isInAutoCancelActionMode() {
-        return mCurrentActionMode != null && AUTO_CANCEL_ACTION_MODE == mCurrentActionMode.getTag();
-    }
-
-    @Override
-    public boolean finishAutoCancelActionMode() {
-        if (isInAutoCancelActionMode()) {
-            mCurrentActionMode.finish();
-            return true;
-        }
-        return false;
-    }
-
-    public abstract View getRootView();
-
-    public void returnToHomescreen() {
-        // no-op
-    }
-
-    @Override
-    @NonNull
-    public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
-        ActivityOptionsWrapper wrapper = super.getActivityLaunchOptions(v, item);
-        addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy);
-        return wrapper;
-    }
-
-    @Override
-    public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) {
-        ActivityOptionsWrapper wrapper = super.makeDefaultActivityOptions(splashScreenStyle);
-        addEventCallback(EVENT_RESUMED, wrapper.onEndCallback::executeAllAndDestroy);
-        return wrapper;
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        DisplayController.INSTANCE.get(this).removeChangeListener(this);
-        WallpaperColorHints.get(this).unregisterOnColorsChangedListener(this);
-    }
-
-    protected void onDeviceProfileInitiated() {
-    }
-
-    @Override
-    public void onDisplayInfoChanged(Context context, Info info, int flags) {
-        if ((flags & CHANGE_ROTATION) != 0 && mDeviceProfile.isVerticalBarLayout()) {
-            reapplyUi();
-        }
-    }
-
-    @Override
-    public View.OnClickListener getItemOnClickListener() {
-        return ItemClickHandler.INSTANCE;
-    }
-
-    protected abstract void reapplyUi();
-
-    protected WindowBounds getMultiWindowDisplaySize() {
-        return WindowBounds.fromWindowMetrics(getWindowManager().getCurrentWindowMetrics());
-    }
-
-    @Override
-    public boolean isAppBlockedForSafeMode() {
-        return LauncherAppState.getInstance(this).isSafeModeEnabled();
-    }
-}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index c26b612..783e82c 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -20,6 +20,10 @@
 import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL;
 import static android.text.Layout.Alignment.ALIGN_NORMAL;
 
+import static com.android.app.animation.Interpolators.EMPHASIZED;
+import static com.android.launcher3.BubbleTextView.RunningAppState.RUNNING;
+import static com.android.launcher3.BubbleTextView.RunningAppState.NOT_RUNNING;
+import static com.android.launcher3.BubbleTextView.RunningAppState.MINIMIZED;
 import static com.android.launcher3.Flags.enableContrastTiles;
 import static com.android.launcher3.Flags.enableCursorHoverStates;
 import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
@@ -27,11 +31,14 @@
 import static com.android.launcher3.icons.BitmapInfo.FLAG_SKIP_USER_BADGE;
 import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
+import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INCREMENTAL_DOWNLOAD_ACTIVE;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -63,6 +70,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.DrawableRes;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 import androidx.annotation.VisibleForTesting;
@@ -73,7 +81,6 @@
 import com.android.launcher3.dragndrop.DragOptions.PreDragCondition;
 import com.android.launcher3.dragndrop.DraggableView;
 import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.FastBitmapDrawable;
@@ -125,6 +132,9 @@
             StringMatcherUtility.StringMatcher.getInstance();
     private static final int BOLD_TEXT_ADJUSTMENT = FONT_WEIGHT_BOLD - FONT_WEIGHT_NORMAL;
 
+    public static final int LINE_INDICATOR_ANIM_DURATION = 150;
+    private static final float MINIMIZED_APP_INDICATOR_SCALE = 0.5f;
+
     private static final int[] STATE_PRESSED = new int[]{android.R.attr.state_pressed};
 
     private float mScaleForReorderBounce = 1f;
@@ -160,8 +170,38 @@
         }
     };
 
+    private static final Property<BubbleTextView, Integer> LINE_INDICATOR_COLOR_PROPERTY =
+            new Property<>(Integer.class, "lineIndicatorColor") {
+
+                @Override
+                public Integer get(BubbleTextView bubbleTextView) {
+                    return bubbleTextView.mLineIndicatorColor;
+                }
+
+                @Override
+                public void set(BubbleTextView bubbleTextView, Integer color) {
+                    bubbleTextView.mLineIndicatorColor = color;
+                    bubbleTextView.invalidate();
+                }
+            };
+
+    private static final Property<BubbleTextView, Float> LINE_INDICATOR_SCALE_PROPERTY =
+            new Property<>(Float.TYPE, "lineIndicatorScale") {
+
+                @Override
+                public Float get(BubbleTextView bubbleTextView) {
+                    return bubbleTextView.mLineIndicatorScale;
+                }
+
+                @Override
+                public void set(BubbleTextView bubbleTextView, Float scale) {
+                    bubbleTextView.mLineIndicatorScale = scale;
+                    bubbleTextView.invalidate();
+                }
+            };
+
     private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this);
-    private final ActivityContext mActivity;
+    protected final ActivityContext mActivity;
     private FastBitmapDrawable mIcon;
     private DeviceProfile mDeviceProfile;
     private boolean mCenterVertically;
@@ -190,7 +230,6 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private DotInfo mDotInfo;
     private DotRenderer mDotRenderer;
-    private String mCurrentLanguage;
     @ViewDebug.ExportedProperty(category = "launcher", deepExport = true)
     protected DotRenderer.DrawParams mDotParams;
     private Animator mDotScaleAnim;
@@ -198,7 +237,6 @@
 
     // These fields, related to showing running apps, are only used for Taskbar.
     private final int mRunningAppIndicatorWidth;
-    private final int mMinimizedAppIndicatorWidth;
     private final int mRunningAppIndicatorHeight;
     private final int mRunningAppIndicatorTopMargin;
     private final Paint mRunningAppIndicatorPaint;
@@ -206,6 +244,15 @@
     private RunningAppState mRunningAppState;
     private final int mRunningAppIndicatorColor;
     private final int mMinimizedAppIndicatorColor;
+    @ViewDebug.ExportedProperty(category = "launcher")
+    private int mLineIndicatorColor;
+    @ViewDebug.ExportedProperty(category = "launcher")
+    private float mLineIndicatorScale;
+    private int mLineIndicatorAnimStartDelay;
+    private Animator mLineIndicatorAnim;
+
+    private final String mMinimizedStateDescription;
+    private final String mRunningStateDescription;
 
     /**
      * Various options for the running state of an app.
@@ -239,6 +286,9 @@
         super(context, attrs, defStyle);
         mActivity = ActivityContext.lookupContext(context);
         FastBitmapDrawable.setFlagHoverEnabled(enableCursorHoverStates());
+        mMinimizedStateDescription = getContext().getString(
+                R.string.app_minimized_state_description);
+        mRunningStateDescription = getContext().getString(R.string.app_running_state_description);
 
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.BubbleTextView, defStyle, 0);
@@ -284,8 +334,6 @@
 
         mRunningAppIndicatorWidth =
                 getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_width);
-        mMinimizedAppIndicatorWidth =
-                getResources().getDimensionPixelSize(R.dimen.taskbar_minimized_app_indicator_width);
         mRunningAppIndicatorHeight =
                 getResources().getDimensionPixelSize(R.dimen.taskbar_running_app_indicator_height);
         mRunningAppIndicatorTopMargin =
@@ -302,7 +350,6 @@
 
         mDotParams = new DotRenderer.DrawParams();
 
-        mCurrentLanguage = context.getResources().getConfiguration().locale.getLanguage();
         setEllipsize(TruncateAt.END);
         setAccessibilityDelegate(mActivity.getAccessibilityDelegate());
         setTextAlpha(1f);
@@ -335,6 +382,11 @@
         mForceHideDot = false;
         setBackground(null);
 
+        mLineIndicatorColor = Color.TRANSPARENT;
+        mLineIndicatorScale = 0;
+        mLineIndicatorAnimStartDelay = 0;
+        cancelLineIndicatorAnim();
+
         setTag(null);
         if (mIconLoadRequest != null) {
             mIconLoadRequest.cancel();
@@ -367,29 +419,6 @@
         mDotScaleAnim.start();
     }
 
-    @UiThread
-    public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
-        applyFromWorkspaceItem(info, /* animate = */ false, /* staggerIndex = */ 0);
-    }
-
-    @UiThread
-    public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
-        applyFromWorkspaceItem(info, null);
-    }
-
-    /**
-     * Returns whether the newInfo differs from the current getTag().
-     */
-    public boolean shouldAnimateIconChange(WorkspaceItemInfo newInfo) {
-        WorkspaceItemInfo oldInfo = getTag() instanceof WorkspaceItemInfo
-                ? (WorkspaceItemInfo) getTag()
-                : null;
-        boolean changedIcons = oldInfo != null && oldInfo.getTargetComponent() != null
-                && newInfo.getTargetComponent() != null
-                && !oldInfo.getTargetComponent().equals(newInfo.getTargetComponent());
-        return changedIcons && isShown();
-    }
-
     @Override
     public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
         if (delegate instanceof BaseAccessibilityDelegate) {
@@ -403,10 +432,10 @@
     }
 
     @UiThread
-    public void applyFromWorkspaceItem(WorkspaceItemInfo info, PreloadIconDrawable icon) {
+    public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
         applyIconAndLabel(info);
         setItemInfo(info);
-        applyLoadingState(icon);
+
         applyDotState(info, false /* animate */);
         setDownloadStateContentDescription(info, info.getProgressLevel());
     }
@@ -414,17 +443,11 @@
     @UiThread
     public void applyFromApplicationInfo(AppInfo info) {
         applyIconAndLabel(info);
-
-        // We don't need to check the info since it's not a WorkspaceItemInfo
         setItemInfo(info);
 
-
         // Verify high res immediately
         verifyHighRes();
 
-        if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
-            applyProgressLevel();
-        }
         applyDotState(info, false /* animate */);
         setDownloadStateContentDescription(info, info.getProgressLevel());
     }
@@ -456,9 +479,63 @@
 
     /** Updates whether the app this view represents is currently running. */
     @UiThread
-    public void updateRunningState(RunningAppState runningAppState) {
+    public void updateRunningState(RunningAppState runningAppState, boolean animate) {
+        if (runningAppState.equals(mRunningAppState)) {
+            return;
+        }
         mRunningAppState = runningAppState;
-        invalidate();
+        cancelLineIndicatorAnim();
+
+        int color = switch (mRunningAppState) {
+            case NOT_RUNNING -> Color.TRANSPARENT;
+            case RUNNING -> mRunningAppIndicatorColor;
+            case MINIMIZED -> mMinimizedAppIndicatorColor;
+        };
+        float scale = switch (mRunningAppState) {
+            case NOT_RUNNING -> 0;
+            case RUNNING -> 1;
+            case MINIMIZED -> MINIMIZED_APP_INDICATOR_SCALE;
+        };
+
+        if (!animate) {
+            mLineIndicatorColor = color;
+            mLineIndicatorScale = scale;
+            invalidate();
+            return;
+        }
+
+        AnimatorSet lineIndicatorAnim  = new AnimatorSet();
+        mLineIndicatorAnim = lineIndicatorAnim;
+        Animator colorAnimator = ObjectAnimator.ofArgb(this, LINE_INDICATOR_COLOR_PROPERTY, color);
+        Animator scaleAnimator = ObjectAnimator.ofFloat(this, LINE_INDICATOR_SCALE_PROPERTY, scale);
+        lineIndicatorAnim.playTogether(colorAnimator, scaleAnimator);
+
+        lineIndicatorAnim.setInterpolator(EMPHASIZED);
+        lineIndicatorAnim.setStartDelay(mLineIndicatorAnimStartDelay);
+        lineIndicatorAnim.setDuration(LINE_INDICATOR_ANIM_DURATION).start();
+    }
+
+    public void setLineIndicatorAnimStartDelay(int lineIndicatorAnimStartDelay) {
+        mLineIndicatorAnimStartDelay = lineIndicatorAnimStartDelay;
+    }
+
+    private void cancelLineIndicatorAnim() {
+        if (mLineIndicatorAnim != null) {
+            mLineIndicatorAnim.cancel();
+        }
+    }
+
+    /**
+     * Returns state description of this icon.
+     */
+    public String getIconStateDescription() {
+        if (mRunningAppState == MINIMIZED) {
+            return mMinimizedStateDescription;
+        } else if (mRunningAppState == RUNNING) {
+            return mRunningStateDescription;
+        } else {
+            return "";
+        }
     }
 
     protected void setItemInfo(ItemInfoWithIcon itemInfo) {
@@ -468,7 +545,51 @@
     @VisibleForTesting
     @UiThread
     public void applyIconAndLabel(ItemInfoWithIcon info) {
-        int flags = shouldUseTheme() ? FLAG_THEMED : 0;
+        FastBitmapDrawable oldIcon = mIcon;
+        if (!canReuseIcon(info)) {
+            setNonPendingIcon(info);
+        }
+        applyLabel(info);
+        maybeApplyProgressLevel(info, oldIcon);
+    }
+
+    /**
+     * Check if we can reuse icon so that any animation is preserved
+     */
+    private boolean canReuseIcon(ItemInfoWithIcon info) {
+        return mIcon instanceof PreloadIconDrawable p
+                && p.hasNotCompleted() && p.isSameInfo(info.bitmap);
+    }
+
+    /**
+     * Apply progress level to the icon if necessary
+     */
+    private void maybeApplyProgressLevel(ItemInfoWithIcon info, FastBitmapDrawable oldIcon) {
+        if (!shouldApplyProgressLevel(info, oldIcon)) {
+            return;
+        }
+        PreloadIconDrawable pendingIcon = applyProgressLevel(info);
+        boolean isNoLongerPending = info instanceof WorkspaceItemInfo wii
+                ? !wii.hasPromiseIconUi() : !info.isArchived();
+        if (isNoLongerPending && info.getProgressLevel() == 100 && pendingIcon != null) {
+            pendingIcon.maybePerformFinishedAnimation(
+                    (oldIcon instanceof PreloadIconDrawable p) ? p : pendingIcon,
+                    () -> setNonPendingIcon(
+                            (getTag() instanceof ItemInfoWithIcon iiwi) ? iiwi : info));
+        }
+    }
+
+    /**
+     * Check if progress level should be applied to the icon
+     */
+    private boolean shouldApplyProgressLevel(ItemInfoWithIcon info, FastBitmapDrawable oldIcon) {
+        return (info.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0
+                || (info instanceof WorkspaceItemInfo wii && wii.hasPromiseIconUi())
+                || (oldIcon instanceof PreloadIconDrawable p && p.hasNotCompleted());
+    }
+
+    private void setNonPendingIcon(ItemInfoWithIcon info) {
+        int flags = shouldUseTheme() ? FLAG_THEMED : info.bitmap.creationFlags;
         // Remove badge on icons smaller than 48dp.
         if (mHideBadge || mDisplay == DISPLAY_SEARCH_RESULT_SMALL) {
             flags |= FLAG_NO_BADGE;
@@ -480,25 +601,19 @@
         mDotParams.appColor = iconDrawable.getIconColor();
         mDotParams.dotColor = Themes.getAttrColor(getContext(), R.attr.notificationDotColor);
         setIcon(iconDrawable);
-        applyLabel(info);
     }
 
     protected boolean shouldUseTheme() {
-        return (mDisplay == DISPLAY_WORKSPACE || mDisplay == DISPLAY_FOLDER
-                || mDisplay == DISPLAY_TASKBAR) && Themes.isThemedIconEnabled(getContext());
+        return mDisplay == DISPLAY_WORKSPACE || mDisplay == DISPLAY_FOLDER
+                || mDisplay == DISPLAY_TASKBAR;
     }
 
     /**
      * Only if actual text can be displayed in two line, the {@code true} value will be effective.
      */
     protected boolean shouldUseTwoLine() {
-        return isCurrentLanguageEnglish() && (mDisplay == DISPLAY_ALL_APPS
-                || mDisplay == DISPLAY_PREDICTION_ROW) && (Flags.enableTwolineToggle()
-                && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(getContext()));
-    }
-
-    protected boolean isCurrentLanguageEnglish() {
-        return mCurrentLanguage.equals(Locale.ENGLISH.getLanguage());
+        return mDeviceProfile.inv.enableTwoLinesInAllApps
+                && (mDisplay == DISPLAY_ALL_APPS || mDisplay == DISPLAY_PREDICTION_ROW);
     }
 
     @UiThread
@@ -714,8 +829,7 @@
     protected void drawDotIfNecessary(Canvas canvas) {
         if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) {
             getIconBounds(mDotParams.iconBounds);
-            Utilities.scaleRectAboutCenter(mDotParams.iconBounds,
-                    IconShape.INSTANCE.get(getContext()).getNormalizationScale());
+            Utilities.scaleRectAboutCenter(mDotParams.iconBounds, ICON_VISIBLE_AREA_FACTOR);
             final int scrollX = getScrollX();
             final int scrollY = getScrollY();
             canvas.translate(scrollX, scrollY);
@@ -743,7 +857,7 @@
         appTitleBounds = new RectF((tmpRect.width() - titleLength) / 2.f - getCompoundPaddingLeft(),
                 0, (tmpRect.width() + titleLength) / 2.f + getCompoundPaddingRight(),
                 (int) Math.ceil(fm.bottom - fm.top));
-        appTitleBounds.inset(mRoundRectPadding * 2, 0);
+        appTitleBounds.inset((mAppTitleHorizontalPadding) * 2, 0);
 
 
         if (mIcon != null) {
@@ -760,21 +874,18 @@
 
     /** Draws a line under the app icon if this is representing a running app in Desktop Mode. */
     protected void drawRunningAppIndicatorIfNecessary(Canvas canvas) {
-        if (mRunningAppState == RunningAppState.NOT_RUNNING || mDisplay != DISPLAY_TASKBAR) {
+        if (mDisplay != DISPLAY_TASKBAR
+                || mLineIndicatorScale == 0
+                || mLineIndicatorColor == Color.TRANSPARENT) {
             return;
         }
         getIconBounds(mRunningAppIconBounds);
-        Utilities.scaleRectAboutCenter(
-                mRunningAppIconBounds,
-                IconShape.INSTANCE.get(getContext()).getNormalizationScale());
+        Utilities.scaleRectAboutCenter(mRunningAppIconBounds, ICON_VISIBLE_AREA_FACTOR);
 
-        final boolean isMinimized = mRunningAppState == RunningAppState.MINIMIZED;
         final int indicatorTop = mRunningAppIconBounds.bottom + mRunningAppIndicatorTopMargin;
-        final int indicatorWidth =
-                isMinimized ? mMinimizedAppIndicatorWidth : mRunningAppIndicatorWidth;
+        final float indicatorWidth = mRunningAppIndicatorWidth * mLineIndicatorScale;
         final float cornerRadius = mRunningAppIndicatorHeight / 2f;
-        mRunningAppIndicatorPaint.setColor(
-                isMinimized ? mMinimizedAppIndicatorColor : mRunningAppIndicatorColor);
+        mRunningAppIndicatorPaint.setColor(mLineIndicatorColor);
 
         canvas.drawRoundRect(
                 mRunningAppIconBounds.centerX() - indicatorWidth / 2f,
@@ -955,10 +1066,14 @@
 
     @Override
     public void setTextColor(ColorStateList colors) {
-        mTextColor = shouldDrawAppContrastTile() ? PillColorProvider.getInstance(
-                getContext()).getAppTitleTextPaint().getColor()
-                : colors.getDefaultColor();
-        mTextColorStateList = colors;
+        if (shouldDrawAppContrastTile()) {
+            mTextColor = PillColorProvider.getInstance(
+                    getContext()).getAppTitleTextPaint().getColor();
+        } else {
+            mTextColor = colors.getDefaultColor();
+            mTextColorStateList = colors;
+        }
+
         if (Float.compare(mTextAlpha, 1) == 0) {
             super.setTextColor(colors);
         } else {
@@ -980,7 +1095,7 @@
     public boolean shouldDrawAppContrastTile() {
         return mDisplay == DISPLAY_WORKSPACE && shouldTextBeVisible()
                 && PillColorProvider.getInstance(getContext()).isMatchaEnabled()
-                && enableContrastTiles() && !TextUtils.isEmpty(getText());
+                && enableContrastTiles();
     }
 
     public void setTextVisibility(boolean visible) {
@@ -1092,38 +1207,10 @@
         mLongPressHelper.cancelLongPress();
     }
 
-    /**
-     * Applies the loading progress value to the progress bar.
-     *
-     * If this app is installing, the progress bar will be updated with the installation progress.
-     * If this app is installed and downloading incrementally, the progress bar will be updated
-     * with the total download progress.
-     */
-    public void applyLoadingState(PreloadIconDrawable icon) {
-        if (getTag() instanceof ItemInfoWithIcon) {
-            WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
-            if ((info.runtimeStatusFlags & FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0
-                    || info.hasPromiseIconUi()
-                    || (info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0
-                    || (icon != null)) {
-                updateProgressBarUi(info.getProgressLevel() == 100 ? icon : null);
-            }
-        }
-    }
-
-    private void updateProgressBarUi(PreloadIconDrawable oldIcon) {
-        FastBitmapDrawable originalIcon = mIcon;
-        PreloadIconDrawable preloadDrawable = applyProgressLevel();
-        if (preloadDrawable != null && oldIcon != null) {
-            preloadDrawable.maybePerformFinishedAnimation(oldIcon, () -> setIcon(originalIcon));
-        }
-    }
-
     /** Applies the given progress level to the this icon's progress bar. */
     @Nullable
-    public PreloadIconDrawable applyProgressLevel() {
-        if (!(getTag() instanceof ItemInfoWithIcon info)
-                || ((ItemInfoWithIcon) getTag()).isInactiveArchive()) {
+    private PreloadIconDrawable applyProgressLevel(ItemInfoWithIcon info) {
+        if (info.isInactiveArchive()) {
             return null;
         }
 
@@ -1137,23 +1224,16 @@
             setContentDescription(getContext()
                     .getString(R.string.app_waiting_download_title, info.title));
         }
-        if (mIcon != null) {
-            PreloadIconDrawable preloadIconDrawable;
-            if (mIcon instanceof PreloadIconDrawable) {
-                preloadIconDrawable = (PreloadIconDrawable) mIcon;
-                preloadIconDrawable.setLevel(progressLevel);
-                preloadIconDrawable.setIsDisabled(isIconDisabled(info));
-            } else {
-                preloadIconDrawable = makePreloadIcon();
-                setIcon(preloadIconDrawable);
-                if (info.isArchived() && Flags.useNewIconForArchivedApps()) {
-                    // reapply text without cloud icon as soon as unarchiving is triggered
-                    applyLabel(info);
-                }
-            }
-            return preloadIconDrawable;
+        PreloadIconDrawable pid;
+        if (mIcon instanceof PreloadIconDrawable p) {
+            pid = p;
+            pid.setLevel(progressLevel);
+            pid.setIsDisabled(isIconDisabled(info));
+        } else {
+            pid = makePreloadIcon(info);
+            setIcon(pid);
         }
-        return null;
+        return pid;
     }
 
     /**
@@ -1162,11 +1242,11 @@
      */
     @Nullable
     public PreloadIconDrawable makePreloadIcon() {
-        if (!(getTag() instanceof ItemInfoWithIcon)) {
-            return null;
-        }
+        return getTag() instanceof ItemInfoWithIcon info ? makePreloadIcon(info) : null;
+    }
 
-        ItemInfoWithIcon info = (ItemInfoWithIcon) getTag();
+    @NonNull
+    private PreloadIconDrawable makePreloadIcon(ItemInfoWithIcon info) {
         int progressLevel = info.getProgressLevel();
         final PreloadIconDrawable preloadDrawable = newPendingIcon(getContext(), info);
 
@@ -1234,7 +1314,7 @@
                 setContentDescription(getContext().getString(
                         R.string.app_archived_title, info.title));
             }
-        } else if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK)
+        } else if ((info.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK)
                 != 0) {
             String percentageString = NumberFormat.getPercentInstance()
                     .format(progressLevel * 0.01);
@@ -1261,6 +1341,7 @@
         mIcon = icon;
         if (mIcon != null) {
             mIcon.setVisible(getWindowVisibility() == VISIBLE && isShown(), false);
+            mIcon.setHoverScaleEnabledForDisplay(mDisplay != DISPLAY_TASKBAR);
         }
     }
 
@@ -1333,7 +1414,6 @@
                 applyFromApplicationInfo((AppInfo) info);
             } else if (info instanceof WorkspaceItemInfo) {
                 applyFromWorkspaceItem((WorkspaceItemInfo) info);
-                mActivity.invalidateParent(info);
             } else if (info != null) {
                 applyFromItemInfoWithIcon(info);
             }
@@ -1347,16 +1427,13 @@
      * Verifies that the current icon is high-res otherwise posts a request to load the icon.
      */
     public void verifyHighRes() {
-        if (mIconLoadRequest != null) {
-            mIconLoadRequest.cancel();
-            mIconLoadRequest = null;
-        }
-        if (getTag() instanceof ItemInfoWithIcon && !mHighResUpdateInProgress) {
-            ItemInfoWithIcon info = (ItemInfoWithIcon) getTag();
-            if (info.usingLowResIcon()) {
-                mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache()
-                        .updateIconInBackground(BubbleTextView.this, info);
+        if (getTag() instanceof ItemInfoWithIcon info && !mHighResUpdateInProgress
+                && info.getMatchingLookupFlag().useLowRes()) {
+            if (mIconLoadRequest != null) {
+                mIconLoadRequest.cancel();
             }
+            mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache()
+                    .updateIconInBackground(BubbleTextView.this, info);
         }
     }
 
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index df5f520..0ce966b 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -47,6 +47,7 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
@@ -170,6 +171,7 @@
 
     private boolean mDragging = false;
     public boolean mHasOnLayoutBeenCalled = false;
+    private boolean mPlayDragHaptics = false;
 
     private final TimeInterpolator mEaseOutInterpolator;
     protected final ShortcutAndWidgetContainer mShortcutsAndWidgets;
@@ -529,9 +531,11 @@
 
         for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
             DelegatedCellDrawing cellDrawing = mDelegatedCellDrawings.get(i);
-            cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
             canvas.save();
-            canvas.translate(mTempLocation[0], mTempLocation[1]);
+            if (cellDrawing.mDelegateCellX >= 0 && cellDrawing.mDelegateCellY >= 0) {
+                cellToPoint(cellDrawing.mDelegateCellX, cellDrawing.mDelegateCellY, mTempLocation);
+                canvas.translate(mTempLocation[0], mTempLocation[1]);
+            }
             cellDrawing.drawUnderItem(canvas);
             canvas.restore();
         }
@@ -660,9 +664,11 @@
 
         for (int i = 0; i < mDelegatedCellDrawings.size(); i++) {
             DelegatedCellDrawing bg = mDelegatedCellDrawings.get(i);
-            cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
             canvas.save();
-            canvas.translate(mTempLocation[0], mTempLocation[1]);
+            if (bg.mDelegateCellX >= 0 && bg.mDelegateCellY >= 0) {
+                cellToPoint(bg.mDelegateCellX, bg.mDelegateCellY, mTempLocation);
+                canvas.translate(mTempLocation[0], mTempLocation[1]);
+            }
             bg.drawOverItem(canvas);
             canvas.restore();
         }
@@ -781,6 +787,12 @@
             }
             mShortcutsAndWidgets.addView(child, index, lp);
 
+            // Whenever an app is added, if Accessibility service is enabled, focus on that app.
+            if (mActivity instanceof Launcher) {
+                child.setTag(R.id.perform_a11y_action_on_launcher_state_normal_tag,
+                        AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+            }
+
             if (markCells) markCellsAsOccupiedForView(child);
 
             return true;
@@ -1160,7 +1172,8 @@
             DropTarget.DragObject dragObject) {
         if (mDragCell[0] != cellX || mDragCell[1] != cellY || mDragCellSpan[0] != spanX
                 || mDragCellSpan[1] != spanY) {
-            if (Flags.msdlFeedback()) {
+            determineIfDragHapticsPlay();
+            if (mPlayDragHaptics && Flags.msdlFeedback()) {
                 mMSDLPlayerWrapper.playToken(MSDLToken.DRAG_INDICATOR_DISCRETE);
             }
             mDragCell[0] = cellX;
@@ -1188,6 +1201,14 @@
         }
     }
 
+    private void determineIfDragHapticsPlay() {
+        if (mDragCell[0] != -1 || mDragCell[1] != -1
+                || mDragCellSpan[0] != -1 || mDragCellSpan[1] != -1) {
+            // The nearest cell is known and we can play haptics
+            mPlayDragHaptics = true;
+        }
+    }
+
     @SuppressLint("StringFormatMatches")
     public String getItemMoveDescription(int cellX, int cellY) {
         if (mContainerType == HOTSEAT) {
@@ -1789,6 +1810,7 @@
      * @param child The child that is being dropped
      */
     void onDropChild(View child) {
+        mPlayDragHaptics = false;
         if (child != null) {
             CellLayoutLayoutParams
                     lp = (CellLayoutLayoutParams) child.getLayoutParams();
@@ -1816,7 +1838,8 @@
                 + (int) Math.ceil(getUnusedHorizontalSpace() / 2f);
         final int vStartPadding = getPaddingTop();
 
-        int x = hStartPadding + (cellX * mBorderSpace.x) + (cellX * cellWidth);
+        int x = hStartPadding + (cellX * mBorderSpace.x) + (cellX * cellWidth)
+                + getTranslationXForCell(cellX, cellY);
         int y = vStartPadding + (cellY * mBorderSpace.y) + (cellY * cellHeight);
 
         int width = cellHSpan * cellWidth + ((cellHSpan - 1) * mBorderSpace.x);
@@ -1825,6 +1848,11 @@
         resultRect.set(x, y, x + width, y + height);
     }
 
+    /** Enables successors to provide an X adjustment for the cell. */
+    protected int getTranslationXForCell(int cellX, int cellY) {
+        return 0;
+    }
+
     public void markCellsAsOccupiedForView(View view) {
         if (view instanceof LauncherAppWidgetHostView
                 && view.getTag() instanceof LauncherAppWidgetInfo) {
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index 425f277..89d54f8 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
 
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
@@ -131,6 +132,10 @@
         ItemInfo item = d.dragInfo;
         if (canRemove(item)) {
             mDropTargetHandler.onDeleteComplete(item);
+        } else if (mText == getResources().getText(R.string.remove_drop_target_label)) {
+            Log.wtf("b/379606516", "If the drop target text is 'remove', then"
+                    + " users should always be able to delete the item from launcher's db."
+                    + " Invalid drag ItemInfo: " + item);
         }
     }
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index f1274dc..c85ca49 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -24,17 +24,14 @@
 import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_LANDSCAPE;
 import static com.android.launcher3.InvariantDeviceProfile.INDEX_TWO_PANEL_PORTRAIT;
 import static com.android.launcher3.Utilities.dpiFromPx;
-import static com.android.launcher3.Utilities.isEnglishLanguage;
 import static com.android.launcher3.Utilities.pxFromSp;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
-import static com.android.launcher3.icons.GraphicsUtils.getShapePath;
 import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
 import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
 import static com.android.launcher3.testing.shared.ResourceUtils.pxFromDp;
 import static com.android.launcher3.testing.shared.ResourceUtils.roundPxValueFromFloat;
 import static com.android.wm.shell.Flags.enableBubbleBar;
-import static com.android.wm.shell.Flags.enableBubbleBarInPersistentTaskBar;
 import static com.android.wm.shell.Flags.enableTinyTaskbar;
+import static com.android.wm.shell.Flags.enableBubbleBarOnPhones;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
@@ -55,8 +52,9 @@
 
 import com.android.launcher3.CellLayout.ContainerType;
 import com.android.launcher3.DevicePaddings.DevicePadding;
+import com.android.launcher3.folder.ClippedFolderIconLayoutRule;
+import com.android.launcher3.graphics.ThemeManager;
 import com.android.launcher3.icons.DotRenderer;
-import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.responsive.CalculatedCellSpec;
 import com.android.launcher3.responsive.CalculatedHotseatSpec;
@@ -389,7 +387,8 @@
     }
 
     /** TODO: Once we fully migrate to staged split, remove "isMultiWindowMode" */
-    DeviceProfile(Context context, InvariantDeviceProfile inv, Info info, WindowBounds windowBounds,
+    DeviceProfile(Context context, InvariantDeviceProfile inv, Info info,
+            WindowManagerProxy wmProxy, ThemeManager themeManager, WindowBounds windowBounds,
             SparseArray<DotRenderer> dotRendererCache, boolean isMultiWindowMode,
             boolean transposeLayoutWithOrientation, boolean isMultiDisplay, boolean isGestureMode,
             @NonNull final ViewScaleProvider viewScaleProvider,
@@ -421,8 +420,10 @@
         isTablet = info.isTablet(windowBounds);
         isPhone = !isTablet;
         isTwoPanels = isTablet && isMultiDisplay;
-        isTaskbarPresent = (isTablet || (enableTinyTaskbar() && isGestureMode))
-                && WindowManagerProxy.INSTANCE.get(context).isTaskbarDrawnInProcess();
+        boolean taskbarOrBubbleBarOnPhones = enableTinyTaskbar()
+                || (enableBubbleBar() && enableBubbleBarOnPhones());
+        isTaskbarPresent = (isTablet || (taskbarOrBubbleBarOnPhones && isGestureMode))
+                && wmProxy.isTaskbarDrawnInProcess();
 
         // Some more constants.
         context = getContext(context, info, inv.isFixedLandscape
@@ -794,8 +795,7 @@
         int leftRightSplitPortraitResId = Resources.getSystem().getIdentifier(
                 "config_leftRightSplitInPortrait", "bool", "android");
         boolean allowLeftRightSplitInPortrait =
-                com.android.wm.shell.Flags.enableLeftRightSplitInPortrait()
-                    && leftRightSplitPortraitResId > 0
+                    leftRightSplitPortraitResId > 0
                     && res.getBoolean(leftRightSplitPortraitResId);
         if (allowLeftRightSplitInPortrait && isTablet) {
             isLeftRightSplit = !isLandscape;
@@ -848,8 +848,8 @@
         dimensionOverrideProvider.accept(this);
 
         // This is done last, after iconSizePx is calculated above.
-        mDotRendererWorkSpace = createDotRenderer(context, iconSizePx, dotRendererCache);
-        mDotRendererAllApps = createDotRenderer(context, allAppsIconSizePx, dotRendererCache);
+        mDotRendererWorkSpace = createDotRenderer(themeManager, iconSizePx, dotRendererCache);
+        mDotRendererAllApps = createDotRenderer(themeManager, allAppsIconSizePx, dotRendererCache);
     }
 
     /**
@@ -871,10 +871,12 @@
     }
 
     private static DotRenderer createDotRenderer(
-            @NonNull Context context, int size, @NonNull SparseArray<DotRenderer> cache) {
+            @NonNull ThemeManager themeManager, int size, @NonNull SparseArray<DotRenderer> cache) {
         DotRenderer renderer = cache.get(size);
         if (renderer == null) {
-            renderer = new DotRenderer(size, getShapePath(context, DEFAULT_DOT_SIZE),
+            renderer = new DotRenderer(
+                    size,
+                    themeManager.getIconShape().getPath(DEFAULT_DOT_SIZE),
                     DEFAULT_DOT_SIZE);
             cache.put(size, renderer);
         }
@@ -1091,7 +1093,7 @@
         dotRendererCache.put(iconSizePx, mDotRendererWorkSpace);
         dotRendererCache.put(allAppsIconSizePx, mDotRendererAllApps);
 
-        return new Builder(context, inv, mInfo)
+        return inv.newDPBuilder(context, mInfo)
                 .setWindowBounds(bounds)
                 .setIsMultiDisplay(isMultiDisplay)
                 .setMultiWindowMode(isMultiWindowMode)
@@ -1229,7 +1231,7 @@
     }
 
     private int getIconSizeWithOverlap(int iconSize) {
-        return (int) Math.ceil(iconSize * ICON_OVERLAP_FACTOR);
+        return (int) Math.ceil(iconSize * ClippedFolderIconLayoutRule.getIconOverlapFactor());
     }
 
     /**
@@ -1363,22 +1365,15 @@
         if (isVerticalLayout && !mIsResponsiveGrid) {
             hideWorkspaceLabelsIfNotEnoughSpace();
         }
-        if ((Flags.enableTwolineToggle()
-                && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(context))) {
-            if (!isEnglishLanguage(context)) {
-                // Set toggle preference value to false if not english here as it's possible the
-                // preference is stale after language change.
-                LauncherPrefs.get(context).put(LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE, false);
-            } else {
-                // Add extra textHeight to the existing allAppsCellHeight.
-                allAppsCellHeightPx += Utilities.calculateTextHeight(allAppsIconTextSizePx);
-            }
+        if (inv.enableTwoLinesInAllApps) {
+            // Add extra textHeight to the existing allAppsCellHeight.
+            allAppsCellHeightPx += Utilities.calculateTextHeight(allAppsIconTextSizePx);
         }
 
         updateHotseatSizes(iconSizePx);
 
         // Folder icon
-        folderIconSizePx = IconNormalizer.getNormalizedCircleSize(iconSizePx);
+        folderIconSizePx = Math.round(iconSizePx * ICON_VISIBLE_AREA_FACTOR);
         folderIconOffsetYPx = (iconSizePx - folderIconSizePx) / 2;
 
         // Update widget padding:
@@ -2433,7 +2428,6 @@
      */
     public boolean shouldAdjustHotseatOnNavBarLocationUpdate(Context context) {
         return enableBubbleBar()
-                && enableBubbleBarInPersistentTaskBar()
                 && !DisplayController.getNavigationMode(context).hasGestures;
     }
 
@@ -2482,9 +2476,11 @@
     }
 
     public static class Builder {
-        private Context mContext;
-        private InvariantDeviceProfile mInv;
-        private Info mInfo;
+        private final Context mContext;
+        private final InvariantDeviceProfile mInv;
+        private final Info mInfo;
+        private final WindowManagerProxy mWMProxy;
+        private final ThemeManager mThemeManager;
 
         private WindowBounds mWindowBounds;
         private boolean mIsMultiDisplay;
@@ -2500,10 +2496,13 @@
 
         private boolean mIsTransientTaskbar;
 
-        public Builder(Context context, InvariantDeviceProfile inv, Info info) {
+        public Builder(Context context, InvariantDeviceProfile inv, Info info,
+                WindowManagerProxy wmProxy, ThemeManager themeManager) {
             mContext = context;
             mInv = inv;
             mInfo = info;
+            mWMProxy = wmProxy;
+            mThemeManager = themeManager;
             mIsTransientTaskbar = info.isTransientTaskbar();
         }
 
@@ -2584,7 +2583,8 @@
             if (mOverrideProvider == null) {
                 mOverrideProvider = DEFAULT_DIMENSION_PROVIDER;
             }
-            return new DeviceProfile(mContext, mInv, mInfo, mWindowBounds, mDotRendererCache,
+            return new DeviceProfile(mContext, mInv, mInfo, mWMProxy, mThemeManager,
+                    mWindowBounds, mDotRendererCache,
                     mIsMultiWindowMode, mTransposeLayoutWithOrientation, mIsMultiDisplay,
                     mIsGestureMode, mViewScaleProvider, mOverrideProvider, mIsTransientTaskbar);
         }
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index 2d99510..7f0c7b5 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.Rect;
+import android.view.View;
 
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
 import com.android.launcher3.dragndrop.DragOptions;
@@ -145,4 +146,9 @@
 
     // These methods are implemented in Views
     void getHitRectRelativeToDragLayer(Rect outRect);
+
+    /** Returns the drop target view. By default, the implementor class is cast to the view. */
+    default View getDropView() {
+        return (View) this;
+    }
 }
diff --git a/src/com/android/launcher3/DropTargetHandler.kt b/src/com/android/launcher3/DropTargetHandler.kt
index 4d3fe52..3c162a2 100644
--- a/src/com/android/launcher3/DropTargetHandler.kt
+++ b/src/com/android/launcher3/DropTargetHandler.kt
@@ -2,7 +2,7 @@
 
 import android.content.ComponentName
 import android.view.View
-import com.android.launcher3.BaseDraggingActivity.EVENT_RESUMED
+import com.android.launcher3.BaseActivity.EVENT_RESUMED
 import com.android.launcher3.DropTarget.DragObject
 import com.android.launcher3.LauncherConstants.ActivityCodes
 import com.android.launcher3.SecondaryDropTarget.DeferredOnComplete
@@ -66,10 +66,16 @@
 
     fun onDeleteComplete(item: ItemInfo) {
         removeItemAndStripEmptyScreens(null /* view */, item)
+        AbstractFloatingView.closeOpenViews(
+            mLauncher,
+            false,
+            AbstractFloatingView.TYPE_WIDGET_RESIZE_FRAME,
+        )
         var pageItem: ItemInfo = item
-        if (item.container <= 0) {
-            val v = mLauncher.workspace.getHomescreenIconByItemId(item.container)
-            v?.let { pageItem = v.tag as ItemInfo }
+        if (item.container >= 0) {
+            mLauncher.workspace.getViewByItemId(item.container)?.let {
+                pageItem = it.tag as ItemInfo
+            }
         }
         val pageIds =
             if (pageItem.container == LauncherSettings.Favorites.CONTAINER_DESKTOP)
diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java
index b20d8a5..1c18179 100644
--- a/src/com/android/launcher3/Hotseat.java
+++ b/src/com/android/launcher3/Hotseat.java
@@ -36,6 +36,7 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.ShortcutAndWidgetContainer.TranslationProvider;
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.util.HorizontalInsettableView;
 import com.android.launcher3.util.MultiPropertyFactory;
@@ -233,6 +234,13 @@
     }
 
     @Override
+    protected int getTranslationXForCell(int cellX, int cellY) {
+        TranslationProvider translationProvider = getShortcutsAndWidgets().getTranslationProvider();
+        if (translationProvider == null) return 0;
+        return (int) translationProvider.getTranslationX(cellX);
+    }
+
+    @Override
     public void setInsets(Rect insets) {
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
         DeviceProfile grid = mActivity.getDeviceProfile();
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index aefb2b1..f189549 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -17,8 +17,10 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.LauncherPrefs.DB_FILE;
+import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE;
 import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
 import static com.android.launcher3.LauncherPrefs.GRID_NAME;
+import static com.android.launcher3.LauncherPrefs.NON_FIXED_LANDSCAPE_GRID_NAME;
 import static com.android.launcher3.Utilities.dpiFromPx;
 import static com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE;
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
@@ -28,8 +30,8 @@
 import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
-import android.annotation.TargetApi;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
@@ -43,7 +45,6 @@
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.Xml;
-import android.view.Display;
 
 import androidx.annotation.DimenRes;
 import androidx.annotation.IntDef;
@@ -53,17 +54,22 @@
 import androidx.core.content.res.ResourcesCompat;
 
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.graphics.ThemeManager;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.DeviceGridState;
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.testing.shared.ResourceUtils;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
-import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.Partner;
 import com.android.launcher3.util.ResourceHelper;
-import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.util.window.CachedDisplayInfo;
 import com.android.launcher3.util.window.WindowManagerProxy;
@@ -79,16 +85,22 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.stream.Collectors;
 
-public class InvariantDeviceProfile implements SafeCloseable {
+import javax.inject.Inject;
+
+@LauncherAppSingleton
+public class InvariantDeviceProfile {
 
     public static final String TAG = "IDP";
     // We do not need any synchronization for this variable as its only written on UI thread.
-    public static final MainThreadInitializedObject<InvariantDeviceProfile> INSTANCE =
-            new MainThreadInitializedObject<>(InvariantDeviceProfile::new);
+    public static final DaggerSingletonObject<InvariantDeviceProfile> INSTANCE =
+            new DaggerSingletonObject<>(LauncherAppComponent::getIDP);
 
     public static final String GRID_NAME_PREFS_KEY = "idp_grid_name";
+    public static final String NON_FIXED_LANDSCAPE_GRID_NAME_PREFS_KEY =
+            "idp_non_fixed_landscape_grid_name";
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({TYPE_PHONE, TYPE_MULTI_DISPLAY, TYPE_TABLET})
@@ -122,6 +134,11 @@
     private static final String RES_GRID_NUM_COLUMNS = "grid_num_columns";
     private static final String RES_GRID_ICON_SIZE_DP = "grid_icon_size_dp";
 
+    private final DisplayController mDisplayController;
+    private final WindowManagerProxy mWMProxy;
+    private final LauncherPrefs mPrefs;
+    private final ThemeManager mThemeManager;
+
     /**
      * Number of icons per row and column in the workspace.
      */
@@ -217,12 +234,12 @@
     @XmlRes
     public int allAppsCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
 
-
+    private String mLocale = "";
+    public boolean enableTwoLinesInAllApps = false;
     /**
      * Fixed landscape mode is the landscape on the phones.
      */
     public boolean isFixedLandscape = false;
-    private LauncherPrefChangeListener mLandscapeModePreferenceListener;
 
     public String dbFile;
     public int defaultLayoutId;
@@ -236,17 +253,25 @@
 
     public Point defaultWallpaperSize;
 
-    private final ArrayList<OnIDPChangeListener> mChangeListeners = new ArrayList<>();
+    private final List<OnIDPChangeListener> mChangeListeners = new CopyOnWriteArrayList<>();
 
-    @VisibleForTesting
-    public InvariantDeviceProfile() {
-    }
+    @Inject
+    InvariantDeviceProfile(
+            @ApplicationContext Context context,
+            LauncherPrefs prefs,
+            DisplayController dc,
+            WindowManagerProxy wmProxy,
+            ThemeManager themeManager,
+            DaggerSingletonTracker lifeCycle) {
+        mDisplayController = dc;
+        mWMProxy = wmProxy;
+        mPrefs = prefs;
+        mThemeManager = themeManager;
 
-    @TargetApi(23)
-    private InvariantDeviceProfile(Context context) {
-        String gridName = getCurrentGridName(context);
+        String gridName = prefs.get(GRID_NAME);
         initGrid(context, gridName);
-        DisplayController.INSTANCE.get(context).setPriorityListener(
+
+        dc.setPriorityListener(
                 (displayContext, info, flags) -> {
                     if ((flags & (CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS
                             | CHANGE_NAVIGATION_MODE | CHANGE_TASKBAR_PINNING
@@ -254,117 +279,46 @@
                         onConfigChanged(displayContext);
                     }
                 });
-        if (Flags.oneGridSpecs()) {
-            mLandscapeModePreferenceListener = (String s) -> {
-                if (isFixedLandscape != FIXED_LANDSCAPE_MODE.get(context)) {
-                    MAIN_EXECUTOR.execute(() -> {
-                        Trace.beginSection("InvariantDeviceProfile#setFixedLandscape");
-                        onConfigChanged(context.getApplicationContext());
-                        Trace.endSection();
-                    });
+        lifeCycle.addCloseable(() -> dc.setPriorityListener(null));
+
+        LauncherPrefChangeListener prefListener = key -> {
+            if (FIXED_LANDSCAPE_MODE.getSharedPrefKey().equals(key)
+                    && isFixedLandscape != prefs.get(FIXED_LANDSCAPE_MODE)) {
+                Trace.beginSection("InvariantDeviceProfile#setFixedLandscape");
+                if (isFixedLandscape) {
+                    setCurrentGrid(context, prefs.get(NON_FIXED_LANDSCAPE_GRID_NAME));
+                } else {
+                    prefs.put(NON_FIXED_LANDSCAPE_GRID_NAME, mPrefs.get(GRID_NAME));
+                    onConfigChanged(context);
                 }
-            };
-            LauncherPrefs.INSTANCE.get(context).addListener(
-                    mLandscapeModePreferenceListener,
-                    FIXED_LANDSCAPE_MODE
-            );
-        }
-    }
+                Trace.endSection();
+            } else if (ENABLE_TWOLINE_ALLAPPS_TOGGLE.getSharedPrefKey().equals(key)
+                    && enableTwoLinesInAllApps != prefs.get(ENABLE_TWOLINE_ALLAPPS_TOGGLE)) {
+                onConfigChanged(context);
+            }
+        };
+        prefs.addListener(prefListener, FIXED_LANDSCAPE_MODE, ENABLE_TWOLINE_ALLAPPS_TOGGLE);
+        lifeCycle.addCloseable(() -> prefs.removeListener(prefListener,
+                FIXED_LANDSCAPE_MODE, ENABLE_TWOLINE_ALLAPPS_TOGGLE));
 
-    /**
-     * This constructor should NOT have any monitors by design.
-     */
-    public InvariantDeviceProfile(Context context, String gridName) {
-        String newName = initGrid(context, gridName);
-        if (newName == null || !newName.equals(gridName)) {
-            throw new IllegalArgumentException("Unknown grid name: " + gridName);
-        }
-    }
-
-    /**
-     * This constructor should NOT have any monitors by design.
-     */
-    public InvariantDeviceProfile(Context context, Display display) {
-        // Ensure that the main device profile is initialized
-        INSTANCE.get(context);
-        String gridName = getCurrentGridName(context);
-
-        // Get the display info based on default display and interpolate it to existing display
-        Info defaultInfo = DisplayController.INSTANCE.get(context).getInfo();
-        @DeviceType int defaultDeviceType = defaultInfo.getDeviceType();
-        DisplayOption defaultDisplayOption = invDistWeightedInterpolate(
-                defaultInfo,
-                getPredefinedDeviceProfiles(
-                        context,
-                        gridName,
-                        defaultInfo,
-                        /*allowDisabledGrid=*/false,
-                        FIXED_LANDSCAPE_MODE.get(context)
-                ),
-                defaultDeviceType);
-
-        Context displayContext = context.createDisplayContext(display);
-        Info myInfo = new Info(displayContext);
-        @DeviceType int deviceType = myInfo.getDeviceType();
-        DisplayOption myDisplayOption = invDistWeightedInterpolate(
-                myInfo,
-                getPredefinedDeviceProfiles(
-                        context,
-                        gridName,
-                        myInfo,
-                        /*allowDisabledGrid=*/false,
-                        FIXED_LANDSCAPE_MODE.get(context)
-                ),
-                deviceType);
-
-        DisplayOption result = new DisplayOption(defaultDisplayOption.grid)
-                .add(myDisplayOption);
-        result.iconSizes[INDEX_DEFAULT] =
-                defaultDisplayOption.iconSizes[INDEX_DEFAULT];
-        for (int i = 1; i < COUNT_SIZES; i++) {
-            result.iconSizes[i] = Math.min(
-                    defaultDisplayOption.iconSizes[i], myDisplayOption.iconSizes[i]);
-        }
-
-        System.arraycopy(defaultDisplayOption.minCellSize, 0, result.minCellSize, 0,
-                COUNT_SIZES);
-        System.arraycopy(defaultDisplayOption.borderSpaces, 0, result.borderSpaces, 0,
-                COUNT_SIZES);
-
-        initGrid(context, myInfo, result);
-    }
-
-    @Override
-    public void close() {
-        DisplayController.INSTANCE.executeIfCreated(dc -> dc.setPriorityListener(null));
-        if (mLandscapeModePreferenceListener != null) {
-            LauncherPrefs.INSTANCE.executeIfCreated(
-                    lp -> lp.removeListener(mLandscapeModePreferenceListener, FIXED_LANDSCAPE_MODE)
-            );
-        }
-    }
-
-    public static String getCurrentGridName(Context context) {
-        return LauncherPrefs.get(context).get(GRID_NAME);
+        SimpleBroadcastReceiver localeReceiver = new SimpleBroadcastReceiver(context,
+                MAIN_EXECUTOR, i -> onConfigChanged(context));
+        localeReceiver.register(Intent.ACTION_LOCALE_CHANGED);
+        lifeCycle.addCloseable(() -> localeReceiver.unregisterReceiverSafely());
     }
 
     private String initGrid(Context context, String gridName) {
-        FileLog.d(TAG, "Before initGrid:"
-                + "gridName:" + gridName
-                + ", dbFile:" + dbFile
-                + ", LauncherPrefs GRID_NAME:" + LauncherPrefs.get(context).get(GRID_NAME)
-                + ", LauncherPrefs DB_FILE:" + LauncherPrefs.get(context).get(DB_FILE));
-        Info displayInfo = DisplayController.INSTANCE.get(context).getInfo();
+        Info displayInfo = mDisplayController.getInfo();
         List<DisplayOption> allOptions = getPredefinedDeviceProfiles(
                 context,
                 gridName,
                 displayInfo,
-                RestoreDbTask.isPending(context),
-                FIXED_LANDSCAPE_MODE.get(context)
+                (RestoreDbTask.isPending(mPrefs) && !Flags.oneGridSpecs()),
+                mPrefs.get(FIXED_LANDSCAPE_MODE)
         );
 
         // Filter out options that don't have the same number of columns as the grid
-        DeviceGridState deviceGridState = new DeviceGridState(context);
+        DeviceGridState deviceGridState = new DeviceGridState(mPrefs);
         List<DisplayOption> allOptionsFilteredByColCount =
                 filterByColumnCount(allOptions, deviceGridState.getColumns());
 
@@ -375,22 +329,23 @@
                         displayInfo.getDeviceType());
 
         if (!displayOption.grid.name.equals(gridName)) {
-            LauncherPrefs.get(context).put(GRID_NAME, displayOption.grid.name);
+            mPrefs.put(GRID_NAME, displayOption.grid.name);
         }
 
         initGrid(context, displayInfo, displayOption);
         FileLog.d(TAG, "After initGrid:"
                 + "gridName:" + gridName
                 + ", dbFile:" + dbFile
-                + ", LauncherPrefs GRID_NAME:" + LauncherPrefs.get(context).get(GRID_NAME)
-                + ", LauncherPrefs DB_FILE:" + LauncherPrefs.get(context).get(DB_FILE));
+                + ", LauncherPrefs GRID_NAME:" + mPrefs.get(GRID_NAME)
+                + ", LauncherPrefs DB_FILE:" + mPrefs.get(DB_FILE));
         return displayOption.grid.name;
     }
 
     private List<DisplayOption> filterByColumnCount(
             List<DisplayOption> allOptions, int numColumns) {
-        return allOptions.stream().filter(
-                option -> option.grid.numColumns == numColumns).toList();
+        return allOptions.stream()
+                .filter(option -> option.grid.numColumns == numColumns)
+                .collect(Collectors.toList());
     }
 
     /**
@@ -399,15 +354,15 @@
      */
     @Deprecated
     public void reset(Context context) {
-        initGrid(context, getCurrentGridName(context));
-    }
-
-    @VisibleForTesting
-    public static String getDefaultGridName(Context context) {
-        return new InvariantDeviceProfile().initGrid(context, null);
+        initGrid(context, mPrefs.get(GRID_NAME));
     }
 
     private void initGrid(Context context, Info displayInfo, DisplayOption displayOption) {
+        enableTwoLinesInAllApps = Flags.enableTwolineToggle()
+                && Utilities.isEnglishLanguage(context)
+                && mPrefs.get(ENABLE_TWOLINE_ALLAPPS_TOGGLE);
+        mLocale = context.getResources().getConfiguration().locale.toString();
+
         DisplayMetrics metrics = context.getResources().getDisplayMetrics();
         GridOption closestProfile = displayOption.grid;
         numRows = closestProfile.numRows;
@@ -496,7 +451,7 @@
         defaultWallpaperSize = new Point(displayInfo.currentSize);
         SparseArray<DotRenderer> dotRendererCache = new SparseArray<>();
         for (WindowBounds bounds : displayInfo.supportedBounds) {
-            localSupportedProfiles.add(new DeviceProfile.Builder(context, this, displayInfo)
+            localSupportedProfiles.add(newDPBuilder(context, displayInfo)
                     .setIsMultiDisplay(deviceType == TYPE_MULTI_DISPLAY)
                     .setWindowBounds(bounds)
                     .setDotRendererCache(dotRendererCache)
@@ -535,6 +490,10 @@
                 });
     }
 
+    DeviceProfile.Builder newDPBuilder(Context context, Info info) {
+        return new DeviceProfile.Builder(context, this, info, mWMProxy, mThemeManager);
+    }
+
     public void addOnChangeListener(OnIDPChangeListener listener) {
         mChangeListeners.add(listener);
     }
@@ -547,8 +506,9 @@
      * Updates the current grid, this triggers a new IDP, reloads the database and triggers a grid
      * migration.
      */
+    @VisibleForTesting
     public void setCurrentGrid(Context context, String newGridName) {
-        LauncherPrefs.get(context).put(GRID_NAME, newGridName);
+        mPrefs.put(GRID_NAME, newGridName);
         MAIN_EXECUTOR.execute(() -> {
             Trace.beginSection("InvariantDeviceProfile#setCurrentGrid");
             onConfigChanged(context.getApplicationContext());
@@ -559,7 +519,7 @@
     private Object[] toModelState() {
         return new Object[]{
                 numColumns, numRows, numSearchContainerColumns, numDatabaseHotseatIcons,
-                iconBitmapSize, fillResIconDpi, numDatabaseAllAppsColumns, dbFile};
+                iconBitmapSize, fillResIconDpi, numDatabaseAllAppsColumns, dbFile, mLocale};
     }
 
     /** Updates IDP using the provided context. Notifies listeners of change. */
@@ -568,8 +528,7 @@
         Object[] oldState = toModelState();
 
         // Re-init grid
-        String gridName = getCurrentGridName(context);
-        initGrid(context, gridName);
+        initGrid(context, mPrefs.get(GRID_NAME));
 
         boolean modelPropsChanged = !Arrays.equals(oldState, toModelState());
         for (OnIDPChangeListener listener : mChangeListeners) {
@@ -670,7 +629,7 @@
         }
 
         // Finds the min width and height in dp for all displays.
-        int[] dimens = findMinWidthAndHeightDpForDevice(displayInfo);
+        int[] dimens = findMinWidthAndHeightPxForDevice(displayInfo);
 
         return findBestGridSize(gridSizes, dimens[0], dimens[1]);
     }
@@ -679,11 +638,11 @@
      * @return the biggest grid size that fits the display dimensions.
      * If no best grid size is found, return null.
      */
-    private static GridSize findBestGridSize(List<GridSize> list, int minWidthDp,
-            int minHeightDp) {
+    private static GridSize findBestGridSize(List<GridSize> list, int minWidthPx,
+            int minHeightPx) {
         GridSize selectedGridSize = null;
         for (GridSize item: list) {
-            if (minWidthDp >= item.mMinDeviceWidthDp && minHeightDp >= item.mMinDeviceHeightDp) {
+            if (minWidthPx >= item.mMinDeviceWidthPx && minHeightPx >= item.mMinDeviceHeightPx) {
                 if (selectedGridSize == null
                         || (selectedGridSize.mNumColumns <= item.mNumColumns
                         && selectedGridSize.mNumRows <= item.mNumRows)) {
@@ -694,16 +653,14 @@
         return selectedGridSize;
     }
 
-    private static int[] findMinWidthAndHeightDpForDevice(Info displayInfo) {
-        int minDisplayWidthDp = Integer.MAX_VALUE;
-        int minDisplayHeightDp = Integer.MAX_VALUE;
+    private static int[] findMinWidthAndHeightPxForDevice(Info displayInfo) {
+        int minDisplayWidthPx = Integer.MAX_VALUE;
+        int minDisplayHeightPx = Integer.MAX_VALUE;
         for (CachedDisplayInfo display: displayInfo.getAllDisplays()) {
-            minDisplayWidthDp = Math.min(minDisplayWidthDp,
-                    (int) dpiFromPx(display.size.x, DisplayMetrics.DENSITY_DEVICE_STABLE));
-            minDisplayHeightDp = Math.min(minDisplayHeightDp,
-                    (int) dpiFromPx(display.size.y, DisplayMetrics.DENSITY_DEVICE_STABLE));
+            minDisplayWidthPx = Math.min(minDisplayWidthPx, display.size.x);
+            minDisplayHeightPx = Math.min(minDisplayHeightPx, display.size.y);
         }
-        return new int[]{minDisplayWidthDp, minDisplayHeightDp};
+        return new int[]{minDisplayWidthPx, minDisplayHeightPx};
     }
 
     /**
@@ -888,11 +845,20 @@
         return out;
     }
 
-    public DeviceProfile getDeviceProfile(Context context) {
-        WindowManagerProxy windowManagerProxy = WindowManagerProxy.INSTANCE.get(context);
-        Rect bounds = windowManagerProxy.getCurrentBounds(context);
-        int rotation = windowManagerProxy.getRotation(context);
+    public DeviceProfile createDeviceProfileForSecondaryDisplay(Context displayContext) {
+        // Disable transpose layout and use multi-window mode so that the icons are scaled properly
+        return newDPBuilder(displayContext, new Info(displayContext))
+                .setIsMultiDisplay(false)
+                .setMultiWindowMode(true)
+                .setWindowBounds(mWMProxy.getRealBounds(
+                        displayContext, mWMProxy.getDisplayInfo(displayContext)))
+                .setTransposeLayoutWithOrientation(false)
+                .build();
+    }
 
+    public DeviceProfile getDeviceProfile(Context context) {
+        Rect bounds = mWMProxy.getCurrentBounds(context);
+        int rotation = mWMProxy.getRotation(context);
         return getBestMatch(bounds.width(), bounds.height(), rotation);
     }
 
@@ -970,6 +936,9 @@
         private static final int DEVICE_CATEGORY_PHONE = 1 << 0;
         private static final int DEVICE_CATEGORY_TABLET = 1 << 1;
         private static final int DEVICE_CATEGORY_MULTI_DISPLAY = 1 << 2;
+        private static final int GRID_TYPE_ONE_GRID = 1 << 0;
+        private static final int GRID_TYPE_NON_ONE_GRID = 1 << 1;
+        private static final int GRID_TYPE_ALL = 1 << 2;
         private static final int DEVICE_CATEGORY_ALL =
                 DEVICE_CATEGORY_PHONE | DEVICE_CATEGORY_TABLET | DEVICE_CATEGORY_MULTI_DISPLAY;
 
@@ -980,11 +949,13 @@
         private static final int DONT_INLINE_QSB = 0;
 
         public final String name;
-        public final String title;
+        public final String gridTitle;
+        public final int gridIconId;
         public final int numRows;
         public final int numColumns;
         public final int numSearchContainerColumns;
         public final int deviceCategory;
+        public final int gridType;
 
         private final int[] numFolderRows = new int[COUNT_SIZES];
         private final int[] numFolderColumns = new int[COUNT_SIZES];
@@ -1023,13 +994,14 @@
         private final int mAllAppsCellSpecsTwoPanelId;
         private final int mGridSizeSpecsId;
         private final boolean mIsFixedLandscape;
-        private final boolean mIsOldGrid;
 
         public GridOption(Context context, AttributeSet attrs, Info displayInfo) {
             TypedArray a = context.obtainStyledAttributes(
                     attrs, R.styleable.GridDisplayOption);
             name = a.getString(R.styleable.GridDisplayOption_name);
-            title = a.getString(R.styleable.GridDisplayOption_title);
+            gridTitle = a.getString(R.styleable.GridDisplayOption_gridTitle);
+            gridIconId = a.getResourceId(
+                    R.styleable.GridDisplayOption_gridIconId, INVALID_RESOURCE_HANDLE);
             deviceCategory = a.getInt(R.styleable.GridDisplayOption_deviceCategory,
                     DEVICE_CATEGORY_ALL);
             mGridSizeSpecsId = a.getResourceId(
@@ -1169,7 +1141,7 @@
             }
 
             mIsFixedLandscape = a.getBoolean(R.styleable.GridDisplayOption_isFixedLandscape, false);
-            mIsOldGrid = a.getBoolean(R.styleable.GridDisplayOption_isOldGrid, false);
+            gridType = a.getInt(R.styleable.GridDisplayOption_gridType, GRID_TYPE_ALL);
 
             int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,
                     DONT_INLINE_QSB);
@@ -1214,13 +1186,11 @@
                 return mIsFixedLandscape && isFixedLandscape && Flags.oneGridSpecs();
             }
 
-            // Here we return true if we want to show the new grids.
-            if (mGridSizeSpecsId != INVALID_RESOURCE_HANDLE) {
+            // If the grid type is one grid we return true when the flag is on, if the grid type
+            // is non-one grid we return true when the flag is off. Otherwise, we return true.
+            if (gridType == GRID_TYPE_ONE_GRID) {
                 return Flags.oneGridSpecs();
-            }
-
-            // Here we return true if we want to show the old grids.
-            if (mIsOldGrid) {
+            } else if (gridType == GRID_TYPE_NON_ONE_GRID) {
                 return !Flags.oneGridSpecs();
             }
 
@@ -1231,8 +1201,8 @@
     public static final class GridSize {
         final int mNumRows;
         final int mNumColumns;
-        final float mMinDeviceWidthDp;
-        final float mMinDeviceHeightDp;
+        final float mMinDeviceWidthPx;
+        final float mMinDeviceHeightPx;
         final String mDbFile;
         final int mDefaultLayoutId;
         final int mDemoModeLayoutId;
@@ -1243,8 +1213,8 @@
 
             mNumRows = (int) a.getFloat(R.styleable.GridSize_numGridRows, 0);
             mNumColumns = (int) a.getFloat(R.styleable.GridSize_numGridColumns, 0);
-            mMinDeviceWidthDp = a.getFloat(R.styleable.GridSize_minDeviceWidthDp, 0);
-            mMinDeviceHeightDp = a.getFloat(R.styleable.GridSize_minDeviceHeightDp, 0);
+            mMinDeviceWidthPx = a.getFloat(R.styleable.GridSize_minDeviceWidthPx, 0);
+            mMinDeviceHeightPx = a.getFloat(R.styleable.GridSize_minDeviceHeightPx, 0);
             mDbFile = a.getString(R.styleable.GridSize_dbFile);
             mDefaultLayoutId = a.getResourceId(
                     R.styleable.GridSize_defaultLayoutId, 0);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 51e5b51..289f175 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -29,6 +29,7 @@
 import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
 import static com.android.launcher3.Flags.enableAddAppWidgetViaConfigActivityV2;
 import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
+import static com.android.launcher3.Flags.enableStrictMode;
 import static com.android.launcher3.Flags.enableWorkspaceInflation;
 import static com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WIDGET_TRANSITION;
@@ -58,6 +59,7 @@
 import static com.android.launcher3.LauncherConstants.TraceEvents.ON_NEW_INTENT_EVT;
 import static com.android.launcher3.LauncherConstants.TraceEvents.ON_RESUME_EVT;
 import static com.android.launcher3.LauncherConstants.TraceEvents.ON_START_EVT;
+import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherState.ALL_APPS;
@@ -69,9 +71,11 @@
 import static com.android.launcher3.LauncherState.NO_SCALE;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.launcher3.Workspace.mapOverCellLayouts;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
 import static com.android.launcher3.config.FeatureFlags.MULTI_SELECT_EDIT_MODE;
-import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
+import static com.android.launcher3.icons.BitmapRenderer.createHardwareBitmap;
 import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.HIDE;
 import static com.android.launcher3.logging.KeyboardStateManager.KeyboardState.SHOW;
 import static com.android.launcher3.logging.StatsLogManager.EventEnum;
@@ -102,6 +106,7 @@
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.ItemInfoMatcher.forFolderMatch;
 import static com.android.launcher3.util.SettingsCache.TOUCHPAD_NATURAL_SCROLLING;
+import static com.android.launcher3.util.WallpaperThemeManager.setWallpaperDependentTheme;
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
@@ -122,7 +127,6 @@
 import android.content.res.Configuration;
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -144,6 +148,7 @@
 import android.view.Menu;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.View.OnClickListener;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver.OnPreDrawListener;
 import android.view.WindowInsets;
@@ -159,7 +164,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
-import androidx.annotation.StringRes;
 import androidx.annotation.UiThread;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.os.BuildCompat;
@@ -168,7 +172,6 @@
 import com.android.launcher3.DropTarget.DragObject;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.allapps.ActivityAllAppsContainerView;
-import com.android.launcher3.allapps.AllAppsRecyclerView;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.anim.AnimationSuccessListener;
@@ -179,9 +182,8 @@
 import com.android.launcher3.celllayout.CellPosMapper.TwoPanelCellPosMapper;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.debug.TestEvent;
 import com.android.launcher3.debug.TestEventEmitter;
-import com.android.launcher3.dot.DotInfo;
+import com.android.launcher3.debug.TestEventEmitter.TestEvent;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
@@ -205,7 +207,6 @@
 import com.android.launcher3.model.ModelWriter;
 import com.android.launcher3.model.StringCache;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -223,6 +224,7 @@
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.launcher3.touch.AllAppsSwipeController;
+import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.ActivityResultInfo;
 import com.android.launcher3.util.BackPressHandler;
@@ -233,6 +235,7 @@
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ItemInflater;
 import com.android.launcher3.util.KeyboardShortcutsDelegate;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.LockedUserState;
 import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.PackageUserKey;
@@ -249,7 +252,6 @@
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.views.ComposeInitializer;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.launcher3.views.FloatingSurfaceView;
 import com.android.launcher3.views.OptionsPopupView;
@@ -278,11 +280,11 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
@@ -417,7 +419,6 @@
 
     private final List<BackPressHandler> mBackPressedHandlers = new ArrayList<>();
     private boolean mIsColdStartupAfterReboot;
-    private boolean mForceConfigUpdate;
 
     private boolean mIsNaturalScrollingEnabled;
 
@@ -459,7 +460,8 @@
         Trace.beginAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
                 DISPLAY_ALL_APPS_TRACE_COOKIE);
         TraceHelper.INSTANCE.beginSection(ON_CREATE_EVT);
-        if (DEBUG_STRICT_MODE) {
+        if (DEBUG_STRICT_MODE
+                || (FeatureFlags.IS_STUDIO_BUILD && enableStrictMode())) {
             StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                     .detectDiskReads()
                     .detectDiskWrites()
@@ -469,6 +471,7 @@
             StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                     .detectLeakedSqlLiteObjects()
                     .detectLeakedClosableObjects()
+                    .detectActivityLeaks()
                     .penaltyLog()
                     .penaltyDeath()
                     .build());
@@ -512,6 +515,7 @@
         }
 
         super.onCreate(savedInstanceState);
+        setWallpaperDependentTheme(this);
 
         LauncherAppState app = LauncherAppState.getInstance(this);
         mModel = app.getModel();
@@ -538,8 +542,8 @@
         mItemInflater = new ItemInflater<>(this, mAppWidgetHolder, getItemOnClickListener(),
                 mFocusHandler, new CellLayout(mWorkspace.getContext(), mWorkspace));
 
-        mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
-        mWidgetPickerDataProvider = new WidgetPickerDataProvider();
+        mPopupDataProvider = new PopupDataProvider(this);
+        mWidgetPickerDataProvider = new WidgetPickerDataProvider(this);
         PillColorProvider.getInstance(mWorkspace.getContext()).registerObserver();
 
         boolean internalStateHandled = ACTIVITY_TRACKER.handleCreate(this);
@@ -573,7 +577,6 @@
         setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL);
 
         setContentView(getRootView());
-        ComposeInitializer.initCompose(this);
 
         if (mOnInitialBindListener != null) {
             getRootView().getViewTreeObserver().addOnPreDrawListener(mOnInitialBindListener);
@@ -605,7 +608,7 @@
             RuleController.getInstance(this).setRules(
                     RuleController.parseRules(this, R.xml.split_configuration));
         }
-        TestEventEmitter.INSTANCE.get(this).sendEvent(TestEvent.LAUNCHER_ON_CREATE);
+        TestEventEmitter.sendEvent(TestEvent.LAUNCHER_ON_CREATE);
     }
 
     protected ModelCallbacks createModelCallbacks() {
@@ -633,6 +636,10 @@
         return new ColdRebootStartupLatencyLogger();
     }
 
+    @NonNull View getAccessibilityActionView() {
+        return findViewById(R.id.accessibility_action_view);
+    }
+
     /**
      * Provide {@link OnBackAnimationCallback} in below order:
      * <ol>
@@ -763,7 +770,7 @@
     protected void onHandleConfigurationChanged() {
         Trace.beginSection("Launcher#onHandleconfigurationChanged");
         try {
-            if (!initDeviceProfile(mDeviceProfile.inv) && !mForceConfigUpdate) {
+            if (!initDeviceProfile(mDeviceProfile.inv)) {
                 return;
             }
             dispatchDeviceProfileChanged();
@@ -776,7 +783,6 @@
             mModel.rebindCallbacks();
             updateDisallowBack();
         } finally {
-            mForceConfigUpdate = false;
             Trace.endSection();
         }
     }
@@ -786,9 +792,13 @@
             return;
         }
         // When the flag oneGridSpecs is on we want to disable ALLOW_ROTATION which is replaced
-        // by FIXED_LANDSCAPE_MODE, ALLOW_ROTATION will only be used on Tablets afterwards.
-        if (getDeviceProfile().isPhone || getDeviceProfile().isTwoPanels) {
+        // by FIXED_LANDSCAPE_MODE, ALLOW_ROTATION will only be used on Tablets and foldables
+        // afterwards.
+        if (getDeviceProfile().isPhone) {
             LauncherPrefs.get(this).put(LauncherPrefs.ALLOW_ROTATION, false);
+        } else if (getDeviceProfile().isTablet) {
+            // Tablet do not use fixed landscape mode, make sure it can't be activated by mistake
+            LauncherPrefs.get(this).put(FIXED_LANDSCAPE_MODE, false);
         }
         getRotationHelper().setFixedLandscape(
                 Objects.requireNonNull(mDeviceProfile.inv).isFixedLandscape
@@ -815,7 +825,6 @@
                     this, getMultiWindowDisplaySize());
         }
 
-        onDeviceProfileInitiated();
         if (FOLDABLE_SINGLE_PAGE.get() && mDeviceProfile.isTwoPanels) {
             mCellPosMapper = new TwoPanelCellPosMapper(mDeviceProfile.inv.numColumns);
         } else {
@@ -827,26 +836,6 @@
         return true;
     }
 
-    @Override
-    public void invalidateParent(ItemInfo info) {
-        if (info.container >= 0) {
-            View collectionIcon = getWorkspace().getHomescreenIconByItemId(info.container);
-            if (collectionIcon instanceof FolderIcon folderIcon
-                    && collectionIcon.getTag() instanceof FolderInfo) {
-                if (createFolderGridOrganizer(getDeviceProfile())
-                        .setFolderInfo((FolderInfo) folderIcon.getTag())
-                        .isItemInPreview(info.rank)) {
-                    folderIcon.invalidate();
-                }
-            } else if (collectionIcon instanceof AppPairIcon appPairIcon
-                    && collectionIcon.getTag() instanceof AppPairInfo appPairInfo) {
-                if (appPairInfo.getContents().contains(info)) {
-                    appPairIcon.getIconDrawableArea().redraw();
-                }
-            }
-        }
-    }
-
     /**
      * Returns whether we should delay spring loaded mode -- for shortcuts and widgets that have
      * a configuration step, this allows the proper animations to run after other transitions.
@@ -865,7 +854,6 @@
             case REQUEST_CREATE_SHORTCUT:
                 completeAddShortcut(intent, info.container, screenId,
                         cellPos.cellX, cellPos.cellY, info);
-                announceForAccessibility(R.string.item_added_to_workspace);
                 break;
             case REQUEST_CREATE_APPWIDGET:
                 completeAddAppWidget(appWidgetId, info, null, null, false, true, null);
@@ -1476,7 +1464,10 @@
             }
 
             getModelWriter().addItemToDatabase(info, container, screenId, cellXY[0], cellXY[1]);
-            mWorkspace.addInScreen(view, info);
+            AnimatorSet anim = new AnimatorSet();
+            anim.addListener(forEndCallback(() ->
+                    view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)));
+            bindInflatedItems(Collections.singletonList(Pair.create(info, view)), anim);
         } else {
             // Adding a shortcut to a Folder.
             FolderIcon folderIcon = findFolderIcon(container);
@@ -1491,7 +1482,7 @@
 
     @Override
     public @Nullable FolderIcon findFolderIcon(final int folderIconId) {
-        return (FolderIcon) mWorkspace.getHomescreenIconByItemId(folderIconId);
+        return (FolderIcon) mWorkspace.getViewByItemId(folderIconId);
     }
 
     /**
@@ -1566,7 +1557,6 @@
         if (!enableAddAppWidgetViaConfigActivityV2() || hostView.getParent() == null) {
             mWorkspace.addInScreen(hostView, launcherInfo);
         }
-        announceForAccessibility(R.string.item_added_to_workspace);
 
         // Show the widget resize frame.
         if (hostView instanceof LauncherAppWidgetHostView) {
@@ -1605,11 +1595,6 @@
 
     private final ScreenOnListener mScreenOnListener = this::onScreenOnChanged;
 
-    private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
-        mWorkspace.updateNotificationDots(updatedDots);
-        mAppsView.getAppsStore().updateNotificationDots(updatedDots);
-    }
-
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
@@ -1675,15 +1660,13 @@
                 }
             }
 
-            if (FeatureFlags.enableSplitContextually()) {
-                handleSplitAnimationGoingToHome(LAUNCHER_SPLIT_SELECTION_EXIT_HOME);
-            }
+            handleSplitAnimationGoingToHome(LAUNCHER_SPLIT_SELECTION_EXIT_HOME);
             mOverlayManager.hideOverlay(isStarted());
             handleGestureContract(intent);
         } else if (Intent.ACTION_ALL_APPS.equals(intent.getAction())) {
             showAllAppsFromIntent(alreadyOnHome);
         } else if (INTENT_ACTION_ALL_APPS_TOGGLE.equals(intent.getAction())) {
-            toggleAllAppsSearch(alreadyOnHome);
+            toggleAllApps(alreadyOnHome, true);
         } else if (Intent.ACTION_SHOW_WORK_APPS.equals(intent.getAction())) {
             showAllAppsWithSelectedTabFromIntent(alreadyOnHome,
                     ActivityAllAppsContainerView.AdapterHolder.WORK);
@@ -1697,12 +1680,15 @@
         // Overridden
     }
 
-    /** Toggles Launcher All Apps with keyboard ready for search. */
-    public void toggleAllAppsSearch() {
-        toggleAllAppsSearch(/* alreadyOnHome= */ true);
+    /**
+     * Toggles Launcher All Apps.
+     * @param focusSearch Indicates whether to make All Apps keyboard ready for search.
+     */
+    public void toggleAllApps(boolean focusSearch) {
+        toggleAllApps(/* alreadyOnHome= */ true, focusSearch);
     }
 
-    protected void toggleAllAppsSearch(boolean alreadyOnHome) {
+    private void toggleAllApps(boolean alreadyOnHome, boolean focusSearch) {
         if (getStateManager().isInStableState(ALL_APPS)) {
             getStateManager().goToState(NORMAL, alreadyOnHome);
         } else {
@@ -1714,7 +1700,8 @@
                     new AnimationSuccessListener() {
                         @Override
                         public void onAnimationSuccess(Animator animator) {
-                            if (mAppsView.getSearchUiManager().getEditText() != null) {
+                            if (focusSearch
+                                    && mAppsView.getSearchUiManager().getEditText() != null) {
                                 mAppsView.getSearchUiManager().getEditText().requestFocus();
                             }
                         }
@@ -1813,6 +1800,7 @@
 
         mAppWidgetHolder.stopListening();
         mAppWidgetHolder.destroy();
+        mWidgetPickerDataProvider.destroy();
 
         TextKeyListener.getInstance().release();
         mModelCallbacks.clearPendingBinds();
@@ -1893,7 +1881,8 @@
             if (dropView != null && dropView.containsAppWidgetHostView()) {
                 // Extracting Bitmap from dropView instead of its content view produces the correct
                 // bitmap.
-                widgetPreviewBitmap = getBitmapFromView(dropView);
+                widgetPreviewBitmap = createHardwareBitmap(
+                        dropView.getWidth(), dropView.getHeight(), dropView::draw);
             }
         }
 
@@ -2063,7 +2052,7 @@
     public boolean removeItem(View v, final ItemInfo itemInfo, boolean deleteFromDb,
             @Nullable final String reason) {
         if (itemInfo instanceof WorkspaceItemInfo) {
-            View collectionIcon = mWorkspace.getHomescreenIconByItemId(itemInfo.container);
+            View collectionIcon = mWorkspace.getViewByItemId(itemInfo.container);
             if (collectionIcon instanceof FolderIcon) {
                 // Remove the shortcut from the folder before removing it from launcher
                 ((FolderInfo) collectionIcon.getTag()).remove((WorkspaceItemInfo) itemInfo, true);
@@ -2300,7 +2289,8 @@
             if (item.container == CONTAINER_DESKTOP) {
                 CellLayout cl = mWorkspace.getScreenWithId(presenterPos.screenId);
                 if (cl != null && cl.isOccupied(presenterPos.cellX, presenterPos.cellY)) {
-                    Object tag = cl.getChildAt(presenterPos.cellX, presenterPos.cellY).getTag();
+                    View occupiedView = cl.getChildAt(presenterPos.cellX, presenterPos.cellY);
+                    Object tag = occupiedView == null ? null : occupiedView.getTag();
                     String desc = "Collision while binding workspace item: " + item
                             + ". Collides with " + tag;
                     if (FeatureFlags.IS_STUDIO_BUILD) {
@@ -2441,52 +2431,25 @@
     }
 
     /**
-     * Similar to {@link #getFirstMatch} but optimized to finding a suitable view for the app close
-     * animation.
+     * Finds the first view on homescreen matching the provided parameters, optimized to finding a
+     * suitable view for the app close animation.
      *
      * @param svi The StableViewInfo of the preferred item to match to if it exists or null
      * @param packageName The package name of the app to match.
      * @param user The user of the app to match.
-     * @param supportsAllAppsState If true and we are in All Apps state, looks for view in All Apps.
-     *                             Else we only looks on the workspace.
      */
-    public @Nullable View getFirstMatchForAppClose(
-            @Nullable StableViewInfo svi, String packageName,
-            UserHandle user, boolean supportsAllAppsState) {
+    public @Nullable View getFirstHomeElementForAppClose(
+            @Nullable StableViewInfo svi, String packageName, UserHandle user) {
         final Predicate<ItemInfo> preferredItem = svi == null ? i -> false : svi::matches;
-        final Predicate<ItemInfo> packageAndUserAndApp = info ->
-                info != null
-                        && info.itemType == ITEM_TYPE_APPLICATION
-                        && info.user.equals(user)
-                        && info.getTargetComponent() != null
-                        && TextUtils.equals(info.getTargetComponent().getPackageName(),
-                        packageName);
-
-        if (supportsAllAppsState && isInState(LauncherState.ALL_APPS)) {
-            AllAppsRecyclerView activeRecyclerView = mAppsView.getActiveRecyclerView();
-            View v = getFirstMatch(Collections.singletonList(activeRecyclerView),
-                    preferredItem, packageAndUserAndApp);
-
-            if (v != null && activeRecyclerView.computeVerticalScrollOffset() > 0) {
-                RectF locationBounds = new RectF();
-                FloatingIconView.getLocationBoundsForView(this, v, false, locationBounds,
-                        new Rect());
-                if (locationBounds.top < mAppsView.getHeaderBottom()) {
-                    // Icon is covered by scrim, return null to play fallback animation.
-                    return null;
-                }
-            }
-
-            return v;
-        }
+        final Predicate<ItemInfo> packageAndUserAndApp = info -> info != null
+                && info.itemType == ITEM_TYPE_APPLICATION
+                && info.user.equals(user)
+                && TextUtils.equals(info.getTargetPackage(), packageName);
 
         // Look for the item inside the folder at the current page
         Folder folder = Folder.getOpen(this);
         if (folder != null) {
-            View v = getFirstMatch(Collections.singletonList(
-                    folder.getContent().getCurrentCellLayout().getShortcutsAndWidgets()),
-                    preferredItem,
-                    packageAndUserAndApp);
+            View v = folder.getFirstMatch(preferredItem, packageAndUserAndApp);
             if (v == null) {
                 folder.close(isStarted() && !isForceInvisible());
             } else {
@@ -2494,72 +2457,19 @@
             }
         }
 
-        List<ViewGroup> containers = new ArrayList<>(mWorkspace.getPanelCount() + 1);
-        containers.add(mWorkspace.getHotseat().getShortcutsAndWidgets());
-        mWorkspace.forEachVisiblePage(page
-                -> containers.add(((CellLayout) page).getShortcutsAndWidgets()));
+        List<CellLayout> containers = new ArrayList<>(mWorkspace.getPanelCount() + 1);
+        containers.add(mWorkspace.getHotseat());
+        mWorkspace.forEachVisiblePage(page -> containers.add((CellLayout) page));
+        CellLayout[] containerArray = containers.toArray(new CellLayout[0]);
+        LauncherBindableItemsContainer visibleContainer =
+                op -> mapOverCellLayouts(containerArray, op);
 
         // Order: Preferred item by itself or in folder, then by matching package/user
-        return getFirstMatch(containers, preferredItem, forFolderMatch(preferredItem),
+        return visibleContainer.getFirstMatch(
+                preferredItem, forFolderMatch(preferredItem),
                 packageAndUserAndApp, forFolderMatch(packageAndUserAndApp));
     }
 
-    /**
-     * Finds the first view matching the ordered operators across the given viewgroups in order.
-     * @param containers List of ViewGroups to scan, in order of preference.
-     * @param operators List of operators, in order starting from best matching operator.
-     */
-    @Nullable
-    private static View getFirstMatch(Iterable<ViewGroup> containers,
-            final Predicate<ItemInfo>... operators) {
-        for (Predicate<ItemInfo> operator : operators) {
-            for (ViewGroup container : containers) {
-                View match = mapOverViewGroup(container, operator);
-                if (match != null) {
-                    return match;
-                }
-            }
-        }
-        return null;
-    }
-
-    /** Convert a {@link View} to {@link Bitmap}. */
-    private static Bitmap getBitmapFromView(@Nullable View view) {
-        if (view == null) {
-            return null;
-        }
-        Bitmap returnedBitmap =
-                Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(returnedBitmap);
-        view.draw(canvas);
-        return returnedBitmap;
-    }
-
-    /**
-     * Returns the first view matching the operator in the given ViewGroups, or null if none.
-     * Forward iteration matters.
-     */
-    @Nullable
-    private static View mapOverViewGroup(ViewGroup container, Predicate<ItemInfo> op) {
-        final int itemCount = container.getChildCount();
-        for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
-            View item = container.getChildAt(itemIdx);
-            if (item.getVisibility() != View.VISIBLE) {
-                continue;
-            }
-            if (item instanceof ViewGroup viewGroup) {
-                View view = mapOverViewGroup(viewGroup, op);
-                if (view != null) {
-                    return view;
-                }
-            }
-            if (item.getTag() instanceof ItemInfo itemInfo && op.test(itemInfo)) {
-                return item;
-            }
-        }
-        return null;
-    }
-
     private ValueAnimator createNewAppBounceAnimation(View v, int i) {
         ValueAnimator bounceAnim = new PropertyListBuilder().alpha(1).scale(1).build(v)
                 .setDuration(ItemInstallQueue.NEW_SHORTCUT_BOUNCE_DURATION);
@@ -2568,10 +2478,6 @@
         return bounceAnim;
     }
 
-    private void announceForAccessibility(@StringRes int stringResId) {
-        getDragLayer().announceForAccessibility(getString(stringResId));
-    }
-
     /**
      * Informs us that the overlay (-1 screen, typically), has either become visible or invisible.
      */
@@ -2618,25 +2524,12 @@
         mModelCallbacks.bindIncrementalDownloadProgressUpdated(app);
     }
 
-    @Override
-    public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) {
-        mModelCallbacks.bindWidgetsRestored(widgets);
-    }
-
     /**
      * See {@code LauncherBindingDelegate}
      */
     @Override
-    public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
-        mModelCallbacks.bindWorkspaceItemsChanged(updated);
-    }
-
-    /**
-     * See {@code LauncherBindingDelegate}
-     */
-    @Override
-    public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
-        mModelCallbacks.bindRestoreItemsChange(updates);
+    public void bindItemsUpdated(Set<ItemInfo> updates) {
+        mModelCallbacks.bindItemsUpdated(updates);
     }
 
     /**
@@ -2651,9 +2544,8 @@
      * See {@code LauncherBindingDelegate}
      */
     @Override
-    public void bindAllWidgets(@NonNull final List<WidgetsListBaseEntry> allWidgets,
-            @NonNull final List<WidgetsListBaseEntry> defaultWidgets) {
-        mModelCallbacks.bindAllWidgets(allWidgets, defaultWidgets);
+    public void bindAllWidgets(@NonNull final List<WidgetsListBaseEntry> allWidgets) {
+        mModelCallbacks.bindAllWidgets(allWidgets);
     }
 
     @Override
@@ -2873,12 +2765,6 @@
         // Overridden
     }
 
-    @Override
-    public void returnToHomescreen() {
-        super.returnToHomescreen();
-        getStateManager().goToState(LauncherState.NORMAL);
-    }
-
     public void closeOpenViews() {
         closeOpenViews(true);
     }
@@ -2975,11 +2861,6 @@
         return mModelCallbacks.getWorkspaceLoading();
     }
 
-    @Override
-    public boolean isBindingItems() {
-        return isWorkspaceLoading();
-    }
-
     /**
      * Returns true if a touch interaction is in progress
      */
@@ -3054,11 +2935,6 @@
         return mWidgetPickerDataProvider;
     }
 
-    @Override
-    public DotInfo getDotInfoForItem(ItemInfo info) {
-        return mPopupDataProvider.getDotInfoForItem(info);
-    }
-
     @NonNull
     public LauncherOverlayManager getOverlayManager() {
         return mOverlayManager;
@@ -3073,6 +2949,12 @@
         return mDragLayer;
     }
 
+    @NonNull
+    @Override
+    public LauncherBindableItemsContainer getContent() {
+        return mWorkspace;
+    }
+
     @Override
     public ActivityAllAppsContainerView<Launcher> getAppsView() {
         return mAppsView;
@@ -3179,13 +3061,6 @@
         return mAnimationCoordinator;
     }
 
-    /**
-     * Set to force config update when set to true next time onHandleConfigurationChanged is called.
-     */
-    public void setForceConfigUpdate(boolean forceConfigUpdate) {
-        mForceConfigUpdate = forceConfigUpdate;
-    }
-
     @Override
     public View.OnLongClickListener getAllAppsItemLongClickListener() {
         return ItemLongClickListener.INSTANCE_ALL_APPS;
@@ -3210,5 +3085,10 @@
         return findViewById(R.id.popup_container);
     }
 
+    @Override
+    public OnClickListener getItemOnClickListener() {
+        return ItemClickHandler.INSTANCE;
+    }
+
     // End of Getters and Setters
 }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
deleted file mode 100644
index a53238d..0000000
--- a/src/com/android/launcher3/LauncherAppState.java
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * Copyright (C) 2013 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 static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
-import static android.content.Context.RECEIVER_EXPORTED;
-
-import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
-import static com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY;
-import static com.android.launcher3.LauncherPrefs.DB_FILE;
-import static com.android.launcher3.LauncherPrefs.GRID_NAME;
-import static com.android.launcher3.LauncherPrefs.ICON_STATE;
-import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
-import static com.android.launcher3.model.DeviceGridState.KEY_DB_FILE;
-import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
-import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
-import static com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.SharedPreferences;
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.pm.LauncherApps;
-import android.content.pm.LauncherApps.ArchiveCompatibilityParams;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.annotation.Nullable;
-import androidx.core.os.BuildCompat;
-
-import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.icons.IconCache;
-import com.android.launcher3.icons.IconProvider;
-import com.android.launcher3.icons.LauncherIconProvider;
-import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.model.ModelLauncherCallbacks;
-import com.android.launcher3.model.WidgetsFilterDataProvider;
-import com.android.launcher3.notification.NotificationListener;
-import com.android.launcher3.pm.InstallSessionHelper;
-import com.android.launcher3.pm.InstallSessionTracker;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.LockedUserState;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.Preconditions;
-import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.util.SettingsCache;
-import com.android.launcher3.util.SimpleBroadcastReceiver;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.TraceHelper;
-import com.android.launcher3.widget.custom.CustomWidgetManager;
-
-import java.util.Locale;
-import java.util.Objects;
-
-public class LauncherAppState implements SafeCloseable {
-
-    public static final String TAG = "LauncherAppState";
-    public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
-
-    // We do not need any synchronization for this variable as its only written on UI thread.
-    public static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
-            new MainThreadInitializedObject<>(LauncherAppState::new);
-
-    private final Context mContext;
-    private final LauncherModel mModel;
-    private final LauncherIconProvider mIconProvider;
-    private final IconCache mIconCache;
-    private final InvariantDeviceProfile mInvariantDeviceProfile;
-    private boolean mIsSafeModeEnabled;
-
-    private final RunnableList mOnTerminateCallback = new RunnableList();
-
-    public static LauncherAppState getInstance(Context context) {
-        return INSTANCE.get(context);
-    }
-
-    public Context getContext() {
-        return mContext;
-    }
-
-    @SuppressWarnings("NewApi")
-    public LauncherAppState(Context context) {
-        this(context, LauncherFiles.APP_ICONS_DB);
-        Log.v(Launcher.TAG, "LauncherAppState initiated");
-        Preconditions.assertUIThread();
-
-        mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",
-                () -> context.getPackageManager().isSafeMode());
-        mInvariantDeviceProfile.addOnChangeListener(modelPropertiesChanged -> {
-            if (modelPropertiesChanged) {
-                refreshAndReloadLauncher();
-            }
-        });
-
-        ModelLauncherCallbacks callbacks = mModel.newModelCallbacks();
-        LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
-        launcherApps.registerCallback(callbacks);
-        mOnTerminateCallback.add(() ->
-                mContext.getSystemService(LauncherApps.class).unregisterCallback(callbacks));
-
-        if (BuildCompat.isAtLeastV() && Flags.enableSupportForArchiving()) {
-            ArchiveCompatibilityParams params = new ArchiveCompatibilityParams();
-            params.setEnableUnarchivalConfirmation(false);
-            params.setEnableIconOverlay(!Flags.useNewIconForArchivedApps());
-            launcherApps.setArchiveCompatibility(params);
-        }
-
-        SimpleBroadcastReceiver modelChangeReceiver =
-                new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, mModel::onBroadcastIntent);
-        final Locale oldLocale = mContext.getResources().getConfiguration().locale;
-        modelChangeReceiver.register(
-                mContext,
-                () -> {
-                    // if local has changed before receiver is registered on bg thread,
-                    // mModel needs to reload.
-                    Locale newLocale = mContext.getResources().getConfiguration().locale;
-                    if (!Objects.equals(oldLocale, newLocale)) {
-                        mModel.forceReload();
-                    }
-                },
-                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(() -> modelChangeReceiver.unregisterReceiverSafely(mContext));
-
-        SafeCloseable userChangeListener = UserCache.INSTANCE.get(mContext)
-                .addUserEventListener(mModel::onUserEvent);
-        mOnTerminateCallback.add(userChangeListener::close);
-
-        if (enableSmartspaceRemovalToggle()) {
-            OnSharedPreferenceChangeListener firstPagePinnedItemListener =
-                    new OnSharedPreferenceChangeListener() {
-                        @Override
-                        public void onSharedPreferenceChanged(
-                                SharedPreferences sharedPreferences, String key) {
-                            if (SMARTSPACE_ON_HOME_SCREEN.equals(key)) {
-                                mModel.forceReload();
-                            }
-                        }
-                    };
-            LauncherPrefs.getPrefs(mContext).registerOnSharedPreferenceChangeListener(
-                    firstPagePinnedItemListener);
-            mOnTerminateCallback.add(() -> LauncherPrefs.getPrefs(mContext)
-                    .unregisterOnSharedPreferenceChangeListener(firstPagePinnedItemListener));
-        }
-
-        LockedUserState.get(context).runOnUserUnlocked(() -> {
-            CustomWidgetManager cwm = CustomWidgetManager.INSTANCE.get(mContext);
-            mOnTerminateCallback.add(cwm.addWidgetRefreshCallback(mModel::rebindCallbacks)::close);
-
-            IconObserver observer = new IconObserver();
-            SafeCloseable iconChangeTracker = mIconProvider.registerIconChangeListener(
-                    observer, MODEL_EXECUTOR.getHandler());
-            mOnTerminateCallback.add(iconChangeTracker::close);
-            MODEL_EXECUTOR.execute(observer::verifyIconChanged);
-            LauncherPrefs.get(context).addListener(observer, THEMED_ICONS);
-            mOnTerminateCallback.add(
-                    () -> LauncherPrefs.get(mContext).removeListener(observer, THEMED_ICONS));
-
-            InstallSessionTracker installSessionTracker =
-                    InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(callbacks);
-            mOnTerminateCallback.add(installSessionTracker::unregister);
-        });
-
-        // Register an observer to rebind the notification listener when dots are re-enabled.
-        SettingsCache settingsCache = SettingsCache.INSTANCE.get(mContext);
-        SettingsCache.OnChangeListener notificationLister = this::onNotificationSettingsChanged;
-        settingsCache.register(NOTIFICATION_BADGING_URI, notificationLister);
-        onNotificationSettingsChanged(settingsCache.getValue(NOTIFICATION_BADGING_URI));
-        mOnTerminateCallback.add(() ->
-                settingsCache.unregister(NOTIFICATION_BADGING_URI, notificationLister));
-        // Register an observer to notify Launcher about Private Space settings toggle.
-        registerPrivateSpaceHideWhenLockListener(settingsCache);
-    }
-
-    public LauncherAppState(Context context, @Nullable String iconCacheFileName) {
-        mContext = context;
-
-        mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context);
-        mIconProvider = new LauncherIconProvider(context);
-        mIconCache = new IconCache(mContext, mInvariantDeviceProfile,
-                iconCacheFileName, mIconProvider);
-        mModel = new LauncherModel(context, this, mIconCache,
-                WidgetsFilterDataProvider.Companion.newInstance(context), new AppFilter(mContext),
-                PackageManagerHelper.INSTANCE.get(context), iconCacheFileName != null);
-        mOnTerminateCallback.add(mIconCache::close);
-        mOnTerminateCallback.add(mModel::destroy);
-    }
-
-    private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) {
-        if (areNotificationDotsEnabled) {
-            NotificationListener.requestRebind(new ComponentName(
-                    mContext, NotificationListener.class));
-        }
-    }
-
-    private void registerPrivateSpaceHideWhenLockListener(SettingsCache settingsCache) {
-        SettingsCache.OnChangeListener psHideWhenLockChangedListener =
-                this::onPrivateSpaceHideWhenLockChanged;
-        settingsCache.register(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, psHideWhenLockChangedListener);
-        mOnTerminateCallback.add(() -> settingsCache.unregister(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI,
-                psHideWhenLockChangedListener));
-    }
-
-    private void onPrivateSpaceHideWhenLockChanged(boolean isPrivateSpaceHideOnLockEnabled) {
-        mModel.forceReload();
-    }
-
-    private void refreshAndReloadLauncher() {
-        LauncherIcons.clearPool(mContext);
-        mIconCache.updateIconParams(
-                mInvariantDeviceProfile.fillResIconDpi, mInvariantDeviceProfile.iconBitmapSize);
-        mModel.forceReload();
-    }
-
-    /**
-     * Call from Application.onTerminate(), which is not guaranteed to ever be called.
-     */
-    @Override
-    public void close() {
-        mOnTerminateCallback.executeAllAndDestroy();
-    }
-
-    public IconProvider getIconProvider() {
-        return mIconProvider;
-    }
-
-    public IconCache getIconCache() {
-        return mIconCache;
-    }
-
-    public LauncherModel getModel() {
-        return mModel;
-    }
-
-    public InvariantDeviceProfile getInvariantDeviceProfile() {
-        return mInvariantDeviceProfile;
-    }
-
-    public boolean isSafeModeEnabled() {
-        return mIsSafeModeEnabled;
-    }
-
-    /**
-     * Shorthand for {@link #getInvariantDeviceProfile()}
-     */
-    public static InvariantDeviceProfile getIDP(Context context) {
-        return InvariantDeviceProfile.INSTANCE.get(context);
-    }
-
-    private class IconObserver
-            implements IconProvider.IconChangeListener, LauncherPrefChangeListener {
-
-        @Override
-        public void onAppIconChanged(String packageName, UserHandle user) {
-            mModel.onAppIconChanged(packageName, user);
-        }
-
-        @Override
-        public void onSystemIconStateChanged(String iconState) {
-            IconShape.INSTANCE.get(mContext).pickBestShape(mContext);
-            refreshAndReloadLauncher();
-            LauncherPrefs.get(mContext).put(ICON_STATE, iconState);
-        }
-
-        void verifyIconChanged() {
-            String iconState = mIconProvider.getSystemIconState();
-            if (!iconState.equals(LauncherPrefs.get(mContext).get(ICON_STATE))) {
-                onSystemIconStateChanged(iconState);
-            }
-        }
-
-        @Override
-        public void onPrefChanged(String key) {
-            if (Themes.KEY_THEMED_ICONS.equals(key)) {
-                mIconProvider.setIconThemeSupported(Themes.isThemedIconEnabled(mContext));
-                verifyIconChanged();
-            } else if (GRID_NAME_PREFS_KEY.equals(key)) {
-                FileLog.d(TAG, "onPrefChanged GRID_NAME changed: "
-                        + LauncherPrefs.get(mContext).get(GRID_NAME));
-            } else if (KEY_DB_FILE.equals(key)) {
-                FileLog.d(TAG, "onPrefChanged DB_FILE changed: "
-                        + LauncherPrefs.get(mContext).get(DB_FILE));
-            }
-        }
-    }
-}
diff --git a/src/com/android/launcher3/LauncherAppState.kt b/src/com/android/launcher3/LauncherAppState.kt
new file mode 100644
index 0000000..ff84c3c
--- /dev/null
+++ b/src/com/android/launcher3/LauncherAppState.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013 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 android.content.Context
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.icons.LauncherIconProvider
+import com.android.launcher3.util.DaggerSingletonObject
+import javax.inject.Inject
+import javax.inject.Named
+
+/** A collection of common dependencies used across Launcher */
+@Deprecated("Inject the specific targets directly instead of using LauncherAppState")
+data class LauncherAppState
+@Inject
+constructor(
+    @ApplicationContext val context: Context,
+    val iconProvider: LauncherIconProvider,
+    val iconCache: IconCache,
+    val model: LauncherModel,
+    val invariantDeviceProfile: InvariantDeviceProfile,
+    @Named("SAFE_MODE") val isSafeModeEnabled: Boolean,
+) {
+
+    companion object {
+
+        @JvmField var INSTANCE = DaggerSingletonObject { it.launcherAppState }
+
+        @JvmStatic fun getInstance(context: Context) = INSTANCE[context]
+
+        /** Shorthand for [.getInvariantDeviceProfile] */
+        @JvmStatic fun getIDP(context: Context) = InvariantDeviceProfile.INSTANCE[context]
+    }
+}
diff --git a/src/com/android/launcher3/LauncherApplication.java b/src/com/android/launcher3/LauncherApplication.java
index 678901b..03eaeea 100644
--- a/src/com/android/launcher3/LauncherApplication.java
+++ b/src/com/android/launcher3/LauncherApplication.java
@@ -20,6 +20,7 @@
 import com.android.launcher3.dagger.DaggerLauncherAppComponent;
 import com.android.launcher3.dagger.LauncherAppComponent;
 import com.android.launcher3.dagger.LauncherBaseAppComponent;
+import com.android.launcher3.util.TraceHelper;
 
 /**
  * Main application class for Launcher
@@ -41,7 +42,8 @@
                 if (mAppComponent == null) {
                     // Initialize the dagger component on demand as content providers can get
                     // accessed before the Launcher application (b/36917845#comment4)
-                    initDaggerComponent(DaggerLauncherAppComponent.builder());
+                    initDaggerComponent(DaggerLauncherAppComponent.builder()
+                            .iconsDbName(LauncherFiles.APP_ICONS_DB));
                 }
             }
         }
@@ -55,7 +57,11 @@
     /**
      * Init with the desired dagger component.
      */
-    public void initDaggerComponent(LauncherAppComponent.Builder componentBuilder) {
-        mAppComponent = componentBuilder.appContext(this).build();
+    public void initDaggerComponent(LauncherBaseAppComponent.Builder componentBuilder) {
+        mAppComponent = componentBuilder
+                .appContext(this)
+                .setSafeModeEnabled(TraceHelper.allowIpcs(
+                        "isSafeMode", () -> getPackageManager().isSafeMode()))
+                .build();
     }
 }
diff --git a/src/com/android/launcher3/LauncherModel.kt b/src/com/android/launcher3/LauncherModel.kt
index b56df46..add0ad8 100644
--- a/src/com/android/launcher3/LauncherModel.kt
+++ b/src/com/android/launcher3/LauncherModel.kt
@@ -15,16 +15,16 @@
  */
 package com.android.launcher3
 
-import android.app.admin.DevicePolicyManager
 import android.content.Context
 import android.content.Intent
 import android.content.pm.ShortcutInfo
 import android.os.UserHandle
 import android.text.TextUtils
-import android.util.Log
 import android.util.Pair
 import androidx.annotation.WorkerThread
 import com.android.launcher3.celllayout.CellPosMapper
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.icons.IconCache
 import com.android.launcher3.model.AddWorkspaceItemsTask
 import com.android.launcher3.model.AllAppsList
@@ -35,6 +35,7 @@
 import com.android.launcher3.model.LoaderTask
 import com.android.launcher3.model.ModelDbController
 import com.android.launcher3.model.ModelDelegate
+import com.android.launcher3.model.ModelInitializer
 import com.android.launcher3.model.ModelLauncherCallbacks
 import com.android.launcher3.model.ModelTaskController
 import com.android.launcher3.model.ModelWriter
@@ -42,35 +43,42 @@
 import com.android.launcher3.model.ReloadStringCacheTask
 import com.android.launcher3.model.ShortcutsChangedTask
 import com.android.launcher3.model.UserLockStateChangedTask
-import com.android.launcher3.model.WidgetsFilterDataProvider
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.pm.UserCache
 import com.android.launcher3.shortcuts.ShortcutRequest
-import com.android.launcher3.testing.shared.TestProtocol.sDebugTracing
+import com.android.launcher3.util.DaggerSingletonTracker
 import com.android.launcher3.util.Executors.MAIN_EXECUTOR
 import com.android.launcher3.util.Executors.MODEL_EXECUTOR
-import com.android.launcher3.util.PackageManagerHelper
 import com.android.launcher3.util.PackageUserKey
 import com.android.launcher3.util.Preconditions
 import java.io.FileDescriptor
 import java.io.PrintWriter
 import java.util.concurrent.CancellationException
 import java.util.function.Consumer
+import javax.inject.Inject
+import javax.inject.Named
+import javax.inject.Provider
 
 /**
  * Maintains in-memory state of the Launcher. It is expected that there should be only one
  * LauncherModel object held in a static. Also provide APIs for updating the database state for the
  * Launcher.
  */
-class LauncherModel(
-    private val context: Context,
-    private val mApp: LauncherAppState,
+@LauncherAppSingleton
+class LauncherModel
+@Inject
+constructor(
+    @ApplicationContext private val context: Context,
+    private val appProvider: Provider<LauncherAppState>,
     private val iconCache: IconCache,
-    private val widgetsFilterDataProvider: WidgetsFilterDataProvider,
+    private val prefs: LauncherPrefs,
+    private val installQueue: ItemInstallQueue,
     appFilter: AppFilter,
-    mPmHelper: PackageManagerHelper,
-    isPrimaryInstance: Boolean,
+    @Named("ICONS_DB") dbFileName: String?,
+    initializer: ModelInitializer,
+    lifecycle: DaggerSingletonTracker,
+    val modelDelegate: ModelDelegate,
 ) {
 
     private val mCallbacksList = ArrayList<BgDataModel.Callbacks>(1)
@@ -84,16 +92,6 @@
      */
     private val mBgDataModel = BgDataModel()
 
-    val modelDelegate: ModelDelegate =
-        ModelDelegate.newInstance(
-            context,
-            mApp,
-            mPmHelper,
-            mBgAllAppsList,
-            mBgDataModel,
-            isPrimaryInstance,
-        )
-
     val modelDbController = ModelDbController(context)
 
     private val mLock = Any()
@@ -128,6 +126,14 @@
         }
     }
 
+    init {
+        if (!dbFileName.isNullOrEmpty()) {
+            initializer.initialize(this)
+        }
+        lifecycle.addCloseable { destroy() }
+        modelDelegate.init(this, mBgAllAppsList, mBgDataModel)
+    }
+
     fun newModelCallbacks() = ModelLauncherCallbacks(this::enqueueModelUpdateTask)
 
     /** Adds the provided items to the workspace. */
@@ -140,12 +146,7 @@
         verifyChanges: Boolean,
         cellPosMapper: CellPosMapper?,
         owner: BgDataModel.Callbacks?,
-    ) = ModelWriter(mApp.context, this, mBgDataModel, verifyChanges, cellPosMapper, owner)
-
-    /** Returns the [WidgetsFilterDataProvider] that manages widget filters. */
-    fun getWidgetsFilterDataProvider(): WidgetsFilterDataProvider {
-        return widgetsFilterDataProvider
-    }
+    ) = ModelWriter(context, this, mBgDataModel, verifyChanges, cellPosMapper, owner)
 
     /** Called when the icon for an app changes, outside of package event */
     @WorkerThread
@@ -167,22 +168,11 @@
     /** Called when the model is destroyed */
     fun destroy() {
         mModelDestroyed = true
-        MODEL_EXECUTOR.execute {
-            modelDelegate.destroy()
-            widgetsFilterDataProvider.destroy()
-        }
+        MODEL_EXECUTOR.execute { modelDelegate.destroy() }
     }
 
-    fun onBroadcastIntent(intent: Intent) {
-        if (DEBUG_RECEIVER || sDebugTracing) Log.d(TAG, "onReceive intent=$intent")
-        when (intent.action) {
-            Intent.ACTION_LOCALE_CHANGED,
-            LauncherAppState.ACTION_FORCE_ROLOAD ->
-                // If we have changed locale we need to clear out the labels in all apps/workspace.
-                forceReload()
-            DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED ->
-                enqueueModelUpdateTask(ReloadStringCacheTask(this.modelDelegate))
-        }
+    fun reloadStringCache() {
+        enqueueModelUpdateTask(ReloadStringCacheTask(this.modelDelegate))
     }
 
     /**
@@ -213,7 +203,7 @@
             UserCache.ACTION_PROFILE_UNLOCKED ->
                 enqueueModelUpdateTask(UserLockStateChangedTask(user, true))
             Intent.ACTION_MANAGED_PROFILE_REMOVED -> {
-                LauncherPrefs.get(mApp.context).put(LauncherPrefs.WORK_EDU_STEP, 0)
+                prefs.put(LauncherPrefs.WORK_EDU_STEP, 0)
                 forceReload()
             }
             UserCache.ACTION_PROFILE_ADDED,
@@ -244,6 +234,13 @@
         rebindCallbacks()
     }
 
+    /** Reloads the model if it is already in use */
+    fun reloadIfActive() {
+        val wasActive: Boolean
+        synchronized(mLock) { wasActive = mModelLoaded || stopLoader() }
+        if (wasActive) forceReload()
+    }
+
     /** Rebinds all existing callbacks with already loaded model */
     fun rebindCallbacks() {
         if (hasCallbacks()) {
@@ -291,7 +288,7 @@
 
     private fun startLoader(newCallbacks: Array<BgDataModel.Callbacks>): Boolean {
         // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
-        ItemInstallQueue.INSTANCE.get(context).pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING)
+        installQueue.pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING)
         synchronized(mLock) {
             // If there is already one running, tell it to stop.
             val wasRunning = stopLoader()
@@ -303,7 +300,12 @@
                 callbacksList.forEach { MAIN_EXECUTOR.execute(it::clearPendingBinds) }
 
                 val launcherBinder =
-                    BaseLauncherBinder(mApp, mBgDataModel, mBgAllAppsList, callbacksList)
+                    BaseLauncherBinder(
+                        appProvider.get(),
+                        mBgDataModel,
+                        mBgAllAppsList,
+                        callbacksList,
+                    )
                 if (bindDirectly) {
                     // Divide the set of loaded items into those that we are binding synchronously,
                     // and everything else that is to be bound normally (asynchronously).
@@ -315,19 +317,19 @@
                     launcherBinder.bindWidgets()
                     return true
                 } else {
-                    mLoaderTask =
+                    val task =
                         LoaderTask(
-                            mApp,
+                            appProvider.get(),
                             mBgAllAppsList,
                             mBgDataModel,
                             this.modelDelegate,
                             launcherBinder,
-                            widgetsFilterDataProvider,
                         )
+                    mLoaderTask = task
 
                     // Always post the loader task, instead of running directly
                     // (even on same thread) so that we exit any nested synchronized blocks
-                    MODEL_EXECUTOR.post(mLoaderTask)
+                    MODEL_EXECUTOR.post(task)
                 }
             }
         }
@@ -423,15 +425,7 @@
     /** Called when the labels for the widgets has updated in the icon cache. */
     fun onWidgetLabelsUpdated(updatedPackages: HashSet<String?>, user: UserHandle) {
         enqueueModelUpdateTask { taskController, dataModel, _ ->
-            dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, mApp)
-            taskController.bindUpdatedWidgets(dataModel)
-        }
-    }
-
-    /** Called when the widget filters are refreshed and available to bind to the model. */
-    fun onWidgetFiltersLoaded() {
-        enqueueModelUpdateTask { taskController, dataModel, _ ->
-            dataModel.widgetsModel.updateWidgetFilters(widgetsFilterDataProvider)
+            dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, appProvider.get())
             taskController.bindUpdatedWidgets(dataModel)
         }
     }
@@ -446,7 +440,13 @@
                 return@execute
             }
             task.execute(
-                ModelTaskController(mApp, mBgDataModel, mBgAllAppsList, this, MAIN_EXECUTOR),
+                ModelTaskController(
+                    appProvider.get(),
+                    mBgDataModel,
+                    mBgAllAppsList,
+                    this,
+                    MAIN_EXECUTOR,
+                ),
                 mBgDataModel,
                 mBgAllAppsList,
             )
@@ -507,8 +507,6 @@
         }
 
     companion object {
-        private const val DEBUG_RECEIVER = false
-
         const val TAG = "Launcher.Model"
     }
 }
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 5b9c2fa..7a04b0f 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -21,74 +21,226 @@
 import androidx.annotation.VisibleForTesting
 import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
 import com.android.launcher3.InvariantDeviceProfile.GRID_NAME_PREFS_KEY
+import com.android.launcher3.InvariantDeviceProfile.NON_FIXED_LANDSCAPE_GRID_NAME_PREFS_KEY
 import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
 import com.android.launcher3.LauncherFiles.SHARED_PREFERENCES_KEY
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.model.DeviceGridState
 import com.android.launcher3.pm.InstallSessionHelper
 import com.android.launcher3.provider.RestoreDbTask
 import com.android.launcher3.provider.RestoreDbTask.FIRST_LOAD_AFTER_RESTORE_KEY
 import com.android.launcher3.settings.SettingsActivity
 import com.android.launcher3.states.RotationHelper
+import com.android.launcher3.util.DaggerSingletonObject
 import com.android.launcher3.util.DisplayController
-import com.android.launcher3.util.MainThreadInitializedObject
-import com.android.launcher3.util.SafeCloseable
-import com.android.launcher3.util.Themes
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
 
 /**
  * Manages Launcher [SharedPreferences] through [Item] instances.
  *
  * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
  */
-abstract class LauncherPrefs : SafeCloseable {
+@LauncherAppSingleton
+open class LauncherPrefs
+@Inject
+constructor(@ApplicationContext private val encryptedContext: Context) {
+
+    private val deviceProtectedSharedPrefs: SharedPreferences by lazy {
+        encryptedContext
+            .createDeviceProtectedStorageContext()
+            .getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE)
+    }
+
+    open protected fun getSharedPrefs(item: Item): SharedPreferences =
+        item.run {
+            if (encryptionType == EncryptionType.DEVICE_PROTECTED) deviceProtectedSharedPrefs
+            else encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
+        }
 
     /** Returns the value with type [T] for [item]. */
-    abstract fun <T> get(item: ContextualItem<T>): T
+    fun <T> get(item: ContextualItem<T>): T =
+        getInner(item, item.defaultValueFromContext(encryptedContext))
 
     /** Returns the value with type [T] for [item]. */
-    abstract fun <T> get(item: ConstantItem<T>): T
+    fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
 
-    /** Stores the values for each item in preferences. */
-    abstract fun put(vararg itemsToValues: Pair<Item, Any>)
+    /**
+     * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
+     * default value type, and will throw an error if the type of the item provided is not a
+     * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set<String>`.
+     */
+    @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
+    private fun <T> getInner(item: Item, default: T): T {
+        val sp = getSharedPrefs(item)
+        return when {
+            item.type == String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
+            item.type == Boolean::class.java || item.type == java.lang.Boolean::class.java ->
+                sp.getBoolean(item.sharedPrefKey, default as Boolean)
+            item.type == Int::class.java || item.type == java.lang.Integer::class.java ->
+                sp.getInt(item.sharedPrefKey, default as Int)
+            item.type == Float::class.java || item.type == java.lang.Float::class.java ->
+                sp.getFloat(item.sharedPrefKey, default as Float)
+            item.type == Long::class.java || item.type == java.lang.Long::class.java ->
+                sp.getLong(item.sharedPrefKey, default as Long)
+            Set::class.java.isAssignableFrom(item.type) ->
+                sp.getStringSet(item.sharedPrefKey, default as? Set<String>)
+            else ->
+                throw IllegalArgumentException(
+                    "item type: ${item.type}" + " is not compatible with sharedPref methods"
+                )
+        }
+            as T
+    }
 
-    /** Stores the [value] with type [T] for [item] in preferences. */
-    abstract fun <T : Any> put(item: Item, value: T)
+    /**
+     * Stores each of the values provided in `SharedPreferences` according to the configuration
+     * contained within the associated items provided. Internally, it uses apply, so the caller
+     * cannot assume that the values that have been put are immediately available for use.
+     *
+     * The forEach loop is necessary here since there is 1 `SharedPreference.Editor` returned from
+     * prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the
+     * provided item configurations.
+     */
+    fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
+        prepareToPutValues(itemsToValues).forEach { it.apply() }
 
-    /** Synchronous version of [put]. */
-    abstract fun putSync(vararg itemsToValues: Pair<Item, Any>)
+    /** See referenced `put` method above. */
+    fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
 
-    /** Registers [listener] for [items]. */
-    abstract fun addListener(listener: LauncherPrefChangeListener, vararg items: Item)
+    /**
+     * Synchronously stores all the values provided according to their associated Item
+     * configuration.
+     */
+    fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
+        prepareToPutValues(itemsToValues).forEach { it.commit() }
 
-    /** Unregisters [listener] for [items]. */
-    abstract fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item)
+    /**
+     * Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If
+     * the item is boot aware, this method updates both the boot aware and the encrypted files. This
+     * is done because: 1) It allows for easy roll-back if the data is already in encrypted prefs
+     * and we need to turn off the boot aware data feature & 2) It simplifies Backup/Restore, which
+     * already points to encrypted storage.
+     *
+     * Returns a list of editors with all transactions added so that the caller can determine to use
+     * .apply() or .commit()
+     */
+    private fun prepareToPutValues(
+        updates: Array<out Pair<Item, Any>>
+    ): List<SharedPreferences.Editor> {
+        val updatesPerPrefFile = updates.groupBy { getSharedPrefs(it.first) }.toMap()
 
-    /** Returns `true` iff all [items] have a value. */
-    abstract fun has(vararg items: Item): Boolean
+        return updatesPerPrefFile.map { (sharedPref, itemList) ->
+            sharedPref.edit().apply { itemList.forEach { (item, value) -> putValue(item, value) } }
+        }
+    }
 
-    /** Removes the value for each item in [items]. */
-    abstract fun remove(vararg items: Item)
+    /**
+     * Handles adding values to `SharedPreferences` regardless of type. This method is especially
+     * helpful for updating `SharedPreferences` values for `List<<Item>Any>` that have multiple
+     * types of Item values.
+     */
+    @Suppress("UNCHECKED_CAST")
+    internal fun SharedPreferences.Editor.putValue(
+        item: Item,
+        value: Any?,
+    ): SharedPreferences.Editor =
+        when {
+            item.type == String::class.java -> putString(item.sharedPrefKey, value as? String)
+            item.type == Boolean::class.java || item.type == java.lang.Boolean::class.java ->
+                putBoolean(item.sharedPrefKey, value as Boolean)
+            item.type == Int::class.java || item.type == java.lang.Integer::class.java ->
+                putInt(item.sharedPrefKey, value as Int)
+            item.type == Float::class.java || item.type == java.lang.Float::class.java ->
+                putFloat(item.sharedPrefKey, value as Float)
+            item.type == Long::class.java || item.type == java.lang.Long::class.java ->
+                putLong(item.sharedPrefKey, value as Long)
+            Set::class.java.isAssignableFrom(item.type) ->
+                putStringSet(item.sharedPrefKey, value as? Set<String>)
+            else ->
+                throw IllegalArgumentException(
+                    "item type: ${item.type} is not compatible with sharedPref methods"
+                )
+        }
 
-    /** Synchronous version of [remove]. */
-    abstract fun removeSync(vararg items: Item)
+    /**
+     * After calling this method, the listener will be notified of any future updates to the
+     * `SharedPreferences` files associated with the provided list of items. The listener will need
+     * to filter update notifications so they don't activate for non-relevant updates.
+     */
+    fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) {
+        items
+            .map { getSharedPrefs(it) }
+            .distinct()
+            .forEach { it.registerOnSharedPreferenceChangeListener(listener) }
+    }
+
+    /**
+     * Stops the listener from getting notified of any more updates to any of the
+     * `SharedPreferences` files associated with any of the provided list of [Item].
+     */
+    fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) {
+        // If a listener is not registered to a SharedPreference, unregistering it does nothing
+        items
+            .map { getSharedPrefs(it) }
+            .distinct()
+            .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) }
+    }
+
+    /**
+     * Checks if all the provided [Item] have values stored in their corresponding
+     * `SharedPreferences` files.
+     */
+    fun has(vararg items: Item): Boolean {
+        items
+            .groupBy { getSharedPrefs(it) }
+            .forEach { (prefs, itemsSublist) ->
+                if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
+            }
+        return true
+    }
+
+    /**
+     * Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file.
+     */
+    fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
+
+    /** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */
+    fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
+
+    /**
+     * Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the
+     * item is boot aware, this method removes the data from both the boot aware and encrypted
+     * files.
+     *
+     * @return a list of editors with all transactions added so that the caller can determine to use
+     *   .apply() or .commit()
+     */
+    private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
+        val itemsPerFile = items.groupBy { getSharedPrefs(it) }.toMap()
+
+        return itemsPerFile.map { (prefs, items) ->
+            prefs.edit().also { editor ->
+                items.forEach { item -> editor.remove(item.sharedPrefKey) }
+            }
+        }
+    }
 
     companion object {
         @VisibleForTesting const val BOOT_AWARE_PREFS_KEY = "boot_aware_prefs"
 
-        @JvmField
-        var INSTANCE = MainThreadInitializedObject<LauncherPrefs> { LauncherPrefsImpl(it) }
+        @JvmField val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getLauncherPrefs)
 
         @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
 
         const val TASKBAR_PINNING_KEY = "TASKBAR_PINNING_KEY"
         const val TASKBAR_PINNING_DESKTOP_MODE_KEY = "TASKBAR_PINNING_DESKTOP_MODE_KEY"
         const val SHOULD_SHOW_SMARTSPACE_KEY = "SHOULD_SHOW_SMARTSPACE_KEY"
-        @JvmField
-        val ICON_STATE = nonRestorableItem("pref_icon_shape_path", "", EncryptionType.ENCRYPTED)
 
         @JvmField
         val ENABLE_TWOLINE_ALLAPPS_TOGGLE = backedUpItem("pref_enable_two_line_toggle", false)
-        @JvmField
-        val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED)
         @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
         @JvmField val WORK_EDU_STEP = backedUpItem("showed_work_profile_edu", 0)
         @JvmField
@@ -154,6 +306,16 @@
         @JvmField
         val FIXED_LANDSCAPE_MODE = backedUpItem(SettingsActivity.FIXED_LANDSCAPE_MODE, false)
 
+        @JvmField
+        val NON_FIXED_LANDSCAPE_GRID_NAME =
+            ConstantItem(
+                NON_FIXED_LANDSCAPE_GRID_NAME_PREFS_KEY,
+                isBackedUp = true,
+                defaultValue = null,
+                encryptionType = EncryptionType.ENCRYPTED,
+                type = String::class.java,
+            )
+
         // Preferences for widget configurations
         @JvmField
         val RECONFIGURABLE_WIDGET_EDUCATION_TIP_SEEN =
@@ -212,214 +374,6 @@
     }
 }
 
-private class LauncherPrefsImpl(private val encryptedContext: Context) : LauncherPrefs() {
-    private val deviceProtectedStorageContext =
-        encryptedContext.createDeviceProtectedStorageContext()
-
-    private val bootAwarePrefs
-        get() =
-            deviceProtectedStorageContext.getSharedPreferences(BOOT_AWARE_PREFS_KEY, MODE_PRIVATE)
-
-    private val Item.encryptedPrefs
-        get() = encryptedContext.getSharedPreferences(sharedPrefFile, MODE_PRIVATE)
-
-    private fun chooseSharedPreferences(item: Item): SharedPreferences =
-        if (item.encryptionType == EncryptionType.DEVICE_PROTECTED) bootAwarePrefs
-        else item.encryptedPrefs
-
-    /** Wrapper around `getInner` for a `ContextualItem` */
-    override fun <T> get(item: ContextualItem<T>): T =
-        getInner(item, item.defaultValueFromContext(encryptedContext))
-
-    /** Wrapper around `getInner` for an `Item` */
-    override fun <T> get(item: ConstantItem<T>): T = getInner(item, item.defaultValue)
-
-    /**
-     * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
-     * default value type, and will throw an error if the type of the item provided is not a
-     * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set<String>`.
-     */
-    @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
-    private fun <T> getInner(item: Item, default: T): T {
-        val sp = chooseSharedPreferences(item)
-
-        return when (item.type) {
-            String::class.java -> sp.getString(item.sharedPrefKey, default as? String)
-            Boolean::class.java,
-            java.lang.Boolean::class.java -> sp.getBoolean(item.sharedPrefKey, default as Boolean)
-            Int::class.java,
-            java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, default as Int)
-            Float::class.java,
-            java.lang.Float::class.java -> sp.getFloat(item.sharedPrefKey, default as Float)
-            Long::class.java,
-            java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, default as Long)
-            Set::class.java -> sp.getStringSet(item.sharedPrefKey, default as? Set<String>)
-            else ->
-                throw IllegalArgumentException(
-                    "item type: ${item.type}" + " is not compatible with sharedPref methods"
-                )
-        }
-            as T
-    }
-
-    /**
-     * Stores each of the values provided in `SharedPreferences` according to the configuration
-     * contained within the associated items provided. Internally, it uses apply, so the caller
-     * cannot assume that the values that have been put are immediately available for use.
-     *
-     * The forEach loop is necessary here since there is 1 `SharedPreference.Editor` returned from
-     * prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the
-     * provided item configurations.
-     */
-    override fun put(vararg itemsToValues: Pair<Item, Any>): Unit =
-        prepareToPutValues(itemsToValues).forEach { it.apply() }
-
-    /** See referenced `put` method above. */
-    override fun <T : Any> put(item: Item, value: T): Unit = put(item.to(value))
-
-    /**
-     * Synchronously stores all the values provided according to their associated Item
-     * configuration.
-     */
-    override fun putSync(vararg itemsToValues: Pair<Item, Any>): Unit =
-        prepareToPutValues(itemsToValues).forEach { it.commit() }
-
-    /**
-     * Updates the values stored in `SharedPreferences` for each corresponding Item-value pair. If
-     * the item is boot aware, this method updates both the boot aware and the encrypted files. This
-     * is done because: 1) It allows for easy roll-back if the data is already in encrypted prefs
-     * and we need to turn off the boot aware data feature & 2) It simplifies Backup/Restore, which
-     * already points to encrypted storage.
-     *
-     * Returns a list of editors with all transactions added so that the caller can determine to use
-     * .apply() or .commit()
-     */
-    private fun prepareToPutValues(
-        updates: Array<out Pair<Item, Any>>
-    ): List<SharedPreferences.Editor> {
-        val updatesPerPrefFile =
-            updates
-                .filter { it.first.encryptionType != EncryptionType.DEVICE_PROTECTED }
-                .groupBy { it.first.encryptedPrefs }
-                .toMutableMap()
-
-        val bootAwareUpdates =
-            updates.filter { it.first.encryptionType == EncryptionType.DEVICE_PROTECTED }
-        if (bootAwareUpdates.isNotEmpty()) {
-            updatesPerPrefFile[bootAwarePrefs] = bootAwareUpdates
-        }
-
-        return updatesPerPrefFile.map { prefToItemValueList ->
-            prefToItemValueList.key.edit().apply {
-                prefToItemValueList.value.forEach { itemToValue: Pair<Item, Any> ->
-                    putValue(itemToValue.first, itemToValue.second)
-                }
-            }
-        }
-    }
-
-    /**
-     * Handles adding values to `SharedPreferences` regardless of type. This method is especially
-     * helpful for updating `SharedPreferences` values for `List<<Item>Any>` that have multiple
-     * types of Item values.
-     */
-    @Suppress("UNCHECKED_CAST")
-    private fun SharedPreferences.Editor.putValue(
-        item: Item,
-        value: Any?,
-    ): SharedPreferences.Editor =
-        when (item.type) {
-            String::class.java -> putString(item.sharedPrefKey, value as? String)
-            Boolean::class.java,
-            java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean)
-            Int::class.java,
-            java.lang.Integer::class.java -> putInt(item.sharedPrefKey, value as Int)
-            Float::class.java,
-            java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float)
-            Long::class.java,
-            java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long)
-            Set::class.java -> putStringSet(item.sharedPrefKey, value as? Set<String>)
-            else ->
-                throw IllegalArgumentException(
-                    "item type: ${item.type} is not compatible with sharedPref methods"
-                )
-        }
-
-    /**
-     * After calling this method, the listener will be notified of any future updates to the
-     * `SharedPreferences` files associated with the provided list of items. The listener will need
-     * to filter update notifications so they don't activate for non-relevant updates.
-     */
-    override fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) {
-        items
-            .map { chooseSharedPreferences(it) }
-            .distinct()
-            .forEach { it.registerOnSharedPreferenceChangeListener(listener) }
-    }
-
-    /**
-     * Stops the listener from getting notified of any more updates to any of the
-     * `SharedPreferences` files associated with any of the provided list of [Item].
-     */
-    override fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) {
-        // If a listener is not registered to a SharedPreference, unregistering it does nothing
-        items
-            .map { chooseSharedPreferences(it) }
-            .distinct()
-            .forEach { it.unregisterOnSharedPreferenceChangeListener(listener) }
-    }
-
-    /**
-     * Checks if all the provided [Item] have values stored in their corresponding
-     * `SharedPreferences` files.
-     */
-    override fun has(vararg items: Item): Boolean {
-        items
-            .groupBy { chooseSharedPreferences(it) }
-            .forEach { (prefs, itemsSublist) ->
-                if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
-            }
-        return true
-    }
-
-    /**
-     * Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file.
-     */
-    override fun remove(vararg items: Item) = prepareToRemove(items).forEach { it.apply() }
-
-    /** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */
-    override fun removeSync(vararg items: Item) = prepareToRemove(items).forEach { it.commit() }
-
-    /**
-     * Removes the key value pairs stored in `SharedPreferences` for each corresponding Item. If the
-     * item is boot aware, this method removes the data from both the boot aware and encrypted
-     * files.
-     *
-     * @return a list of editors with all transactions added so that the caller can determine to use
-     *   .apply() or .commit()
-     */
-    private fun prepareToRemove(items: Array<out Item>): List<SharedPreferences.Editor> {
-        val itemsPerFile =
-            items
-                .filter { it.encryptionType != EncryptionType.DEVICE_PROTECTED }
-                .groupBy { it.encryptedPrefs }
-                .toMutableMap()
-
-        val bootAwareUpdates = items.filter { it.encryptionType == EncryptionType.DEVICE_PROTECTED }
-        if (bootAwareUpdates.isNotEmpty()) {
-            itemsPerFile[bootAwarePrefs] = bootAwareUpdates
-        }
-
-        return itemsPerFile.map { (prefs, items) ->
-            prefs.edit().also { editor ->
-                items.forEach { item -> editor.remove(item.sharedPrefKey) }
-            }
-        }
-    }
-
-    override fun close() {}
-}
-
 abstract class Item {
     abstract val sharedPrefKey: String
     abstract val isBackedUp: Boolean
@@ -466,3 +420,26 @@
     ENCRYPTED,
     DEVICE_PROTECTED,
 }
+
+/**
+ * LauncherPrefs which delegates all lookup to [prefs] but uses the real prefs for initial values
+ */
+class ProxyPrefs(context: Context, private val prefs: SharedPreferences) : LauncherPrefs(context) {
+
+    private val copiedPrefs = ConcurrentHashMap<SharedPreferences, Boolean>()
+
+    override fun getSharedPrefs(item: Item): SharedPreferences {
+        val originalPrefs = super.getSharedPrefs(item)
+        // Copy all existing values, when the pref is accessed for the first time
+        copiedPrefs.computeIfAbsent(originalPrefs) { op ->
+            val editor = prefs.edit()
+            op.all.forEach { (key, value) ->
+                if (value != null) {
+                    editor.putValue(backedUpItem(key, value), value)
+                }
+            }
+            editor.commit()
+        }
+        return prefs
+    }
+}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 6e2d357..03ecf14 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -24,35 +24,47 @@
 import android.content.ContentProvider;
 import android.content.ContentUris;
 import android.content.ContentValues;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.Process;
 import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.model.ModelDbController;
+import com.android.launcher3.util.LayoutImportExportHelper;
 import com.android.launcher3.widget.LauncherWidgetHolder;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
 import java.util.function.ToIntFunction;
 
 public class LauncherProvider extends ContentProvider {
     private static final String TAG = "LauncherProvider";
 
+    // Method API For Provider#call method.
+    private static final String METHOD_EXPORT_LAYOUT_XML = "EXPORT_LAYOUT_XML";
+    private static final String METHOD_IMPORT_LAYOUT_XML = "IMPORT_LAYOUT_XML";
+    private static final String KEY_RESULT = "KEY_RESULT";
+    private static final String KEY_LAYOUT = "KEY_LAYOUT";
+    private static final String SUCCESS = "success";
+    private static final String FAILURE = "failure";
+
     /**
      * $ adb shell dumpsys activity provider com.android.launcher3
      */
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
-        LauncherAppState.INSTANCE.executeIfCreated(appState -> {
-            if (appState.getModel().isModelLoaded()) {
-                appState.getModel().dumpState("", fd, writer, args);
-            }
-        });
+        LauncherModel model = LauncherAppState.INSTANCE.get(getContext()).getModel();
+        if (model.isModelLoaded()) {
+            model.dumpState("", fd, writer, args);
+        }
     }
 
     @Override
@@ -142,6 +154,43 @@
         return executeControllerTask(c -> c.update(args.table, values, args.where, args.args));
     }
 
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        Bundle b = new Bundle();
+
+        // The caller must have the read or write permission for this content provider to
+        // access the "call" method at all. We also enforce the appropriate per-method permissions.
+        switch(method) {
+            case METHOD_EXPORT_LAYOUT_XML:
+                if (getContext().checkCallingOrSelfPermission(getReadPermission())
+                        != PackageManager.PERMISSION_GRANTED) {
+                    throw new SecurityException("Caller doesn't have read permission");
+                }
+
+                CompletableFuture<String> resultFuture = LayoutImportExportHelper.INSTANCE
+                        .exportModelDbAsXmlFuture(getContext());
+                try {
+                    b.putString(KEY_LAYOUT, resultFuture.get());
+                    b.putString(KEY_RESULT, SUCCESS);
+                } catch (ExecutionException | InterruptedException e) {
+                    b.putString(KEY_RESULT, FAILURE);
+                }
+                return b;
+
+            case METHOD_IMPORT_LAYOUT_XML:
+                if (getContext().checkCallingOrSelfPermission(getWritePermission())
+                        != PackageManager.PERMISSION_GRANTED) {
+                    throw new SecurityException("Caller doesn't have write permission");
+                }
+
+                LayoutImportExportHelper.INSTANCE.importModelFromXml(getContext(), arg);
+                b.putString(KEY_RESULT, SUCCESS);
+                return b;
+            default:
+                return null;
+        }
+    }
+
     private int executeControllerTask(ToIntFunction<ModelDbController> task) {
         if (Binder.getCallingPid() == Process.myPid()) {
             throw new IllegalArgumentException("Same process should call model directly");
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index a5b95c7..b8a0abd 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -12,6 +12,7 @@
 import com.android.launcher3.graphics.SysUiScrim;
 import com.android.launcher3.statemanager.StatefulContainer;
 import com.android.launcher3.util.window.WindowManagerProxy;
+import com.android.launcher3.views.ActivityContext;
 
 import java.util.Collections;
 import java.util.List;
@@ -36,7 +37,7 @@
 
     public LauncherRootView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mStatefulContainer = StatefulContainer.fromContext(context);
+        mStatefulContainer = ActivityContext.lookupContext(context);
         mSysUiScrim = new SysUiScrim(this);
     }
 
@@ -54,7 +55,7 @@
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         mStatefulContainer.handleConfigurationChanged(
-                mStatefulContainer.getContext().getResources().getConfiguration());
+                mStatefulContainer.asContext().getResources().getConfiguration());
         return updateInsets(insets);
     }
 
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 1d2d161..5dc5d31 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -19,12 +19,15 @@
 import static android.util.Base64.NO_PADDING;
 import static android.util.Base64.NO_WRAP;
 
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
+
 import android.database.sqlite.SQLiteDatabase;
 import android.provider.BaseColumns;
 import android.util.Base64;
 
 import androidx.annotation.NonNull;
 
+import com.android.launcher3.icons.cache.CacheLookupFlag;
 import com.android.launcher3.model.data.ItemInfo;
 
 import java.util.LinkedHashMap;
@@ -352,6 +355,11 @@
         public static String getColumns(long profileId) {
             return String.join(", ", getColumnsToTypes(profileId).keySet());
         }
+
+        /**
+         * Lookup flag to be used for items which are visible on the home screen
+         */
+        public static final CacheLookupFlag DESKTOP_ICON_FLAG = DEFAULT_LOOKUP_FLAG;
     }
 
     /**
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 7d5e481..78ad04b 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -70,6 +70,7 @@
     public static final int WORKSPACE_PAGE_INDICATOR = 1 << 5;
     public static final int SPLIT_PLACHOLDER_VIEW = 1 << 6;
     public static final int FLOATING_SEARCH_BAR = 1 << 7;
+    public static final int ADD_DESK_BUTTON = 1 << 8;
 
     // Flag indicating workspace has multiple pages visible.
     public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0);
@@ -118,7 +119,7 @@
             LAUNCHER_STATE_HOME,
             FLAG_DISABLE_RESTORE | FLAG_WORKSPACE_ICONS_CAN_BE_DRAGGED | FLAG_HAS_SYS_UI_SCRIM) {
         @Override
-        public int getTransitionDuration(Context context, boolean isToState) {
+        public int getTransitionDuration(ActivityContext context, boolean isToState) {
             // Arbitrary duration, when going to NORMAL we use the state we're coming from instead.
             return 0;
         }
@@ -345,7 +346,7 @@
     public final  <DEVICE_PROFILE_CONTEXT extends Context & ActivityContext>
             float getDepth(DEVICE_PROFILE_CONTEXT context) {
         return getDepth(context,
-                BaseDraggingActivity.fromContext(context).getDeviceProfile().isMultiWindowMode);
+                ActivityContext.lookupContext(context).getDeviceProfile().isMultiWindowMode);
     }
 
     /**
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index 5d32525..32b47d0 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -11,14 +11,12 @@
 import com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID
 import com.android.launcher3.allapps.AllAppsStore
 import com.android.launcher3.config.FeatureFlags
-import com.android.launcher3.debug.TestEvent
 import com.android.launcher3.debug.TestEventEmitter
+import com.android.launcher3.debug.TestEventEmitter.TestEvent
 import com.android.launcher3.model.BgDataModel
 import com.android.launcher3.model.StringCache
 import com.android.launcher3.model.data.AppInfo
 import com.android.launcher3.model.data.ItemInfo
-import com.android.launcher3.model.data.LauncherAppWidgetInfo
-import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.popup.PopupContainerWithArrow
 import com.android.launcher3.util.ComponentKey
 import com.android.launcher3.util.IntArray as LIntArray
@@ -156,7 +154,7 @@
             /*pause=*/ false,
             deviceProfile.isTwoPanels,
         )
-        TestEventEmitter.INSTANCE.get(launcher).sendEvent(TestEvent.WORKSPACE_FINISH_LOADING)
+        TestEventEmitter.sendEvent(TestEvent.WORKSPACE_FINISH_LOADING)
     }
 
     /**
@@ -215,29 +213,13 @@
         launcher.appsView.appsStore.updateProgressBar(app)
     }
 
-    override fun bindWidgetsRestored(widgets: ArrayList<LauncherAppWidgetInfo?>?) {
-        launcher.workspace.widgetsRestored(widgets)
-    }
-
-    /**
-     * Some shortcuts were updated in the background. Implementation of the method from
-     * LauncherModel.Callbacks.
-     *
-     * @param updated list of shortcuts which have changed.
-     */
-    override fun bindWorkspaceItemsChanged(updated: List<WorkspaceItemInfo?>) {
-        if (updated.isNotEmpty()) {
-            launcher.workspace.updateWorkspaceItems(updated, launcher)
-            PopupContainerWithArrow.dismissInvalidPopup(launcher)
-        }
-    }
-
     /**
      * Update the state of a package, typically related to install state. Implementation of the
      * method from LauncherModel.Callbacks.
      */
-    override fun bindRestoreItemsChange(updates: HashSet<ItemInfo?>?) {
-        launcher.workspace.updateRestoreItems(updates, launcher)
+    override fun bindItemsUpdated(updates: Set<ItemInfo>) {
+        launcher.workspace.updateContainerItems(updates, launcher)
+        PopupContainerWithArrow.dismissInvalidPopup(launcher)
     }
 
     /**
@@ -252,11 +234,8 @@
         PopupContainerWithArrow.dismissInvalidPopup(launcher)
     }
 
-    override fun bindAllWidgets(
-        allWidgets: List<WidgetsListBaseEntry>,
-        defaultWidgets: List<WidgetsListBaseEntry>,
-    ) {
-        launcher.widgetPickerDataProvider.setWidgets(allWidgets, defaultWidgets)
+    override fun bindAllWidgets(allWidgets: List<WidgetsListBaseEntry>) {
+        launcher.widgetPickerDataProvider.setWidgets(allWidgets)
     }
 
     /** Returns the ids of the workspaces to bind. */
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 0ec3b79..d6abb56 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.app.animation.Interpolators.SCROLL;
+import static com.android.launcher3.RemoveAnimationSettingsTracker.WINDOW_ANIMATION_SCALE_URI;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
 import static com.android.launcher3.testing.shared.TestProtocol.SCROLL_FINISHED_MESSAGE;
@@ -33,7 +34,6 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.provider.Settings;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.InputDevice;
@@ -718,12 +718,14 @@
     }
 
     /**
-     * Queues the given callback to be run once {@code mPageScrolls} has been initialized.
+     * Run the given `callback` immediately once {@code mPageScrolls} has been initialized,
+     * otherwise queue the callback to `mOnPageScrollsInitializedCallbacks`.
      */
     public void runOnPageScrollsInitialized(Runnable callback) {
-        mOnPageScrollsInitializedCallbacks.add(callback);
         if (isPageScrollsInitialized()) {
-            onPageScrollsInitialized();
+            callback.run();
+        } else {
+            mOnPageScrollsInitializedCallbacks.add(callback);
         }
     }
 
@@ -1754,8 +1756,8 @@
         }
 
         if (FeatureFlags.IS_STUDIO_BUILD && !Utilities.isRunningInTestHarness()) {
-            duration *= Settings.Global.getFloat(getContext().getContentResolver(),
-                    Settings.Global.WINDOW_ANIMATION_SCALE, 1);
+            duration *= RemoveAnimationSettingsTracker.INSTANCE.get(getContext()).getValue(
+                    WINDOW_ANIMATION_SCALE_URI);
         }
 
         whichPage = validateNewPage(whichPage);
diff --git a/src/com/android/launcher3/PillColorPorovider.kt b/src/com/android/launcher3/PillColorPorovider.kt
index 347c5d6..e7f37bd 100644
--- a/src/com/android/launcher3/PillColorPorovider.kt
+++ b/src/com/android/launcher3/PillColorPorovider.kt
@@ -58,13 +58,8 @@
     }
 
     fun setup() {
-        appTitlePillPaint.color =
-            context.resources.getColor(
-                R.color.material_color_surface_container_lowest,
-                context.theme,
-            )
-        appTitleTextPaint.color =
-            context.resources.getColor(R.color.material_color_on_surface, context.theme)
+        appTitlePillPaint.color = context.getColor(R.color.materialColorSurfaceContainer)
+        appTitleTextPaint.color = context.getColor(R.color.materialColorOnSurface)
         isMatchaEnabledInternal = Settings.Secure.getInt(context.contentResolver, MATCHA_SETTING, 0)
         isMatchaEnabled = isMatchaEnabledInternal != 0
     }
diff --git a/src/com/android/launcher3/RemoveAnimationSettingsTracker.kt b/src/com/android/launcher3/RemoveAnimationSettingsTracker.kt
new file mode 100644
index 0000000..dbc04f1
--- /dev/null
+++ b/src/com/android/launcher3/RemoveAnimationSettingsTracker.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3
+
+import android.content.Context
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import android.provider.Settings.Global.ANIMATOR_DURATION_SCALE
+import android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE
+import android.provider.Settings.Global.WINDOW_ANIMATION_SCALE
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.DaggerSingletonObject
+import com.android.launcher3.util.DaggerSingletonTracker
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+
+/** Tracker Class for when user turns on/off remove animation setting. */
+@LauncherAppSingleton
+class RemoveAnimationSettingsTracker
+@Inject
+constructor(@ApplicationContext val context: Context, tracker: DaggerSingletonTracker) :
+    ContentObserver(Handler(Looper.getMainLooper())) {
+
+    private val contentResolver = context.contentResolver
+
+    /** Caches the last seen value for registered keys. */
+    private val cache: MutableMap<Uri, Float> = ConcurrentHashMap()
+
+    init {
+        UI_HELPER_EXECUTOR.execute {
+            contentResolver.registerContentObserver(WINDOW_ANIMATION_SCALE_URI, false, this)
+            contentResolver.registerContentObserver(TRANSITION_ANIMATION_SCALE_URI, false, this)
+            contentResolver.registerContentObserver(ANIMATOR_DURATION_SCALE_URI, false, this)
+        }
+
+        tracker.addCloseable {
+            UI_HELPER_EXECUTOR.execute { contentResolver.unregisterContentObserver(this) }
+        }
+    }
+
+    /**
+     * Returns the value for this classes key from the cache. If not in cache, will call
+     * [updateValue] to fetch.
+     */
+    fun getValue(uri: Uri): Float {
+        return getValue(uri, 1f)
+    }
+
+    /**
+     * Returns the value for this classes key from the cache. If not in cache, will call
+     * [getValueFromSettingsGlobal] to fetch.
+     */
+    private fun getValue(uri: Uri, defaultValue: Float): Float {
+        return cache.computeIfAbsent(uri) { getValueFromSettingsGlobal(uri, defaultValue) }
+    }
+
+    /** Returns if user has opted into having no animation on their device. */
+    fun isRemoveAnimationEnabled(): Boolean {
+        return getValue(WINDOW_ANIMATION_SCALE_URI) == 0f &&
+            getValue(TRANSITION_ANIMATION_SCALE_URI) == 0f &&
+            getValue(ANIMATOR_DURATION_SCALE_URI) == 0f
+    }
+
+    override fun onChange(selfChange: Boolean, uri: Uri?) {
+        if (uri == null) return
+        updateValue(uri)
+    }
+
+    private fun getValueFromSettingsGlobal(uri: Uri, defaultValue: Float = 1f): Float {
+        return Settings.Global.getFloat(contentResolver, uri.lastPathSegment, defaultValue)
+    }
+
+    private fun updateValue(uri: Uri, defaultValue: Float = 1f) {
+        val newValue = getValueFromSettingsGlobal(uri, defaultValue)
+        cache[uri] = newValue
+    }
+
+    companion object {
+        @JvmField
+        val INSTANCE =
+            DaggerSingletonObject(LauncherAppComponent::getRemoveAnimationSettingsTracker)
+        @JvmField
+        val WINDOW_ANIMATION_SCALE_URI: Uri = Settings.Global.getUriFor(WINDOW_ANIMATION_SCALE)
+        @JvmField
+        val TRANSITION_ANIMATION_SCALE_URI: Uri =
+            Settings.Global.getUriFor(TRANSITION_ANIMATION_SCALE)
+        @JvmField
+        val ANIMATOR_DURATION_SCALE_URI: Uri = Settings.Global.getUriFor(ANIMATOR_DURATION_SCALE)
+    }
+}
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index a8733f2..180ed87 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -330,8 +330,13 @@
         mTranslationProvider = provider;
     }
 
+    /** Returns the current {@link TranslationProvider translation provider}. */
+    public @Nullable TranslationProvider getTranslationProvider() {
+        return mTranslationProvider;
+    }
+
     /** Provides translation values to apply when laying out child views. */
-    interface TranslationProvider {
+    public interface TranslationProvider {
         float getTranslationX(int cellX);
     }
 }
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index ebcb5da..9a9bc1d 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -18,6 +18,7 @@
 
 import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
 import static com.android.launcher3.Flags.enableSmartspaceAsAWidget;
+import static com.android.launcher3.graphics.ShapeDelegate.DEFAULT_PATH_SIZE;
 import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
@@ -39,6 +40,7 @@
 import android.graphics.LightingColorFilter;
 import android.graphics.Matrix;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
@@ -74,9 +76,11 @@
 import androidx.core.graphics.ColorUtils;
 
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
+import com.android.launcher3.graphics.ThemeManager;
 import com.android.launcher3.graphics.TintedDrawableSpan;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.CacheableShortcutInfo;
+import com.android.launcher3.icons.IconThemeController;
 import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -88,7 +92,6 @@
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -626,13 +629,12 @@
     @WorkerThread
     public static <T extends Context & ActivityContext> Pair<AdaptiveIconDrawable, Drawable>
             getFullDrawable(T context, ItemInfo info, int width, int height, boolean useTheme) {
-        useTheme &= Themes.isThemedIconEnabled(context);
         LauncherAppState appState = LauncherAppState.getInstance(context);
         Drawable mainIcon = null;
 
         Drawable badge = null;
-        if ((info instanceof ItemInfoWithIcon iiwi) && !iiwi.usingLowResIcon()) {
-            badge = iiwi.bitmap.getBadgeDrawable(context, useTheme);
+        if ((info instanceof ItemInfoWithIcon iiwi) && !iiwi.getMatchingLookupFlag().useLowRes()) {
+            badge = iiwi.bitmap.getBadgeDrawable(context, useTheme, getIconShapeOrNull(context));
         }
 
         if (info instanceof PendingAddShortcutInfo) {
@@ -658,8 +660,12 @@
                         appState.getInvariantDeviceProfile().fillResIconDpi);
                 // Only fetch badge if the icon is on workspace
                 if (info.id != ItemInfo.NO_ID && badge == null) {
-                    badge = appState.getIconCache().getShortcutInfoBadge(si)
-                            .newIcon(context, FLAG_THEMED);
+                    badge = appState.getIconCache().getShortcutInfoBadge(si).newIcon(
+                            context,
+                            ThemeManager.INSTANCE.get(context).isIconThemeEnabled()
+                                    ? FLAG_THEMED : 0,
+                            getIconShapeOrNull(context)
+                    );
                 }
             }
         } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
@@ -681,7 +687,7 @@
         } else {
             // Wrap the main icon in AID
             try (LauncherIcons li = LauncherIcons.obtain(context)) {
-                result = li.wrapToAdaptiveIcon(mainIcon, null);
+                result = li.wrapToAdaptiveIcon(mainIcon);
             }
         }
         if (result == null) {
@@ -690,25 +696,26 @@
 
         // Inject theme icon drawable
         if (ATLEAST_T && useTheme) {
-            try (LauncherIcons li = LauncherIcons.obtain(context)) {
-                if (li.getThemeController() != null) {
-                    AdaptiveIconDrawable themed = li.getThemeController().createThemedAdaptiveIcon(
-                            context,
-                            result,
-                            info instanceof ItemInfoWithIcon iiwi ? iiwi.bitmap : null);
-                    if (themed != null) {
-                        result = themed;
-                    }
+            IconThemeController themeController =
+                    ThemeManager.INSTANCE.get(context).getThemeController();
+            if (themeController != null) {
+                AdaptiveIconDrawable themed = themeController.createThemedAdaptiveIcon(
+                        context,
+                        result,
+                        info instanceof ItemInfoWithIcon iiwi ? iiwi.bitmap : null);
+                if (themed != null) {
+                    result = themed;
                 }
             }
         }
 
         if (badge == null) {
             badge = BitmapInfo.LOW_RES_INFO.withFlags(
-                            UserCache.INSTANCE.get(context)
-                                    .getUserInfo(info.user)
-                                    .applyBitmapInfoFlags(FlagOp.NO_OP))
-                    .getBadgeDrawable(context, useTheme);
+                    UserCache.INSTANCE.get(context)
+                            .getUserInfo(info.user)
+                            .applyBitmapInfoFlags(FlagOp.NO_OP)
+                    )
+                    .getBadgeDrawable(context, useTheme, getIconShapeOrNull(context));
             if (badge == null) {
                 badge = new ColorDrawable(Color.TRANSPARENT);
             }
@@ -938,4 +945,18 @@
         }
         return null;
     }
+
+    /**
+     * Returns current icon shape to use for badges if flag is on, otherwise null.
+     */
+    @Nullable
+    public static Path getIconShapeOrNull(Context context) {
+        if (Flags.enableLauncherIconShapes()) {
+            return ThemeManager.INSTANCE.get(context)
+                    .getIconShape()
+                    .getPath(DEFAULT_PATH_SIZE);
+        } else {
+            return null;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 1804dd6..6ed183a 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -53,8 +53,7 @@
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.Message;
+import android.os.Bundle;
 import android.os.Parcelable;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -68,8 +67,10 @@
 import android.widget.FrameLayout;
 import android.widget.Toast;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.core.view.ViewCompat;
 
 import com.android.app.animation.Interpolators;
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
@@ -81,9 +82,8 @@
 import com.android.launcher3.celllayout.CellPosMapper;
 import com.android.launcher3.celllayout.CellPosMapper.CellPos;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.debug.TestEvent;
 import com.android.launcher3.debug.TestEventEmitter;
-import com.android.launcher3.dot.FolderDotInfo;
+import com.android.launcher3.debug.TestEventEmitter.TestEvent;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragOptions;
@@ -108,6 +108,7 @@
 import com.android.launcher3.pageindicators.PageIndicator;
 import com.android.launcher3.statemanager.StateManager;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
+import com.android.launcher3.statemanager.StateManager.StateListener;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.WorkspaceTouchListener;
 import com.android.launcher3.util.EdgeEffectCompat;
@@ -118,18 +119,13 @@
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.OverlayEdgeEffect;
-import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.WallpaperOffsetInterpolator;
 import com.android.launcher3.widget.LauncherAppWidgetHostView;
-import com.android.launcher3.widget.LauncherWidgetHolder;
-import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
 import com.android.launcher3.widget.NavigableAppWidgetHostView;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
-import com.android.launcher3.widget.WidgetManagerHelper;
 import com.android.launcher3.widget.util.WidgetSizes;
 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayCallbacks;
 import com.android.systemui.plugins.shared.LauncherOverlayManager.LauncherOverlayTouchProxy;
@@ -304,6 +300,19 @@
 
     private final MSDLPlayerWrapper mMSDLPlayerWrapper;
 
+    private final StateManager.StateListener<LauncherState> mAccessibilityDropListener =
+            new StateListener<>() {
+                @Override
+                public void onStateTransitionComplete(LauncherState finalState) {
+                    if (finalState == NORMAL) {
+                        performAccessibilityActionOnViewTree(Workspace.this);
+                    }
+                }
+            };
+
+    @Nullable
+    private DragController.DragListener mAccessibilityDragListener;
+
     /**
      * Used to inflate the Workspace from XML.
      *
@@ -512,6 +521,9 @@
             }
         }
 
+        if (mAccessibilityDragListener != null) {
+            mAccessibilityDragListener.onDragStart(dragObject, options);
+        }
         if (!mLauncher.isInState(EDIT_MODE)) {
             mLauncher.getStateManager().goToState(SPRING_LOADED);
         }
@@ -548,6 +560,9 @@
             }
         });
 
+        if (mAccessibilityDragListener != null) {
+            mAccessibilityDragListener.onDragEnd();
+        }
         mDragInfo = null;
         mDragSourceInternal = null;
     }
@@ -654,9 +669,6 @@
             bindAndInitFirstWorkspaceScreen();
         }
 
-        // Remove any deferred refresh callbacks
-        mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class);
-
         // Re-enable the layout transitions
         enableLayoutTransitions();
     }
@@ -880,6 +892,9 @@
                 mScreenOrder.removeValue(extraEmptyPageId);
             });
 
+            // Since we removed some screens, before moving to next page, update the state
+            // description with correct page numbers.
+            updateAccessibilityViewPageDescription();
             setCurrentPage(getNextPage());
 
             // Update the page indicator to reflect the removed page.
@@ -1105,6 +1120,9 @@
         if (pageShift >= 0) {
             setCurrentPage(currentPage - pageShift);
         }
+
+        // Now that we have removed some pages, ensure state description is up to date.
+        updateAccessibilityViewPageDescription();
     }
 
     /**
@@ -1453,11 +1471,13 @@
         super.onAttachedToWindow();
         mWallpaperOffset.setWindowToken(getWindowToken());
         computeScroll();
+        mLauncher.getStateManager().addStateListener(mAccessibilityDropListener);
     }
 
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mWallpaperOffset.setWindowToken(null);
+        mLauncher.getStateManager().removeStateListener(mAccessibilityDropListener);
     }
 
     @Override
@@ -1656,7 +1676,7 @@
         child.setVisibility(INVISIBLE);
 
         if (options.isAccessibleDrag) {
-            mDragController.addDragListener(
+            mAccessibilityDragListener =
                     new AccessibleDragListenerAdapter(this, WorkspaceAccessibilityHelper::new) {
                         @Override
                         protected void enableAccessibleDrag(boolean enable,
@@ -1669,7 +1689,7 @@
                                         IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
                             }
                         }
-                    });
+                    };
         }
 
         beginDragShared(child, this, options);
@@ -2232,12 +2252,21 @@
             }
             mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
                     .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
+
+            if (mAccessibilityDragListener != null) {
+                // This code needs to be called after StateManager.cancelAnimation. Before changing
+                // the order of operations in this method related to the StateListener below, please
+                // test that accessibility moves retain focus after accessibility dropping an item.
+                // Accessibility focus must be requested after launcher is back to a normal state
+                cell.setTag(R.id.perform_a11y_action_on_launcher_state_normal_tag,
+                        AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+            }
         }
 
         if (d.stateAnnouncer != null && !droppedOnOriginalCell) {
             d.stateAnnouncer.completeAction(R.string.item_moved);
         }
-        TestEventEmitter.INSTANCE.get(getContext()).sendEvent(TestEvent.WORKSPACE_ON_DROP);
+        TestEventEmitter.sendEvent(TestEvent.WORKSPACE_ON_DROP);
     }
 
     @Nullable
@@ -3135,7 +3164,7 @@
                         + "Workspace#onDropCompleted. Please file a bug. ");
             }
         }
-        View cell = getHomescreenIconByItemId(d.originalDragInfo.id);
+        View cell = getViewByItemId(d.originalDragInfo.id);
         if (d.cancelled && cell != null) {
             cell.setVisibility(VISIBLE);
         }
@@ -3284,29 +3313,9 @@
         return layouts;
     }
 
-    public View getHomescreenIconByItemId(final int id) {
-        return getFirstMatch((info, v) -> info != null && info.id == id);
-    }
-
     public LauncherAppWidgetHostView getWidgetForAppWidgetId(final int appWidgetId) {
-        return (LauncherAppWidgetHostView) getFirstMatch((info, v) ->
-                (info instanceof LauncherAppWidgetInfo) &&
-                        ((LauncherAppWidgetInfo) info).appWidgetId == appWidgetId);
-    }
-
-    public View getFirstMatch(final ItemOperator operator) {
-        final View[] value = new View[1];
-        mapOverItems(new ItemOperator() {
-            @Override
-            public boolean evaluate(ItemInfo info, View v) {
-                if (operator.evaluate(info, v)) {
-                    value[0] = v;
-                    return true;
-                }
-                return false;
-            }
-        });
-        return value[0];
+        return (LauncherAppWidgetHostView) mapOverItems((info, v) ->
+                (info instanceof LauncherAppWidgetInfo lawi) && lawi.appWidgetId == appWidgetId);
     }
 
     void clearDropTargets() {
@@ -3365,68 +3374,31 @@
     }
 
     @Override
-    public void mapOverItems(ItemOperator op) {
-        for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) {
-            if (mapOverCellLayout(layout, op) != null) {
-                return;
-            }
-        }
+    public View mapOverItems(@NonNull ItemOperator op) {
+        return mapOverCellLayouts(getWorkspaceAndHotseatCellLayouts(), op);
     }
 
     /**
-     * Perform {param operator} over all the items in a given {param layout}.
-     *
-     * @return The first item that satisfies the operator or null.
+     * Perform {param op} over all the items in the provided {param layouts} until a match is found
      */
-    public View mapOverCellLayout(CellLayout layout, ItemOperator operator) {
-        // TODO(b/128460496) Potential race condition where layout is not yet loaded
-        if (layout == null) {
-            return null;
-        }
-        ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets();
-        // map over all the shortcuts on the workspace
-        final int itemCount = container.getChildCount();
-        for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
-            View item = container.getChildAt(itemIdx);
-            if (operator.evaluate((ItemInfo) item.getTag(), item)) {
-                return item;
+    public static View mapOverCellLayouts(CellLayout[] layouts, ItemOperator op) {
+        for (CellLayout layout : layouts) {
+            // TODO(b/128460496) Potential race condition where layout is not yet loaded
+            if (layout == null) continue;
+
+            ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets();
+            // map over all the shortcuts on the layout
+            final int itemCount = container.getChildCount();
+            for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
+                View item = container.getChildAt(itemIdx);
+                if (op.evaluate((ItemInfo) item.getTag(), item)) {
+                    return item;
+                }
             }
         }
         return null;
     }
 
-    public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
-        final PackageUserKey packageUserKey = new PackageUserKey(null, null);
-        Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
-                || updatedDots.test(packageUserKey);
-
-        ItemOperator op = (info, v) -> {
-            if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
-                if (matcher.test(info)) {
-                    ((BubbleTextView) v).applyDotState(info, true /* animate */);
-                }
-            } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
-                FolderInfo fi = (FolderInfo) info;
-                if (fi.anyMatch(matcher)) {
-                    FolderDotInfo folderDotInfo = new FolderDotInfo();
-                    for (ItemInfo si : fi.getContents()) {
-                        folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si));
-                    }
-                    ((FolderIcon) v).setDotInfo(folderDotInfo);
-                }
-            }
-
-            // process all the shortcuts
-            return false;
-        };
-
-        mapOverItems(op);
-        Folder folder = Folder.getOpen(mLauncher);
-        if (folder != null) {
-            folder.iterateOverItems(op);
-        }
-    }
-
     /**
      * Remove workspace icons & widget information related to items in matcher.
      *
@@ -3438,43 +3410,6 @@
         removeItemsByMatcher(matcher);
     }
 
-    public void widgetsRestored(final ArrayList<LauncherAppWidgetInfo> changedInfo) {
-        if (!changedInfo.isEmpty()) {
-            DeferredWidgetRefresh widgetRefresh = new DeferredWidgetRefresh(changedInfo,
-                    mLauncher.getAppWidgetHolder());
-
-            LauncherAppWidgetInfo item = changedInfo.get(0);
-            final AppWidgetProviderInfo widgetInfo;
-            WidgetManagerHelper widgetHelper = new WidgetManagerHelper(getContext());
-            if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
-                widgetInfo = widgetHelper.findProvider(item.providerName, item.user);
-            } else {
-                widgetInfo = widgetHelper.getLauncherAppWidgetInfo(item.appWidgetId,
-                        item.getTargetComponent());
-            }
-
-            if (widgetInfo != null) {
-                // Re-inflate the widgets which have changed status
-                widgetRefresh.run();
-            } else {
-                // widgetRefresh will automatically run when the packages are updated.
-                // For now just update the progress bars
-                mapOverItems(new ItemOperator() {
-                    @Override
-                    public boolean evaluate(ItemInfo info, View view) {
-                        if (view instanceof PendingAppWidgetHostView
-                                && changedInfo.contains(info)) {
-                            ((LauncherAppWidgetInfo) info).installProgress = 100;
-                            ((PendingAppWidgetHostView) view).applyState();
-                        }
-                        // process all the shortcuts
-                        return false;
-                    }
-                });
-            }
-        }
-    }
-
     public boolean isOverlayShown() {
         return mOverlayShown;
     }
@@ -3519,12 +3454,30 @@
 
     @Override
     protected boolean canAnnouncePageDescription() {
-        // Disable announcements while overscrolling potentially to overlay screen because if we end
-        // up on the overlay screen, it will take care of announcing itself.
         return Float.compare(mOverlayProgress, 0f) == 0;
     }
 
     @Override
+    protected void announcePageForAccessibility() {
+        // Talkback focuses on AccessibilityActionView by default, so we need to modify the state
+        // description there in order for the change in page scroll to be announced.
+        updateAccessibilityViewPageDescription();
+    }
+
+    /**
+     * Updates the state description that is set on the accessibility actions view for the
+     * workspace.
+     * <p>The updated value is called out when talkback focuses on the view and is not disruptive.
+     * </p>
+     */
+    protected void updateAccessibilityViewPageDescription() {
+        // Set the state description on accessibility action view so that when it is focused,
+        // talkback describes the correct state of home screen pages.
+        ViewCompat.setStateDescription(mLauncher.getAccessibilityActionView(),
+                getCurrentPageDescription());
+    }
+
+    @Override
     protected String getCurrentPageDescription() {
         int pageIndex = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
         return getPageDescription(pageIndex);
@@ -3539,7 +3492,7 @@
         int nScreens = getChildCount();
         int extraScreenId = mScreenOrder.indexOf(EXTRA_EMPTY_SCREEN_ID);
         if (extraScreenId >= 0 && nScreens > 1) {
-            if (page == extraScreenId) {
+            if (page == extraScreenId || (isTwoPanelEnabled() && page == extraScreenId + 1)) {
                 return getContext().getString(R.string.workspace_new_page);
             }
             nScreens--;
@@ -3551,6 +3504,11 @@
         int panelCount = getPanelCount();
         int currentPage = (page / panelCount) + 1;
         int totalPages = nScreens / panelCount + nScreens % panelCount;
+
+        // When dragging, a blank screen is added. This increases the total page count, but we still
+        // want to describe the original page count where icons are currently pinned
+        if (extraScreenId > 0) totalPages--;
+
         return getContext().getString(R.string.workspace_scroll_format, currentPage, totalPages);
     }
 
@@ -3570,62 +3528,6 @@
         return mLauncher.getCellPosMapper();
     }
 
-    /**
-     * Used as a workaround to ensure that the AppWidgetService receives the
-     * PACKAGE_ADDED broadcast before updating widgets.
-     */
-    private class DeferredWidgetRefresh implements Runnable, ProviderChangedListener {
-        private final ArrayList<LauncherAppWidgetInfo> mInfos;
-        private final LauncherWidgetHolder mWidgetHolder;
-        private final Handler mHandler;
-
-        private boolean mRefreshPending;
-
-        DeferredWidgetRefresh(ArrayList<LauncherAppWidgetInfo> infos,
-                              LauncherWidgetHolder holder) {
-            mInfos = infos;
-            mWidgetHolder = holder;
-            mHandler = mLauncher.mHandler;
-            mRefreshPending = true;
-
-            mWidgetHolder.addProviderChangeListener(this);
-            // Force refresh after 10 seconds, if we don't get the provider changed event.
-            // This could happen when the provider is no longer available in the app.
-            Message msg = Message.obtain(mHandler, this);
-            msg.obj = DeferredWidgetRefresh.class;
-            mHandler.sendMessageDelayed(msg, 10000);
-        }
-
-        @Override
-        public void run() {
-            mWidgetHolder.removeProviderChangeListener(this);
-            mHandler.removeCallbacks(this);
-
-            if (!mRefreshPending) {
-                return;
-            }
-
-            mRefreshPending = false;
-
-            ArrayList<PendingAppWidgetHostView> views = new ArrayList<>(mInfos.size());
-            mapOverItems((info, view) -> {
-                if (view instanceof PendingAppWidgetHostView && mInfos.contains(info)) {
-                    views.add((PendingAppWidgetHostView) view);
-                }
-                // process all children
-                return false;
-            });
-            for (PendingAppWidgetHostView view : views) {
-                view.reInflate();
-            }
-        }
-
-        @Override
-        public void notifyWidgetProvidersChanged() {
-            run();
-        }
-    }
-
     private class StateTransitionListener extends AnimatorListenerAdapter
             implements AnimatorUpdateListener {
 
@@ -3644,4 +3546,22 @@
             onEndStateTransition();
         }
     }
+
+    /**
+     * Recursively check view tag {@link R.id.perform_a11y_action_on_launcher_state_normal_tag} and
+     * call {@link View#performAccessibilityAction(int, Bundle)} on view tree. The tag is cleared
+     * after this call.
+     */
+    private static void performAccessibilityActionOnViewTree(View view) {
+        Object tag = view.getTag(R.id.perform_a11y_action_on_launcher_state_normal_tag);
+        if (tag instanceof Integer) {
+            view.performAccessibilityAction((int) tag, null);
+            view.setTag(R.id.perform_a11y_action_on_launcher_state_normal_tag, null);
+        }
+        if (view instanceof ViewGroup viewgroup) {
+            for (int i = 0; i < viewgroup.getChildCount(); i++) {
+                performAccessibilityActionOnViewTree(viewgroup.getChildAt(i));
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 68a6e62..78b53a9 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -24,6 +24,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.ButtonDropTarget;
 import com.android.launcher3.CellLayout;
@@ -48,7 +49,6 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.ArrowPopup;
 import com.android.launcher3.popup.PopupContainerWithArrow;
-import com.android.launcher3.shortcuts.DeepShortcutTextView;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.touch.ItemLongClickListener;
 import com.android.launcher3.util.IntArray;
@@ -199,6 +199,8 @@
                 host.requestFocus();
                 host.sendAccessibilityEvent(TYPE_VIEW_FOCUSED);
                 host.performAccessibilityAction(ACTION_ACCESSIBILITY_FOCUS, null);
+                AbstractFloatingView.closeOpenViews(mContext, /* animate= */ false,
+                        AbstractFloatingView.TYPE_WIDGET_RESIZE_FRAME);
             });
             return true;
         } else if (action == DEEP_SHORTCUTS) {
@@ -305,7 +307,6 @@
                 info.spanX, info.spanY);
         host.requestLayout();
         mContext.getModelWriter().updateItemInDatabase(info);
-        announceConfirmation(mContext.getString(R.string.widget_resized, info.spanX, info.spanY));
         return true;
     }
 
@@ -405,6 +406,11 @@
      */
     public boolean addToWorkspace(ItemInfo item, boolean accessibility,
             @Nullable Consumer<Boolean> finishCallback) {
+        // Dismiss widget resize frame if it is showing. The frame marks its cells as unoccupied
+        // while it is showing, so findSpaceOnWorkspace may try to use those cells.
+        AbstractFloatingView.closeOpenViews(mContext, /* animate= */ false,
+                AbstractFloatingView.TYPE_WIDGET_RESIZE_FRAME);
+
         final int[] coordinates = new int[2];
         final int screenId = findSpaceOnWorkspace(item, coordinates);
         if (screenId == -1) {
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
index d115f9f..c91e783 100644
--- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
@@ -24,7 +24,6 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.R;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.shortcuts.DeepShortcutView;
@@ -67,7 +66,6 @@
                         screenId, coordinates[0], coordinates[1]);
                 mContext.bindItems(Collections.singletonList(info), true);
                 AbstractFloatingView.closeAllOpenViews(mContext);
-                announceConfirmation(R.string.item_added_to_workspace);
             }));
             return true;
         }
diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
index 84d6a6f..65b0662 100644
--- a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -114,9 +114,7 @@
         LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo();
 
         View child = mView.getChildAt(x, y);
-        if (child == null || child == dragInfo.item) {
-            return mContext.getString(R.string.item_moved);
-        } else {
+        if (child != null && child != dragInfo.item) {
             ItemInfo info = (ItemInfo) child.getTag();
             if (Folder.willAccept(info)) {
                 return mContext.getString(R.string.folder_created);
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index b0001af..fafa60b 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -116,6 +116,7 @@
         ScrimView.ScrimDrawingController {
 
 
+    private static final String TAG = "ActivityAllAppsContainerView";
     public static final float PULL_MULTIPLIER = .02f;
     public static final float FLING_VELOCITY_MULTIPLIER = 1200f;
     protected static final String BUNDLE_KEY_CURRENT_PAGE = "launcher.allapps.current_page";
@@ -273,12 +274,7 @@
         mFastScroller = findViewById(R.id.fast_scroller);
         mFastScroller.setPopupView(findViewById(R.id.fast_scroller_popup));
         mFastScrollLetterLayout = findViewById(R.id.scroll_letter_layout);
-        if (Flags.letterFastScroller()) {
-            // Set clip children to false otherwise the scroller letters will be clipped.
-            setClipChildren(false);
-        } else {
-            setClipChildren(true);
-        }
+        setClipChildren(false);
 
         mSearchContainer = inflateSearchBar();
         if (!isSearchBarFloating()) {
@@ -605,6 +601,7 @@
             mViewPager.getPageIndicator().setActiveMarker(AdapterHolder.MAIN);
             findViewById(R.id.tab_personal)
                     .setOnClickListener((View view) -> {
+                        Log.d(TAG, "rebindAdapters: " + "Clicked personal tab.");
                         if (mViewPager.snapToPage(AdapterHolder.MAIN)) {
                             mActivityContext.getStatsLogManager().logger()
                                     .log(LAUNCHER_ALLAPPS_TAP_ON_PERSONAL_TAB);
@@ -612,6 +609,7 @@
                     });
             findViewById(R.id.tab_work)
                     .setOnClickListener((View view) -> {
+                        Log.d(TAG, "rebindAdapters: " + "Clicked work tab.");
                         if (mViewPager.snapToPage(AdapterHolder.WORK)) {
                             mActivityContext.getStatsLogManager().logger()
                                     .log(LAUNCHER_ALLAPPS_TAP_ON_WORK_TAB);
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index 29b9e77..d5a4022 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -17,7 +17,6 @@
 
 import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
 import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
-import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK;
 
 import android.content.Context;
 import android.os.UserHandle;
@@ -229,11 +228,7 @@
     public void updateProgressBar(AppInfo app) {
         updateAllIcons((child) -> {
             if (child.getTag() == app) {
-                if ((app.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) == 0) {
-                    child.applyFromApplicationInfo(app);
-                } else {
-                    child.applyProgressLevel();
-                }
+                child.applyFromApplicationInfo(app);
             }
         });
     }
@@ -261,12 +256,14 @@
         writer.println(prefix + "\tAllAppsStore Apps[] size: " + mApps.length);
         for (int i = 0; i < mApps.length; i++) {
             writer.println(String.format(Locale.getDefault(),
-                    "%s\tPackage index, name, class, and description: %d/%s:%s, %s",
+                    "%s\tPackage index, name, class, description, bitmap flag: %d/%s:%s, %s, %s+%s",
                     prefix,
                     i,
                     mApps[i].componentName.getPackageName(),
                     mApps[i].componentName.getClassName(),
-                    mApps[i].contentDescription));
+                    mApps[i].contentDescription,
+                    Integer.toBinaryString(mApps[i].bitmap.flags),
+                    Integer.toBinaryString(mApps[i].bitmap.creationFlags)));
         }
     }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 8554de5..4cc31d2 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -34,6 +34,7 @@
 import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.util.FloatProperty;
 import android.view.HapticFeedbackConstants;
@@ -365,6 +366,12 @@
         Interpolator verticalProgressInterpolator = config.getInterpolator(ANIM_VERTICAL_PROGRESS,
                 config.isUserControlled() ? LINEAR : DECELERATE_1_7);
         Animator anim = createSpringAnimation(mProgress, targetProgress);
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                setProgress(targetProgress);
+            }
+        });
         anim.setInterpolator(verticalProgressInterpolator);
         builder.add(anim);
 
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 709b52a..5f632fe 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -15,10 +15,14 @@
  */
 package com.android.launcher3.allapps;
 
+import static android.multiuser.Flags.enableMovingContentIntoPrivateSpace;
+
 import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_BOTTOM_VIEW_TO_SCROLL_TO;
+import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_MASK_PRIVATE_SPACE_HEADER;
 import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_LEFT;
 import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_RIGHT;
 import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING;
+import static com.android.launcher3.icons.BitmapInfo.FLAG_NO_BADGE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_PREINSTALLED_APPS_COUNT;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_PRIVATE_SPACE_USER_INSTALLED_APPS_COUNT;
 
@@ -60,6 +64,7 @@
         AllAppsStore.OnUpdateListener {
 
     public static final String TAG = "AlphabeticalAppsList";
+    private static final String PRIVATE_SPACE_PACKAGE = "com.android.privatespace";
 
     private final WorkProfileManager mWorkProviderManager;
 
@@ -382,7 +387,7 @@
 
     private int addPrivateSpaceApps(int position) {
         // Add Install Apps Button first.
-        if (Flags.privateSpaceAppInstallerButton()) {
+        if (Flags.privateSpaceAppInstallerButton() && !enableMovingContentIntoPrivateSpace()) {
             mPrivateProviderManager.addPrivateSpaceInstallAppButton(mAdapterItems);
             position++;
         }
@@ -419,6 +424,28 @@
         // Add system apps.
         position = addAppsWithSections(split.get(false), position);
 
+        if (enableMovingContentIntoPrivateSpace()) {
+            // Look for the private space app via package and move it after header.
+            int headerIndex = -1;
+            int privateSpaceAppIndex = -1;
+            for (int i = 0; i < mAdapterItems.size(); i++) {
+                BaseAllAppsAdapter.AdapterItem currentItem = mAdapterItems.get(i);
+                if (currentItem.viewType == VIEW_TYPE_MASK_PRIVATE_SPACE_HEADER) {
+                    headerIndex = i;
+                }
+                if (currentItem.itemInfo != null && Objects.equals(
+                        currentItem.itemInfo.getTargetPackage(), PRIVATE_SPACE_PACKAGE)) {
+                    currentItem.itemInfo.bitmap.creationFlags |= FLAG_NO_BADGE;
+                    privateSpaceAppIndex = i;
+                }
+            }
+            if (headerIndex != -1 && privateSpaceAppIndex != -1) {
+                BaseAllAppsAdapter.AdapterItem movedItem =
+                        mAdapterItems.remove(privateSpaceAppIndex);
+                // Move the icon after the header.
+                mAdapterItems.add(headerIndex + 1, movedItem);
+            }
+        }
         return position;
     }
 
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
index 60bf3ea..e8b7247 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsAdapter.java
@@ -40,8 +40,6 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.Flags;
-import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.allapps.search.SearchAdapterProvider;
 import com.android.launcher3.model.data.AppInfo;
@@ -219,9 +217,7 @@
     public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         switch (viewType) {
             case VIEW_TYPE_ICON:
-                int layout = (Flags.enableTwolineToggle()
-                        && LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(
-                                mActivityContext.getApplicationContext()))
+                int layout = mActivityContext.getDeviceProfile().inv.enableTwoLinesInAllApps
                         ? R.layout.all_apps_icon_twoline : R.layout.all_apps_icon;
                 BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
                         layout, parent, false);
diff --git a/src/com/android/launcher3/allapps/WorkPausedCard.java b/src/com/android/launcher3/allapps/WorkPausedCard.java
index a14ac98..864ede8 100644
--- a/src/com/android/launcher3/allapps/WorkPausedCard.java
+++ b/src/com/android/launcher3/allapps/WorkPausedCard.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
 import android.widget.Button;
 import android.widget.LinearLayout;
@@ -34,6 +35,7 @@
  */
 public class WorkPausedCard extends LinearLayout implements View.OnClickListener {
 
+    private static final String TAG = "WorkPausedCard";
     private final ActivityContext mActivityContext;
     private Button mBtn;
 
@@ -79,6 +81,7 @@
 
     @Override
     public void onClick(View view) {
+        Log.d(TAG, "WorkPausedCard clicked.");
         mActivityContext.getAppsView().getWorkManager().setWorkProfileEnabled(true);
         mActivityContext.getStatsLogManager().logger().log(LAUNCHER_TURN_ON_WORK_APPS_TAP);
     }
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index 6d7d193..920efa4 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -200,6 +200,7 @@
 
     private void onWorkFabClicked(View view) {
         if (getCurrentState() == STATE_ENABLED && mWorkUtilityView.isEnabled()) {
+            Log.d(TAG, "Work FAB clicked.");
             logEvents(LAUNCHER_TURN_OFF_WORK_APPS_TAP);
             setWorkProfileEnabled(false);
         }
diff --git a/src/com/android/launcher3/allapps/WorkUtilityView.java b/src/com/android/launcher3/allapps/WorkUtilityView.java
index b263639..20ceb15 100644
--- a/src/com/android/launcher3/allapps/WorkUtilityView.java
+++ b/src/com/android/launcher3/allapps/WorkUtilityView.java
@@ -15,6 +15,11 @@
  */
 package com.android.launcher3.allapps;
 
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_BEGIN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_END;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_BEGIN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_END;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -25,6 +30,7 @@
 import android.graphics.Rect;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowInsets;
@@ -46,6 +52,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedPropertySetter;
 import com.android.launcher3.anim.KeyboardInsetAnimationCallback;
+import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.model.StringCache;
 import com.android.launcher3.views.ActivityContext;
 
@@ -57,6 +64,7 @@
 public class WorkUtilityView extends LinearLayout implements Insettable,
         KeyboardInsetAnimationCallback.KeyboardInsetListener {
 
+    private static final String TAG = "WorkUtilityView";
     private static final int TEXT_EXPAND_OPACITY_DURATION = 300;
     private static final int TEXT_COLLAPSE_OPACITY_DURATION = 50;
     private static final int EXPAND_COLLAPSE_DURATION = 300;
@@ -88,6 +96,8 @@
     private TextView mPauseText;
     private ImageView mWorkIcon;
     private ImageButton mSchedulerButton;
+    private final StatsLogManager mStatsLogManager;
+    private LinearLayout mWorkUtilityView;
 
     public WorkUtilityView(@NonNull Context context) {
         this(context, null, 0);
@@ -111,6 +121,7 @@
                 R.dimen.work_fab_icon_start_margin_expanded);
         mWorkSchedulerIntentAction = mContext.getResources().getString(
                 R.string.work_profile_scheduler_intent);
+        mStatsLogManager = mActivityContext.getStatsLogManager();
     }
 
     @Override
@@ -121,6 +132,7 @@
         mWorkIcon = findViewById(R.id.work_icon);
         mWorkFAB = findViewById(R.id.work_mode_toggle);
         mSchedulerButton = findViewById(R.id.work_scheduler);
+        mWorkUtilityView = findViewById(R.id.work_utility_view);
         setSelected(true);
         KeyboardInsetAnimationCallback keyboardInsetAnimationCallback =
                 new KeyboardInsetAnimationCallback(this);
@@ -133,8 +145,11 @@
         mSchedulerButton.setOnClickListener(null);
         if (shouldUseScheduler()) {
             mSchedulerButton.setVisibility(VISIBLE);
-            mSchedulerButton.setOnClickListener(view ->
-                    mContext.startActivity(new Intent(mWorkSchedulerIntentAction)));
+            mSchedulerButton.setOnClickListener(view -> {
+                Log.d(TAG, "WorkScheduler button clicked.");
+                mActivityContext.startActivitySafely(view,
+                        new Intent(mWorkSchedulerIntentAction), null /* itemInfo */);
+            });
         }
     }
 
@@ -318,6 +333,23 @@
             animatorList.add(animateSchedulerScale(isExpanding));
             animatorList.add(animateSchedulerAlpha(isExpanding));
         }
+        animatorSet.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mStatsLogManager.logger().sendToInteractionJankMonitor(
+                        isExpanding ? LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_BEGIN
+                                : LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_BEGIN,
+                        mWorkUtilityView);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mStatsLogManager.logger().sendToInteractionJankMonitor(
+                        isExpanding ? LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_END
+                                : LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_END,
+                        mWorkUtilityView);
+            }
+        });
         animatorSet.playTogether(animatorList);
         animatorSet.start();
     }
diff --git a/src/com/android/launcher3/anim/AnimatedPropertySetter.java b/src/com/android/launcher3/anim/AnimatedPropertySetter.java
index 82e645a..0f1b8ad 100644
--- a/src/com/android/launcher3/anim/AnimatedPropertySetter.java
+++ b/src/com/android/launcher3/anim/AnimatedPropertySetter.java
@@ -20,6 +20,7 @@
 
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
@@ -121,19 +122,31 @@
      * Adds a listener to be run on every frame of the animation
      */
     public void addOnFrameListener(ValueAnimator.AnimatorUpdateListener listener) {
-        if (mProgressAnimator == null) {
-            mProgressAnimator = ValueAnimator.ofFloat(0, 1);
-        }
-
-        mProgressAnimator.addUpdateListener(listener);
+        getProgressAnimator().addUpdateListener(listener);
     }
 
     @Override
     public void addEndListener(Consumer<Boolean> listener) {
+        getProgressAnimator().addListener(AnimatorListeners.forEndCallback(listener));
+    }
+
+    /**
+     * Add a callback to run on progress start.
+     */
+    public void addStartListener(Runnable listener) {
+        getProgressAnimator().addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                listener.run();
+            }
+        });
+    }
+
+    private ValueAnimator getProgressAnimator() {
         if (mProgressAnimator == null) {
             mProgressAnimator = ValueAnimator.ofFloat(0, 1);
         }
-        mProgressAnimator.addListener(AnimatorListeners.forEndCallback(listener));
+        return mProgressAnimator;
     }
 
     /**
diff --git a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
index 034b686..81a92f6 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
+++ b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
@@ -27,7 +27,6 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener
 import com.android.launcher3.icons.BitmapInfo
 import com.android.launcher3.model.data.AppPairInfo
-import com.android.launcher3.util.Themes
 import com.android.launcher3.views.ActivityContext
 
 /**
@@ -46,12 +45,11 @@
         @JvmStatic
         fun composeDrawable(
             appPairInfo: AppPairInfo,
-            p: AppPairIconDrawingParams
+            p: AppPairIconDrawingParams,
         ): AppPairIconDrawable {
-            // Generate new icons, using themed flag if needed.
-            val flags = if (Themes.isThemedIconEnabled(p.context)) BitmapInfo.FLAG_THEMED else 0
-            val appIcon1 = appPairInfo.getFirstApp().newIcon(p.context, flags)
-            val appIcon2 = appPairInfo.getSecondApp().newIcon(p.context, flags)
+            // Generate new icons, using themed flag since the icon is drawn on homescreen
+            val appIcon1 = appPairInfo.getFirstApp().newIcon(p.context, BitmapInfo.FLAG_THEMED)
+            val appIcon2 = appPairInfo.getSecondApp().newIcon(p.context, BitmapInfo.FLAG_THEMED)
             appIcon1.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt())
             appIcon2.setBounds(0, 0, p.memberIconSize.toInt(), p.memberIconSize.toInt())
 
@@ -125,7 +123,7 @@
             ((parentIcon.width - drawParams.backgroundSize) / 2).toInt(),
             // y-coordinate in parent's coordinate system
             (parentIcon.paddingTop + drawParams.standardIconPadding + drawParams.outerPadding)
-                .toInt()
+                .toInt(),
         )
     }
 
@@ -140,17 +138,13 @@
         drawable.draw(canvas)
     }
 
-    /**
-     * Sets the scale of the icon background while hovered.
-     */
+    /** Sets the scale of the icon background while hovered. */
     fun setHoverScale(scale: Float) {
         drawParams.hoverScale = scale
         redraw()
     }
 
-    /**
-     * Gets the scale of the icon background while hovered.
-     */
+    /** Gets the scale of the icon background while hovered. */
     fun getHoverScale(): Float {
         return drawParams.hoverScale
     }
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 9e38824..44dcc06 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -144,15 +144,6 @@
         return ENABLE_TASKBAR_PINNING.get() || Flags.enableTaskbarPinning();
     }
 
-    // Aconfig migration complete for ENABLE_APP_PAIRS.
-    public static final BooleanFlag ENABLE_APP_PAIRS = getDebugFlag(274189428,
-            "ENABLE_APP_PAIRS", DISABLED,
-            "Enables the ability to create and save app pairs on the Home screen for easy"
-                    + " split screen launching.");
-    public static boolean enableAppPairs() {
-        return ENABLE_APP_PAIRS.get() || com.android.wm.shell.Flags.enableAppPairs();
-    }
-
     // TODO(Block 20): Clean up flags
     // Aconfig migration complete for ENABLE_HOME_TRANSITION_LISTENER.
     public static final BooleanFlag ENABLE_HOME_TRANSITION_LISTENER = getDebugFlag(306053414,
@@ -201,15 +192,6 @@
             "USE_LOCAL_ICON_OVERRIDES", ENABLED,
             "Use inbuilt monochrome icons if app doesn't provide one");
 
-    // Aconfig migration complete for ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.
-    public static final BooleanFlag ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE = getDebugFlag(
-            270393453, "ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE", DISABLED,
-            "Enable initiating split screen from workspace to workspace.");
-    public static boolean enableSplitContextually() {
-        return ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get() ||
-                com.android.wm.shell.Flags.enableSplitContextual();
-    }
-
     // TODO(Block 29): Clean up flags
     // Aconfig migration complete for ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT.
     public static final BooleanFlag ENABLE_ALL_APPS_BUTTON_IN_HOTSEAT = getDebugFlag(270393897,
diff --git a/src/com/android/launcher3/contextualeducation/ContextualEduStatsManager.java b/src/com/android/launcher3/contextualeducation/ContextualEduStatsManager.java
deleted file mode 100644
index 5664174..0000000
--- a/src/com/android/launcher3/contextualeducation/ContextualEduStatsManager.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 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.contextualeducation;
-
-import com.android.launcher3.dagger.LauncherAppSingleton;
-import com.android.launcher3.dagger.LauncherBaseAppComponent;
-import com.android.launcher3.util.DaggerSingletonObject;
-import com.android.systemui.contextualeducation.GestureType;
-
-import javax.inject.Inject;
-
-/**
- * A class to update contextual education data. It is a no-op implementation and could be
- * overridden through dagger modules to provide a real implementation.
- */
-@LauncherAppSingleton
-public class ContextualEduStatsManager {
-    public static final DaggerSingletonObject<ContextualEduStatsManager> INSTANCE =
-            new DaggerSingletonObject<>(LauncherBaseAppComponent::getContextualEduStatsManager);
-
-    @Inject
-    public ContextualEduStatsManager() { }
-
-
-    /**
-     * Updates contextual education stats when a gesture is triggered
-     * @param isTrackpadGesture indicates if the gesture is triggered by trackpad
-     * @param gestureType type of gesture triggered
-     */
-    public void updateEduStats(boolean isTrackpadGesture, GestureType gestureType) {
-    }
-}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewData.kt b/src/com/android/launcher3/dagger/LauncherAppModule.java
similarity index 67%
rename from quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewData.kt
rename to src/com/android/launcher3/dagger/LauncherAppModule.java
index 3502029..c58a414 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewData.kt
+++ b/src/com/android/launcher3/dagger/LauncherAppModule.java
@@ -14,11 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.quickstep.task.thumbnail
+package com.android.launcher3.dagger;
 
-import kotlinx.coroutines.flow.MutableStateFlow
+import dagger.Module;
 
-class TaskThumbnailViewData {
-    val width = MutableStateFlow(0)
-    val height = MutableStateFlow(0)
+@Module(includes = {
+        WindowManagerProxyModule.class,
+        ApiWrapperModule.class,
+        PluginManagerWrapperModule.class,
+        StaticObjectModule.class,
+        AppModule.class
+})
+public class LauncherAppModule {
 }
diff --git a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
index 1e3df1e..06643d3 100644
--- a/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
+++ b/src/com/android/launcher3/dagger/LauncherBaseAppComponent.java
@@ -18,24 +18,39 @@
 
 import android.content.Context;
 
-import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
-import com.android.launcher3.graphics.IconShape;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.RemoveAnimationSettingsTracker;
+import com.android.launcher3.graphics.GridCustomizationsProxy;
+import com.android.launcher3.graphics.ThemeManager;
+import com.android.launcher3.icons.LauncherIcons.IconPool;
 import com.android.launcher3.model.ItemInstallQueue;
+import com.android.launcher3.model.WidgetsFilterDataProvider;
 import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.DaggerSingletonTracker;
+import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DynamicResource;
+import com.android.launcher3.util.LockedUserState;
 import com.android.launcher3.util.MSDLPlayerWrapper;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.PluginManagerWrapper;
 import com.android.launcher3.util.ScreenOnTracker;
 import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.util.VibratorWrapper;
+import com.android.launcher3.util.WallpaperColorHints;
 import com.android.launcher3.util.window.RefreshRateTracker;
+import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import dagger.BindsInstance;
 
+import javax.inject.Named;
+
 /**
  * Launcher base component for Dagger injection.
  *
@@ -47,10 +62,8 @@
 public interface LauncherBaseAppComponent {
     DaggerSingletonTracker getDaggerSingletonTracker();
     ApiWrapper getApiWrapper();
-    ContextualEduStatsManager getContextualEduStatsManager();
     CustomWidgetManager getCustomWidgetManager();
     DynamicResource getDynamicResource();
-    IconShape getIconShape();
     InstallSessionHelper getInstallSessionHelper();
     ItemInstallQueue getItemInstallQueue();
     RefreshRateTracker getRefreshRateTracker();
@@ -60,10 +73,25 @@
     PluginManagerWrapper getPluginManagerWrapper();
     VibratorWrapper getVibratorWrapper();
     MSDLPlayerWrapper getMSDLPlayerWrapper();
+    WindowManagerProxy getWmProxy();
+    LauncherPrefs getLauncherPrefs();
+    ThemeManager getThemeManager();
+    UserCache getUserCache();
+    DisplayController getDisplayController();
+    WallpaperColorHints getWallpaperColorHints();
+    LockedUserState getLockedUserState();
+    InvariantDeviceProfile getIDP();
+    IconPool getIconPool();
+    RemoveAnimationSettingsTracker getRemoveAnimationSettingsTracker();
+    LauncherAppState getLauncherAppState();
+    GridCustomizationsProxy getGridCustomizationsProxy();
+    WidgetsFilterDataProvider getWidgetsFilterDataProvider();
 
     /** Builder for LauncherBaseAppComponent. */
     interface Builder {
         @BindsInstance Builder appContext(@ApplicationContext Context context);
+        @BindsInstance Builder iconsDbName(@Nullable @Named("ICONS_DB") String dbFileName);
+        @BindsInstance Builder setSafeModeEnabled(@Named("SAFE_MODE") boolean safeModeEnabled);
         LauncherBaseAppComponent build();
     }
 }
diff --git a/src/com/android/launcher3/dagger/LauncherComponentProvider.kt b/src/com/android/launcher3/dagger/LauncherComponentProvider.kt
index 5015e54..6199149 100644
--- a/src/com/android/launcher3/dagger/LauncherComponentProvider.kt
+++ b/src/com/android/launcher3/dagger/LauncherComponentProvider.kt
@@ -39,14 +39,20 @@
 
         // Create a new component
         return Holder(
-                DaggerLauncherAppComponent.builder().appContext(app).build()
-                    as LauncherAppComponent,
+                DaggerLauncherAppComponent.builder()
+                    .appContext(app)
+                    .setSafeModeEnabled(true)
+                    .build() as LauncherAppComponent,
                 existingFilter,
             )
             .apply { inflater.filter = this }
             .component
     }
 
+    /** Extension method easily access LauncherAppComponent */
+    val Context.appComponent: LauncherAppComponent
+        get() = get(this)
+
     private data class Holder(
         val component: LauncherAppComponent,
         private val filter: LayoutInflater.Filter?,
diff --git a/src/com/android/launcher3/debug/TestEventEmitter.java b/src/com/android/launcher3/debug/TestEventEmitter.java
new file mode 100644
index 0000000..ed3b4bb
--- /dev/null
+++ b/src/com/android/launcher3/debug/TestEventEmitter.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.debug;
+
+/**
+ * TestEventsEmitter shouldn't do anything since it runs on the launcher code and not on
+ * tests. This is just a placeholder and test should mock the static sendEvent method.
+ * See "EventsRule.kt" in tests folder where sendEvent is statically mocked to change the
+ * behavior in tests.
+ */
+public class TestEventEmitter {
+    public static void sendEvent(TestEvent event) {
+    }
+
+    /** Events fired by the launcher. */
+    public enum TestEvent {
+
+        LAUNCHER_ON_CREATE("LAUNCHER_ON_CREATE"),
+        WORKSPACE_ON_DROP("WORKSPACE_ON_DROP"),
+        RESIZE_FRAME_SHOWING("RESIZE_FRAME_SHOWING"),
+        WORKSPACE_FINISH_LOADING("WORKSPACE_FINISH_LOADING"),
+        SPRING_LOADED_STATE_STARTED("SPRING_LOADED_STATE_STARTED"),
+        SPRING_LOADED_STATE_COMPLETED("SPRING_LOADED_STATE_COMPLETED");
+
+        TestEvent(String event) {
+        }
+
+    }
+}
+
+
diff --git a/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt b/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt
deleted file mode 100644
index 52b454f..0000000
--- a/src/com/android/launcher3/debug/TestEventsEmitterProduction.kt
+++ /dev/null
@@ -1,59 +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.debug
-
-import android.content.Context
-import android.util.Log
-import com.android.launcher3.util.MainThreadInitializedObject
-import com.android.launcher3.util.SafeCloseable
-
-/** Events fired by the launcher. */
-enum class TestEvent(val event: String) {
-    LAUNCHER_ON_CREATE("LAUNCHER_ON_CREATE"),
-    WORKSPACE_ON_DROP("WORKSPACE_ON_DROP"),
-    RESIZE_FRAME_SHOWING("RESIZE_FRAME_SHOWING"),
-    WORKSPACE_FINISH_LOADING("WORKSPACE_FINISH_LOADING"),
-    SPRING_LOADED_STATE_STARTED("SPRING_LOADED_STATE_STARTED"),
-    SPRING_LOADED_STATE_COMPLETED("SPRING_LOADED_STATE_COMPLETED"),
-}
-
-/** Interface to create TestEventEmitters. */
-interface TestEventEmitter : SafeCloseable {
-
-    companion object {
-        @JvmField
-        val INSTANCE =
-            MainThreadInitializedObject<TestEventEmitter> { _: Context? ->
-                TestEventsEmitterProduction()
-            }
-    }
-
-    fun sendEvent(event: TestEvent)
-}
-
-/**
- * TestEventsEmitterProduction shouldn't do anything since it runs on the launcher code and not on
- * tests. This is just a placeholder and test should override this class.
- */
-class TestEventsEmitterProduction : TestEventEmitter {
-
-    override fun close() {}
-
-    override fun sendEvent(event: TestEvent) {
-        Log.d("TestEventsEmitterProduction", "Event sent ${event.event}")
-    }
-}
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index c50c008..613b430 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -558,7 +558,7 @@
 
             target.getHitRectRelativeToDragLayer(r);
             if (r.contains(x, y)) {
-                mActivity.getDragLayer().mapCoordInSelfToDescendant((View) target,
+                mActivity.getDragLayer().mapCoordInSelfToDescendant(target.getDropView(),
                         mCoordinatesTemp);
                 mDragObject.x = mCoordinatesTemp[0];
                 mDragObject.y = mCoordinatesTemp[1];
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index a24f3ff..41b42b6 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -50,6 +50,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
+import com.android.launcher3.ShortcutAndWidgetContainer.TranslationProvider;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.PendingAnimation;
@@ -69,7 +70,9 @@
 public class DragLayer extends BaseDragLayer<Launcher> implements LauncherOverlayCallbacks {
 
     public static final int ALPHA_INDEX_OVERLAY = 0;
-    private static final int ALPHA_CHANNEL_COUNT = 1;
+
+    public static final int ALPHA_INDEX_LOADER = 1;
+    private static final int ALPHA_CHANNEL_COUNT = 2;
 
     public static final int ANIMATION_END_DISAPPEAR = 0;
     public static final int ANIMATION_END_REMAIN_VISIBLE = 2;
@@ -246,23 +249,27 @@
     public void animateViewIntoPosition(DragView dragView, final View child, int duration,
             View anchorView) {
 
-        ShortcutAndWidgetContainer parentChildren = (ShortcutAndWidgetContainer) child.getParent();
+        ShortcutAndWidgetContainer childParent = (ShortcutAndWidgetContainer) child.getParent();
         CellLayoutLayoutParams lp =  (CellLayoutLayoutParams) child.getLayoutParams();
-        parentChildren.measureChild(child);
-        parentChildren.layoutChild(child);
+        childParent.measureChild(child);
+        childParent.layoutChild(child);
 
         float coord[] = new float[2];
         float childScale = child.getScaleX();
 
         coord[0] = lp.x + (child.getMeasuredWidth() * (1 - childScale) / 2);
         coord[1] = lp.y + (child.getMeasuredHeight() * (1 - childScale) / 2);
+        TranslationProvider translationProvider = childParent.getTranslationProvider();
+        if (translationProvider != null) {
+            coord[0] = coord[0] + translationProvider.getTranslationX(lp.getCellX());
+        }
 
         // Since the child hasn't necessarily been laid out, we force the lp to be updated with
         // the correct coordinates (above) and use these to determine the final location
         float scale = getDescendantCoordRelativeToSelf((View) child.getParent(), coord);
 
         // We need to account for the scale of the child itself, as the above only accounts for
-        // for the scale in parents.
+        // the scale in parents.
         scale *= childScale;
         int toX = Math.round(coord[0]);
         int toY = Math.round(coord[1]);
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 67fe889..981b78b 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -60,8 +60,9 @@
 import com.android.app.animation.Interpolators;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.ThemeManager;
 import com.android.launcher3.icons.FastBitmapDrawable;
-import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.views.ActivityContext;
@@ -245,10 +246,12 @@
     public void setItemInfo(final ItemInfo info) {
         // Load the adaptive icon on a background thread and add the view in ui thread.
         MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> {
+            ThemeManager themeManager = ThemeManager.INSTANCE.get(getContext());
             int w = mWidth;
             int h = mHeight;
             Pair<AdaptiveIconDrawable, Drawable> fullDrawable = Utilities.getFullDrawable(
-                    mActivity, info, w, h, true /* shouldThemeIcon */);
+                    mActivity, info, w, h,
+                    themeManager.isIconThemeEnabled());
             if (fullDrawable != null) {
                 AdaptiveIconDrawable adaptiveIcon = fullDrawable.first;
                 int blurMargin = (int) mActivity.getResources()
@@ -260,20 +263,17 @@
                 // be scaled down due to icon normalization.
                 mBadge = fullDrawable.second;
                 FastBitmapDrawable.setBadgeBounds(mBadge, bounds);
-
-                try (LauncherIcons li = LauncherIcons.obtain(mActivity)) {
-                    // Since we just want the scale, avoid heavy drawing operations
-                    Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(
-                            new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null),
-                            null, null, null));
-                }
+                Utilities.scaleRectAboutCenter(bounds, IconNormalizer.ICON_VISIBLE_AREA_FACTOR);
 
                 // Shrink very tiny bit so that the clip path is smaller than the original bitmap
                 // that has anti aliased edges and shadows.
                 Rect shrunkBounds = new Rect(bounds);
                 Utilities.scaleRectAboutCenter(shrunkBounds, 0.98f);
                 adaptiveIcon.setBounds(shrunkBounds);
-                final Path mask = adaptiveIcon.getIconMask();
+
+                final Path mask = (adaptiveIcon instanceof FolderAdaptiveIcon
+                        ? themeManager.getFolderShape() : themeManager.getIconShape())
+                        .getPath(shrunkBounds);
 
                 mTranslateX = new SpringFloatValue(DragView.this,
                         w * AdaptiveIconDrawable.getExtraInsetFraction());
@@ -563,7 +563,7 @@
         return mContentViewParent;
     }
 
-    /** Return true if {@link mContent} is a {@link AppWidgetHostView}. */
+    /** Return true if {@link #mContent} is a {@link AppWidgetHostView}. */
     public boolean containsAppWidgetHostView() {
         return mContent instanceof AppWidgetHostView;
     }
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 6a43b24..929e52e 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -165,7 +165,7 @@
         Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
         backgroundPaint.setColor(bg.getBgColor());
         bg.drawShadow(backgroundCanvas);
-        backgroundCanvas.drawCircle(size / 2f, size / 2f, bg.getRadius(), backgroundPaint);
+        backgroundCanvas.drawPaint(backgroundPaint);
         bg.drawBackgroundStroke(backgroundCanvas);
     }
 
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
deleted file mode 100644
index bebef70..0000000
--- a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2010 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.dragndrop;
-
-import com.android.launcher3.Alarm;
-import com.android.launcher3.CellLayout;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.OnAlarmListener;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
-
-public class SpringLoadedDragController implements OnAlarmListener {
-    // how long the user must hover over a mini-screen before it unshrinks
-    private static final long ENTER_SPRING_LOAD_HOVER_TIME = 500;
-    private static final long ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST = 3000;
-    private static final long ENTER_SPRING_LOAD_CANCEL_HOVER_TIME = 950;
-
-    Alarm mAlarm;
-
-    // the screen the user is currently hovering over, if any
-    private CellLayout mScreen;
-    private Launcher mLauncher;
-
-    public SpringLoadedDragController(Launcher launcher) {
-        mLauncher = launcher;
-        mAlarm = new Alarm();
-        mAlarm.setOnAlarmListener(this);
-    }
-
-    private long getEnterSpringLoadHoverTime() {
-        // Some TAPL tests are flaky on Cuttlefish with a low waiting time
-        return Utilities.isRunningInTestHarness()
-                ? ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST
-                : ENTER_SPRING_LOAD_HOVER_TIME;
-    }
-
-    public void cancel() {
-        mAlarm.cancelAlarm();
-    }
-
-    // Set a new alarm to expire for the screen that we are hovering over now
-    public void setAlarm(CellLayout cl) {
-        mAlarm.cancelAlarm();
-        mAlarm.setAlarm((cl == null) ? ENTER_SPRING_LOAD_CANCEL_HOVER_TIME
-                : getEnterSpringLoadHoverTime());
-        mScreen = cl;
-    }
-
-    // this is called when our timer runs out
-    public void onAlarm(Alarm alarm) {
-        if (mScreen != null) {
-            // Snap to the screen that we are hovering over now
-            Workspace<?> w = mLauncher.getWorkspace();
-            if (!w.isVisible(mScreen)) {
-                w.snapToPage(w.indexOfChild(mScreen));
-            }
-        } else {
-            mLauncher.getDragController().cancelDrag();
-        }
-    }
-}
diff --git a/src/com/android/launcher3/dragndrop/SpringLoadedDragController.kt b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.kt
new file mode 100644
index 0000000..4cbe7bb
--- /dev/null
+++ b/src/com/android/launcher3/dragndrop/SpringLoadedDragController.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2010 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.dragndrop
+
+import com.android.launcher3.Alarm
+import com.android.launcher3.CellLayout
+import com.android.launcher3.Launcher
+import com.android.launcher3.OnAlarmListener
+import com.android.launcher3.Utilities
+
+class SpringLoadedDragController(private val launcher: Launcher) : OnAlarmListener {
+    internal val alarm = Alarm().also { it.setOnAlarmListener(this) }
+
+    // the screen the user is currently hovering over, if any
+    private var screen: CellLayout? = null
+
+    fun cancel() = alarm.cancelAlarm()
+
+    // Set a new alarm to expire for the screen that we are hovering over now
+    fun setAlarm(cl: CellLayout?) {
+        cancel()
+        alarm.setAlarm(
+            when {
+                cl == null -> ENTER_SPRING_LOAD_CANCEL_HOVER_TIME
+                // Some TAPL tests are flaky on Cuttlefish with a low waiting time
+                Utilities.isRunningInTestHarness() -> ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST
+                else -> ENTER_SPRING_LOAD_HOVER_TIME
+            }
+        )
+        screen = cl
+    }
+
+    // this is called when our timer runs out
+    override fun onAlarm(alarm: Alarm) {
+        if (screen != null) {
+            // Snap to the screen that we are hovering over now
+            with(launcher.workspace) {
+                if (!isVisible(screen) && launcher.dragController.mDistanceSinceScroll != 0) {
+                    snapToPage(indexOfChild(screen))
+                }
+            }
+        } else {
+            launcher.dragController.cancelDrag()
+        }
+    }
+
+    companion object {
+        // how long the user must hover over a mini-screen before it unshrinks
+        private const val ENTER_SPRING_LOAD_HOVER_TIME: Long = 500
+        private const val ENTER_SPRING_LOAD_HOVER_TIME_IN_TEST: Long = 3000
+        private const val ENTER_SPRING_LOAD_CANCEL_HOVER_TIME: Long = 950
+    }
+}
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 8cd91d3..fedc118 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.folder;
 
+import com.android.launcher3.Flags;
+
 public class ClippedFolderIconLayoutRule {
 
     public static final int MAX_NUM_ITEMS_IN_PREVIEW = 4;
@@ -7,9 +9,12 @@
 
     private static final float MIN_SCALE = 0.44f;
     private static final float MAX_SCALE = 0.51f;
+    // TODO: figure out exact radius for different icons
+    private static final float MAX_RADIUS_DILATION_SHAPES = 0.15f;
     private static final float MAX_RADIUS_DILATION = 0.25f;
     // The max amount of overlap the preview items can go outside of the background bounds.
     public static final float ICON_OVERLAP_FACTOR = 1 + (MAX_RADIUS_DILATION / 2f);
+    public static final float ICON_OVERLAP_FACTOR_SHAPES = 1f;
     private static final float ITEM_RADIUS_SCALE_FACTOR = 1.15f;
 
     public static final int EXIT_INDEX = -2;
@@ -28,7 +33,7 @@
         mRadius = ITEM_RADIUS_SCALE_FACTOR * availableSpace / 2f;
         mIconSize = intrinsicIconSize;
         mIsRtl = rtl;
-        mBaselineIconScale = availableSpace / (intrinsicIconSize * 1f);
+        mBaselineIconScale = availableSpace / intrinsicIconSize;
     }
 
     public PreviewItemDrawingParams computePreviewItemDrawingParams(int index, int curNumItems,
@@ -48,6 +53,20 @@
         } else if (index >= MAX_NUM_ITEMS_IN_PREVIEW) {
             // Items beyond those displayed in the preview are animated to the center
             mTmpPoint[0] = mTmpPoint[1] = mAvailableSpace / 2 - (mIconSize * totalScale) / 2;
+        } else if (Flags.enableLauncherIconShapes()) {
+            if (index == 0) {
+                // top left
+                getGridPosition(0, 0, mTmpPoint);
+            } else if (index == 1) {
+                // top right
+                getGridPosition(0, 1, mTmpPoint);
+            } else if (index == 2) {
+                // bottom left
+                getGridPosition(1, 0, mTmpPoint);
+            } else if (index == 3) {
+                // bottom right
+                getGridPosition(1, 1, mTmpPoint);
+            }
         } else {
             getPosition(index, curNumItems, mTmpPoint);
         }
@@ -84,6 +103,7 @@
         result[1] = top + (row * dy);
     }
 
+    // b/392610664 TODO: Change positioning from circular geometry to square / grid-based.
     private void getPosition(int index, int curNumItems, float[] result) {
         // The case of two items is homomorphic to the case of one.
         curNumItems = Math.max(curNumItems, 2);
@@ -113,8 +133,10 @@
         }
 
         // We bump the radius up between 0 and MAX_RADIUS_DILATION % as the number of items increase
-        float radius = mRadius * (1 + MAX_RADIUS_DILATION * (curNumItems -
-                MIN_NUM_ITEMS_IN_PREVIEW) / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW));
+        float radiusDilation = Flags.enableLauncherIconShapes() ? MAX_RADIUS_DILATION_SHAPES
+                : MAX_RADIUS_DILATION;
+        float radius = mRadius * (1 + radiusDilation * (curNumItems - MIN_NUM_ITEMS_IN_PREVIEW)
+                / (MAX_NUM_ITEMS_IN_PREVIEW - MIN_NUM_ITEMS_IN_PREVIEW));
         double theta = theta0 + index * (2 * Math.PI / curNumItems) * direction;
 
         float halfIconSize = (mIconSize * scaleForItem(curNumItems)) / 2;
@@ -130,7 +152,7 @@
     public float scaleForItem(int numItems) {
         // Scale is determined by the number of items in the preview.
         final float scale;
-        if (numItems <= 3) {
+        if (numItems <= 3 && !Flags.enableLauncherIconShapes()) {
             scale = MAX_SCALE;
         } else {
             scale = MIN_SCALE;
@@ -141,4 +163,15 @@
     public float getIconSize() {
         return mIconSize;
     }
+
+    /**
+     * Gets correct constant for icon overlap.
+     */
+    public static float getIconOverlapFactor() {
+        if (Flags.enableLauncherIconShapes()) {
+            return ICON_OVERLAP_FACTOR_SHAPES;
+        } else {
+            return ICON_OVERLAP_FACTOR;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 5defef3..2803256 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -65,6 +65,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.content.res.ResourcesCompat;
@@ -100,6 +101,7 @@
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pageindicators.PageIndicatorDots;
 import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.views.ActivityContext;
@@ -110,7 +112,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
@@ -123,7 +124,8 @@
  */
 public class Folder extends AbstractFloatingView implements ClipPathView, DragSource,
         View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
-        View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener {
+        View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener,
+        LauncherBindableItemsContainer {
     private static final String TAG = "Launcher.Folder";
     private static final boolean DEBUG = false;
 
@@ -262,7 +264,7 @@
     @Nullable
     private KeyboardInsetAnimationCallback mKeyboardInsetAnimationCallback;
 
-    private GradientDrawable mBackground;
+    private final @NonNull GradientDrawable mBackground;
 
     /**
      * Used to inflate the Workspace from XML.
@@ -284,6 +286,10 @@
         // click).
         setFocusableInTouchMode(true);
 
+        mBackground = (GradientDrawable) Objects.requireNonNull(
+                ResourcesCompat.getDrawable(getResources(),
+                        R.drawable.round_rect_folder, getContext().getTheme()));
+        mBackground.setCallback(this);
     }
 
     @Override
@@ -297,9 +303,6 @@
         final DeviceProfile dp = mActivityContext.getDeviceProfile();
         final int paddingLeftRight = dp.folderContentPaddingLeftRight;
 
-        mBackground = (GradientDrawable) ResourcesCompat.getDrawable(getResources(),
-                R.drawable.round_rect_folder, getContext().getTheme());
-
         mContent = findViewById(R.id.folder_content);
         mContent.setPadding(paddingLeftRight, dp.folderContentPaddingTop, paddingLeftRight, 0);
         mContent.setFolder(this);
@@ -346,6 +349,11 @@
         return true;
     }
 
+    @Override
+    protected boolean verifyDrawable(@NonNull Drawable who) {
+        return super.verifyDrawable(who) || (who == mBackground);
+    }
+
     void callBeginDragShared(View v, DragOptions options) {
         mLauncherDelegate.beginDragShared(v, this, options);
     }
@@ -517,8 +525,6 @@
         mInfo = info;
         mFromTitle = info.title;
         mFromLabelState = info.getFromLabelState();
-        ArrayList<ItemInfo> children = info.getContents();
-        Collections.sort(children, ITEM_POS_COMPARATOR);
         updateItemLocationsInDatabaseBatch(true);
 
         BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) getLayoutParams();
@@ -680,6 +686,7 @@
         closeOpenFolder(openFolder);
 
         mContent.bindItems(items);
+        mContent.setCanAnnouncePageDescriptionForFolder(true);
         centerAboutIcon();
         mItemsInvalidated = true;
         updateTextViewFocus();
@@ -699,15 +706,23 @@
             }
         }
 
+        Log.d("b/383526431", "animateOpen: content child count before: "
+                + mContent.getTotalChildCount());
+
         mContent.completePendingPageChanges();
         mContent.setCurrentPage(pageNo);
 
+        Log.d("b/383526431", "animateOpen: content child count after pending page"
+                + " changes: " + mContent.getTotalChildCount());
+
         // This is set to true in close(), but isn't reset to false until onDropCompleted(). This
         // leads to an inconsistent state if you drag out of the folder and drag back in without
         // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
         mDeleteFolderOnDropCompleted = false;
 
         cancelRunningAnimations();
+        Log.d("b/383526431", "animateOpen: content child count after cancelling"
+                + " animation: " + mContent.getTotalChildCount());
         FolderAnimationManager fam = new FolderAnimationManager(this, true /* isOpening */);
         AnimatorSet anim = fam.getAnimator();
         anim.addListener(new AnimatorListenerAdapter() {
@@ -813,6 +828,7 @@
     @Override
     protected void handleClose(boolean animate) {
         mIsOpen = false;
+        mContent.setCanAnnouncePageDescriptionForFolder(false);
 
         if (!animate && mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
             mCurrentAnimator.cancel();
@@ -1499,8 +1515,10 @@
     /**
      * Utility methods to iterate over items of the view
      */
-    public void iterateOverItems(ItemOperator op) {
-        mContent.iterateOverItems(op);
+    @Override
+    @Nullable
+    public View mapOverItems(@NonNull ItemOperator op) {
+        return mContent.iterateOverItems(op);
     }
 
     /**
@@ -1701,7 +1719,7 @@
 
     @Override
     public boolean canInterceptEventsInSystemGestureRegion() {
-        return true;
+        return !mIsEditingName;
     }
 
     /**
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index 588a6db..d2354c1 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -35,6 +35,7 @@
 import android.util.Property;
 import android.view.View;
 import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
@@ -45,8 +46,8 @@
 import com.android.launcher3.anim.PropertyResetListener;
 import com.android.launcher3.apppairs.AppPairIcon;
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
-import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.graphics.IconShape.ShapeDelegate;
+import com.android.launcher3.graphics.ShapeDelegate;
+import com.android.launcher3.graphics.ThemeManager;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer;
 
@@ -79,9 +80,10 @@
     private final int mDuration;
     private final int mDelay;
 
-    private final TimeInterpolator mFolderInterpolator;
-    private final TimeInterpolator mLargeFolderPreviewItemOpenInterpolator;
-    private final TimeInterpolator mLargeFolderPreviewItemCloseInterpolator;
+    private final Interpolator mFolderOpenInterpolator;
+    private final Interpolator mFolderCloseInterpolator;
+    private final Interpolator mLargeFolderPreviewItemOpenInterpolator;
+    private final Interpolator mLargeFolderPreviewItemCloseInterpolator;
 
     private final PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0);
     private final FolderGridOrganizer mPreviewVerifier;
@@ -108,7 +110,9 @@
         mDuration = res.getInteger(R.integer.config_materialFolderExpandDuration);
         mDelay = res.getInteger(R.integer.config_folderDelay);
 
-        mFolderInterpolator = AnimationUtils.loadInterpolator(mContext,
+        mFolderOpenInterpolator = AnimationUtils.loadInterpolator(mContext,
+                R.interpolator.standard_interpolator);
+        mFolderCloseInterpolator = AnimationUtils.loadInterpolator(mContext,
                 R.interpolator.standard_interpolator);
         mLargeFolderPreviewItemOpenInterpolator = AnimationUtils.loadInterpolator(mContext,
                 R.interpolator.large_folder_preview_item_open_interpolator);
@@ -233,7 +237,7 @@
         }
         play(a, getAnimator(mFolder.mFooter, ALPHA, 0, 1f), footerStartDelay, footerAlphaDuration);
 
-        ShapeDelegate shapeDelegate = IconShape.INSTANCE.get(mContext).getShape();
+        ShapeDelegate shapeDelegate = ThemeManager.INSTANCE.get(mContext).getFolderShape();
         // Create reveal animator for the folder background
         play(a, shapeDelegate.createRevealAnimator(
                 mFolder, startRect, endRect, finalRadius, !mIsOpening));
@@ -252,6 +256,7 @@
                 (int) (left + (startRect.right / initialScale)) + extraRadius,
                 (int) (startRect.bottom / initialScale) + extraRadius);
         Rect contentEnd = new Rect(left, 0, left + lp.width, lp.height);
+        // animated contents of folder with the folder background
         play(a, shapeDelegate.createRevealAnimator(
                 mFolder.getContent(), contentStart, contentEnd, finalRadius, !mIsOpening));
 
@@ -332,7 +337,11 @@
         // We set the interpolator on all current child animators here, because the preview item
         // animators may use a different interpolator.
         for (Animator animator : a.getChildAnimations()) {
-            animator.setInterpolator(mFolderInterpolator);
+            animator.setInterpolator(
+                    mIsOpening
+                    ? mFolderOpenInterpolator
+                    : mFolderCloseInterpolator
+            );
         }
 
         int radiusDiff = scaledRadius - mPreviewBackground.getRadius();
@@ -467,7 +476,7 @@
         return mFolder.getItemCount() > MAX_NUM_ITEMS_IN_PREVIEW;
     }
 
-    private TimeInterpolator getPreviewItemInterpolator() {
+    private Interpolator getPreviewItemInterpolator() {
         if (isLargeFolder()) {
             // With larger folders, we want the preview items to reach their final positions faster
             // (when opening) and later (when closing) so that they appear aligned with the rest of
@@ -476,7 +485,7 @@
                     ? mLargeFolderPreviewItemOpenInterpolator
                     : mLargeFolderPreviewItemCloseInterpolator;
         }
-        return mFolderInterpolator;
+        return mIsOpening ? mFolderOpenInterpolator : mFolderCloseInterpolator;
     }
 
     private Animator getAnimator(View view, Property property, float v1, float v2) {
diff --git a/src/com/android/launcher3/folder/FolderGridOrganizer.java b/src/com/android/launcher3/folder/FolderGridOrganizer.java
index a7ab7b9..06286d6 100644
--- a/src/com/android/launcher3/folder/FolderGridOrganizer.java
+++ b/src/com/android/launcher3/folder/FolderGridOrganizer.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
 
 import android.graphics.Point;
+import android.util.Log;
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.model.data.FolderInfo;
@@ -178,6 +179,14 @@
                 break;
             }
         }
+
+        if (result.isEmpty()) {
+            // Log specifics since we are getting empty result
+            Log.d("b/383526431", "previewItemsForPage: "
+                    + "mCountX = " + mCountX
+                    + ", mCountY = " + mCountY
+                    + ", content size = " + contents.size());
+        }
         return result;
     }
 
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 9ff6475..0ed8787 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.folder;
 
 import static com.android.launcher3.Flags.enableCursorHoverStates;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
 import static com.android.launcher3.folder.FolderGridOrganizer.createFolderGridOrganizer;
 import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
@@ -248,7 +247,8 @@
         mPreviewItemManager.recomputePreviewDrawingParams();
         mBackground.getBounds(outBounds);
         // The preview items go outside of the bounds of the background.
-        Utilities.scaleRectAboutCenter(outBounds, ICON_OVERLAP_FACTOR);
+        Utilities.scaleRectAboutCenter(outBounds,
+                ClippedFolderIconLayoutRule.getIconOverlapFactor());
     }
 
     public float getBackgroundStrokeWidth() {
@@ -818,6 +818,10 @@
      * Returns a formatted accessibility title for folder
      */
     public String getAccessiblityTitle(CharSequence title) {
+        if (title == null) {
+            // Avoids "Talkback -> Folder: null" announcement.
+            title = getContext().getString(R.string.unnamed_folder);
+        }
         int size = mInfo.getContents().size();
         if (size < MAX_NUM_ITEMS_IN_PREVIEW) {
             return getContext().getString(R.string.folder_name_format_exact, title, size);
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index be5f8f7..8a1f96d 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.folder;
 
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
+
 import android.annotation.SuppressLint;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
@@ -37,6 +39,7 @@
 import com.android.launcher3.model.StringCache;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.CollectionInfo;
+import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.Preconditions;
@@ -197,9 +200,18 @@
         @Override
         public void execute(@NonNull ModelTaskController taskController,
                 @NonNull BgDataModel dataModel, @NonNull AllAppsList apps) {
-            mCollectionInfos = dataModel.collections.clone();
+            mCollectionInfos = getCollectionForSuggestions(dataModel);
             mAppInfos = Arrays.asList(apps.copyData());
         }
     }
 
+    public static IntSparseArrayMap<CollectionInfo> getCollectionForSuggestions(
+            BgDataModel dataModel) {
+        IntSparseArrayMap<CollectionInfo> result = new IntSparseArrayMap<>();
+        dataModel.itemsIdMap.stream()
+                .filter(item -> item.itemType == ITEM_TYPE_FOLDER)
+                .forEach(item -> result.put(item.id, (FolderInfo) item));
+        return result;
+    }
+
 }
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index fe26194..bebe1a4 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.graphics.Canvas;
 import android.graphics.Path;
-import android.graphics.drawable.Drawable;
 import android.util.ArrayMap;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -59,6 +58,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.ToIntFunction;
 import java.util.stream.Collectors;
 
@@ -100,6 +100,8 @@
     // animating or is open.
     private boolean mViewsBound = false;
 
+    private boolean mCanAnnouncePageDescription;
+
     public FolderPagedView(Context context, AttributeSet attrs) {
         this(
                 context,
@@ -170,6 +172,19 @@
         mViewsBound = true;
     }
 
+    void setCanAnnouncePageDescriptionForFolder(boolean canAnnounce) {
+        mCanAnnouncePageDescription = canAnnounce;
+    }
+
+    private boolean canAnnouncePageDescriptionForFolder() {
+        return mCanAnnouncePageDescription;
+    }
+
+    @Override
+    protected boolean canAnnouncePageDescription() {
+        return super.canAnnouncePageDescription() && canAnnouncePageDescriptionForFolder();
+    }
+
     /**
      * Removes all the icons from the folder
      */
@@ -517,6 +532,16 @@
         verifyVisibleHighResIcons(getCurrentPage() + 1);
     }
 
+    int getTotalChildCount() {
+        AtomicInteger count = new AtomicInteger();
+        iterateOverItems((i, v) -> {
+            count.getAndIncrement();
+            return false;
+        });
+
+        return count.get();
+    }
+
     /**
      * Ensures that all the icons on the given page are of high-res
      */
@@ -526,19 +551,10 @@
             ShortcutAndWidgetContainer parent = page.getShortcutsAndWidgets();
             for (int i = parent.getChildCount() - 1; i >= 0; i--) {
                 View iconView = parent.getChildAt(i);
-                Drawable d = null;
                 if (iconView instanceof BubbleTextView btv) {
                     btv.verifyHighRes();
-                    d = btv.getIcon();
                 } else if (iconView instanceof AppPairIcon api) {
                     api.verifyHighRes();
-                    d = api.getIconDrawableArea().getDrawable();
-                }
-
-                // Set the callback back to the actual icon, in case
-                // it was captured by the FolderIcon
-                if (d != null) {
-                    d.setCallback(iconView);
                 }
             }
         }
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index df41d47..ba8a290 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -18,7 +18,6 @@
 
 import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
 import static com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE;
-import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 
 import android.animation.Animator;
@@ -48,8 +47,8 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.celllayout.DelegatedCellDrawing;
-import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.graphics.IconShape.ShapeDelegate;
+import com.android.launcher3.graphics.ShapeDelegate;
+import com.android.launcher3.graphics.ThemeManager;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
 
@@ -261,7 +260,7 @@
     }
 
     private ShapeDelegate getShape() {
-        return IconShape.INSTANCE.get(mContext).getShape();
+        return ThemeManager.INSTANCE.get(mContext).getFolderShape();
     }
 
     public void drawShadow(Canvas canvas) {
@@ -373,7 +372,7 @@
 
     public Path getClipPath() {
         mPath.reset();
-        float radius = getScaledRadius() * ICON_OVERLAP_FACTOR;
+        float radius = getScaledRadius() * ClippedFolderIconLayoutRule.getIconOverlapFactor();
         // Find the difference in radius so that the clip path remains centered.
         float radiusDifference = radius - getRadius();
         float offsetX = basePreviewOffsetX - radiusDifference;
diff --git a/src/com/android/launcher3/folder/PreviewItemManager.java b/src/com/android/launcher3/folder/PreviewItemManager.java
index 2276ac7..4cf618d 100644
--- a/src/com/android/launcher3/folder/PreviewItemManager.java
+++ b/src/com/android/launcher3/folder/PreviewItemManager.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.folder;
 
 import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER;
+import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
@@ -43,14 +44,15 @@
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.Flags;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.apppairs.AppPairIcon;
 import com.android.launcher3.apppairs.AppPairIconDrawingParams;
 import com.android.launcher3.apppairs.AppPairIconGraphic;
 import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ActivityContext;
 
 import java.util.ArrayList;
@@ -445,8 +447,7 @@
             if (isActivePendingIcon(wii)) {
                 p.drawable = newPendingIcon(mContext, wii);
             } else {
-                p.drawable = wii.newIcon(mContext,
-                        Themes.isThemedIconEnabled(mContext) ? FLAG_THEMED : 0);
+                p.drawable = wii.newIcon(mContext, FLAG_THEMED);
             }
             p.drawable.setBounds(0, 0, mIconSize, mIconSize);
         } else if (item instanceof AppPairInfo api) {
@@ -460,6 +461,18 @@
         // Set the callback to FolderIcon as it is responsible to drawing the icon. The
         // callback will be released when the folder is opened.
         p.drawable.setCallback(mIcon);
+
+        // Verify high res
+        if (item instanceof ItemInfoWithIcon info
+                && info.getMatchingLookupFlag().isVisuallyLessThan(DESKTOP_ICON_FLAG)) {
+            LauncherAppState.getInstance(mContext).getIconCache().updateIconInBackground(
+                    newInfo -> {
+                        if (p.item == newInfo) {
+                            setDrawable(p, newInfo);
+                            mIcon.invalidate();
+                        }
+                    }, info);
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
similarity index 76%
rename from src/com/android/launcher3/graphics/GridCustomizationsProvider.java
rename to src/com/android/launcher3/graphics/GridCustomizationsProxy.java
index 836ae98..70b9f46 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProxy.java
@@ -15,19 +15,18 @@
  */
 package com.android.launcher3.graphics;
 
-import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
+
+import static com.android.launcher3.graphics.ThemeManager.PREF_ICON_SHAPE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.launcher3.util.Themes.isThemedIconEnabled;
 
-import android.content.ContentProvider;
+import static java.util.Objects.requireNonNullElse;
+
 import android.content.ContentValues;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.net.Uri;
-import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder.DeathRecipient;
@@ -43,22 +42,30 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
 import com.android.launcher3.LauncherPrefs;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.model.BgDataModel;
-import com.android.launcher3.shapes.AppShape;
-import com.android.launcher3.shapes.AppShapesProvider;
+import com.android.launcher3.shapes.IconShapeModel;
+import com.android.launcher3.shapes.ShapesProvider;
+import com.android.launcher3.util.ContentProviderProxy.ProxyProvider;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RunnableList;
 import com.android.systemui.shared.Flags;
 
 import java.lang.ref.WeakReference;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 
+import javax.inject.Inject;
+
 /**
  * Exposes various launcher grid options and allows the caller to change them.
  * APIs:
@@ -73,7 +80,7 @@
  *          rows: number of rows in the grid
  *          cols: number of columns in the grid
  *          preview_count: number of previews available for this grid option. The preview uri
- *                         looks like /preview/<grid-name>/<preview index starting with 0>
+ *                         looks like /preview/[grid-name]/[preview index starting with 0]
  *          is_default: true if this grid option is currently set to the system
  *
  *     /get_preview: Open a file stream for the grid preview
@@ -82,14 +89,18 @@
  *          shape_key: key of the shape to apply
  *          name: key of the grid to apply
  */
-public class GridCustomizationsProvider extends ContentProvider {
+@LauncherAppSingleton
+public class GridCustomizationsProxy implements ProxyProvider {
 
     private static final String TAG = "GridCustomizationsProvider";
 
+    // KEY_NAME is the name of the grid used internally while the KEY_GRID_TITLE is the translated
+    // string title of the grid.
     private static final String KEY_NAME = "name";
     private static final String KEY_GRID_TITLE = "grid_title";
     private static final String KEY_ROWS = "rows";
     private static final String KEY_COLS = "cols";
+    private static final String KEY_GRID_ICON_ID = "grid_icon_id";
     private static final String KEY_PREVIEW_COUNT = "preview_count";
     // is_default means if a certain option is currently set to the system
     private static final String KEY_IS_DEFAULT = "is_default";
@@ -124,17 +135,31 @@
     private final Set<PreviewLifecycleObserver> mActivePreviews =
             Collections.newSetFromMap(new ConcurrentHashMap<>());
 
-    @Override
-    public boolean onCreate() {
-        return true;
+    private final Context mContext;
+    private final ThemeManager mThemeManager;
+    private final LauncherPrefs mPrefs;
+    private final InvariantDeviceProfile mIdp;
+
+    @Inject
+    GridCustomizationsProxy(
+            @ApplicationContext Context context,
+            ThemeManager themeManager,
+            LauncherPrefs prefs,
+            InvariantDeviceProfile idp,
+            DaggerSingletonTracker lifeCycle
+    ) {
+        mContext = context;
+        mThemeManager = themeManager;
+        mPrefs = prefs;
+        mIdp = idp;
+        lifeCycle.addCloseable(() -> mActivePreviews.forEach(PreviewLifecycleObserver::binderDied));
     }
 
     @Override
     public Cursor query(Uri uri, String[] projection, String selection,
             String[] selectionArgs, String sortOrder) {
-        Context context = getContext();
         String path = uri.getPath();
-        if (context == null || path == null) {
+        if (path == null) {
             return null;
         }
 
@@ -143,16 +168,24 @@
                 if (Flags.newCustomizationPickerUi()) {
                     MatrixCursor cursor = new MatrixCursor(new String[]{
                             KEY_SHAPE_KEY, KEY_SHAPE_TITLE, KEY_PATH, KEY_IS_DEFAULT});
-                    List<AppShape> shapes =  AppShapesProvider.INSTANCE.getShapes();
-                    for (int i = 0; i < shapes.size(); i++) {
-                        AppShape shape = shapes.get(i);
+                    String currentShapePath = mThemeManager.getIconState().getIconMask();
+                    Optional<IconShapeModel> selectedShape = Arrays.stream(
+                            ShapesProvider.INSTANCE.getIconShapes()).filter(
+                                    shape -> shape.getPathString().equals(currentShapePath)
+                    ).findFirst();
+                    // Handle default for when current shape doesn't match new shapes.
+                    if (selectedShape.isEmpty()) {
+                        selectedShape = Optional.of(Arrays.stream(
+                                ShapesProvider.INSTANCE.getIconShapes()
+                        ).findFirst().get());
+                    }
+
+                    for (IconShapeModel shape : ShapesProvider.INSTANCE.getIconShapes()) {
                         cursor.newRow()
                                 .add(KEY_SHAPE_KEY, shape.getKey())
                                 .add(KEY_SHAPE_TITLE, shape.getTitle())
-                                .add(KEY_PATH, shape.getPath())
-                                // TODO (b/348664593): We should fetch the currently-set shape
-                                //  option from the preferences.
-                                .add(KEY_IS_DEFAULT, i == 0);
+                                .add(KEY_PATH, shape.getPathString())
+                                .add(KEY_IS_DEFAULT, shape.equals(selectedShape.get()));
                     }
                     return cursor;
                 } else  {
@@ -162,24 +195,30 @@
             case KEY_LIST_OPTIONS: {
                 MatrixCursor cursor = new MatrixCursor(new String[]{
                         KEY_NAME, KEY_GRID_TITLE, KEY_ROWS, KEY_COLS, KEY_PREVIEW_COUNT,
-                        KEY_IS_DEFAULT});
-                InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(getContext());
-                for (GridOption gridOption : idp.parseAllGridOptions(getContext())) {
+                        KEY_IS_DEFAULT, KEY_GRID_ICON_ID});
+                List<GridOption> gridOptionList = mIdp.parseAllGridOptions(mContext);
+                if (com.android.launcher3.Flags.oneGridSpecs()) {
+                    gridOptionList.sort(Comparator
+                            .comparingInt((GridOption option) -> option.numColumns)
+                            .reversed());
+                }
+                for (GridOption gridOption : gridOptionList) {
                     cursor.newRow()
                             .add(KEY_NAME, gridOption.name)
-                            .add(KEY_GRID_TITLE, gridOption.title)
+                            .add(KEY_GRID_TITLE, gridOption.gridTitle)
                             .add(KEY_ROWS, gridOption.numRows)
                             .add(KEY_COLS, gridOption.numColumns)
                             .add(KEY_PREVIEW_COUNT, 1)
-                            .add(KEY_IS_DEFAULT, idp.numColumns == gridOption.numColumns
-                                    && idp.numRows == gridOption.numRows);
+                            .add(KEY_IS_DEFAULT, mIdp.numColumns == gridOption.numColumns
+                                    && mIdp.numRows == gridOption.numRows)
+                            .add(KEY_GRID_ICON_ID, gridOption.gridIconId);
                 }
                 return cursor;
             }
             case GET_ICON_THEMED:
             case ICON_THEMED: {
                 MatrixCursor cursor = new MatrixCursor(new String[]{BOOLEAN_VALUE});
-                cursor.newRow().add(BOOLEAN_VALUE, isThemedIconEnabled(getContext()) ? 1 : 0);
+                cursor.newRow().add(BOOLEAN_VALUE, mThemeManager.isMonoThemeEnabled() ? 1 : 0);
                 return cursor;
             }
             default:
@@ -188,42 +227,21 @@
     }
 
     @Override
-    public String getType(Uri uri) {
-        return "vnd.android.cursor.dir/launcher_grid";
-    }
-
-    @Override
-    public Uri insert(Uri uri, ContentValues initialValues) {
-        return null;
-    }
-
-    @Override
-    public int delete(Uri uri, String selection, String[] selectionArgs) {
-        return 0;
-    }
-
-    @Override
     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
         String path = uri.getPath();
-        Context context = getContext();
-        if (path == null || context == null) {
+        if (path == null) {
             return 0;
         }
         switch (path) {
             case KEY_DEFAULT_GRID: {
                 if (Flags.newCustomizationPickerUi()) {
-                    String shapeKey = values.getAsString(KEY_SHAPE_KEY);
-                    Optional<AppShape> optionalShape = AppShapesProvider.INSTANCE.getShapes()
-                            .stream().filter(shape -> shape.getKey().equals(shapeKey)).findFirst();
-                    String pathToSet = optionalShape.map(AppShape::getPath).orElse(null);
-                    // TODO (b/348664593): Apply shapeName to the system. This needs to be a
-                    //  synchronous call.
+                    mPrefs.put(PREF_ICON_SHAPE,
+                            requireNonNullElse(values.getAsString(KEY_SHAPE_KEY), ""));
                 }
                 String gridName = values.getAsString(KEY_NAME);
-                InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(context);
                 // Verify that this is a valid grid option
                 GridOption match = null;
-                for (GridOption option : idp.parseAllGridOptions(context)) {
+                for (GridOption option : mIdp.parseAllGridOptions(mContext)) {
                     String name = option.name;
                     if (name != null && name.equals(gridName)) {
                         match = option;
@@ -234,23 +252,22 @@
                     return 0;
                 }
 
-                idp.setCurrentGrid(context, gridName);
+                mIdp.setCurrentGrid(mContext, gridName);
                 if (Flags.newCustomizationPickerUi()) {
                     try {
                         // Wait for device profile to be fully reloaded and applied to the launcher
-                        loadModelSync(context);
+                        loadModelSync(mContext);
                     } catch (ExecutionException | InterruptedException e) {
                         Log.e(TAG, "Fail to load model", e);
                     }
                 }
-                context.getContentResolver().notifyChange(uri, null);
+                mContext.getContentResolver().notifyChange(uri, null);
                 return 1;
             }
             case ICON_THEMED:
             case SET_ICON_THEMED: {
-                LauncherPrefs.get(context)
-                        .put(THEMED_ICONS, values.getAsBoolean(BOOLEAN_VALUE));
-                context.getContentResolver().notifyChange(uri, null);
+                mThemeManager.setMonoThemeEnabled(values.getAsBoolean(BOOLEAN_VALUE));
+                mContext.getContentResolver().notifyChange(uri, null);
                 return 1;
             }
             default:
@@ -277,17 +294,6 @@
 
     @Override
     public Bundle call(@NonNull String method, String arg, Bundle extras) {
-        Context context = getContext();
-        if (context == null) {
-            return null;
-        }
-
-        if (context.checkPermission("android.permission.BIND_WALLPAPER",
-                Binder.getCallingPid(), Binder.getCallingUid())
-                != PackageManager.PERMISSION_GRANTED) {
-            return null;
-        }
-
         if (METHOD_GET_PREVIEW.equals(method)) {
             return getPreview(extras);
         } else {
@@ -296,14 +302,10 @@
     }
 
     private synchronized Bundle getPreview(Bundle request) {
-        Context context = getContext();
-        if (context == null) {
-            return null;
-        }
         RunnableList lifeCycleTracker = new RunnableList();
         try {
             PreviewSurfaceRenderer renderer = new PreviewSurfaceRenderer(
-                    getContext(), lifeCycleTracker, request);
+                    mContext, lifeCycleTracker, request);
             PreviewLifecycleObserver observer =
                     new PreviewLifecycleObserver(lifeCycleTracker, renderer);
 
@@ -363,14 +365,12 @@
                     renderer.hideBottomRow(message.getData().getBoolean(KEY_HIDE_BOTTOM_ROW));
                     break;
                 case MESSAGE_ID_UPDATE_SHAPE:
-                    if (Flags.newCustomizationPickerUi()) {
+                    if (Flags.newCustomizationPickerUi()
+                            && com.android.launcher3.Flags.enableLauncherIconShapes()) {
                         String shapeKey = message.getData().getString(KEY_SHAPE_KEY);
-                        Optional<AppShape> optionalShape = AppShapesProvider.INSTANCE.getShapes()
-                                .stream()
-                                .filter(shape -> shape.getKey().equals(shapeKey))
-                                .findFirst();
-                        String pathToSet = optionalShape.map(AppShape::getPath).orElse(null);
-                        // TODO (b/348664593): Update launcher preview with the given shape
+                        if (!TextUtils.isEmpty(shapeKey)) {
+                            renderer.updateShape(shapeKey);
+                        }
                     }
                     break;
                 case MESSAGE_ID_UPDATE_GRID:
diff --git a/src/com/android/launcher3/graphics/IconShape.java b/src/com/android/launcher3/graphics/IconShape.java
deleted file mode 100644
index cb14587..0000000
--- a/src/com/android/launcher3/graphics/IconShape.java
+++ /dev/null
@@ -1,482 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.graphics;
-
-import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.FloatArrayEvaluator;
-import android.animation.ValueAnimator;
-import android.animation.ValueAnimator.AnimatorUpdateListener;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.Region.Op;
-import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.util.AttributeSet;
-import android.util.Xml;
-import android.view.View;
-import android.view.ViewOutlineProvider;
-
-import com.android.launcher3.R;
-import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
-import com.android.launcher3.dagger.ApplicationContext;
-import com.android.launcher3.dagger.LauncherAppSingleton;
-import com.android.launcher3.dagger.LauncherBaseAppComponent;
-import com.android.launcher3.icons.GraphicsUtils;
-import com.android.launcher3.icons.IconNormalizer;
-import com.android.launcher3.util.DaggerSingletonObject;
-import com.android.launcher3.views.ClipPathView;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.inject.Inject;
-
-/**
- * Abstract representation of the shape of an icon shape
- */
-@LauncherAppSingleton
-public final class IconShape {
-
-    public static DaggerSingletonObject<IconShape> INSTANCE =
-            new DaggerSingletonObject<>(LauncherBaseAppComponent::getIconShape);
-
-    private ShapeDelegate mDelegate = new Circle();
-    private float mNormalizationScale = ICON_VISIBLE_AREA_FACTOR;
-
-    @Inject
-    public IconShape(@ApplicationContext Context context) {
-        pickBestShape(context);
-    }
-
-    public ShapeDelegate getShape() {
-        return mDelegate;
-    }
-
-    public float getNormalizationScale() {
-        return mNormalizationScale;
-    }
-
-    /**
-     * Initializes the shape which is closest to the {@link AdaptiveIconDrawable}
-     */
-    public void pickBestShape(Context context) {
-        // Pick any large size
-        final int size = 200;
-
-        Region full = new Region(0, 0, size, size);
-        Region iconR = new Region();
-        AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
-                new ColorDrawable(Color.BLACK), new ColorDrawable(Color.BLACK));
-        drawable.setBounds(0, 0, size, size);
-        iconR.setPath(drawable.getIconMask(), full);
-
-        Path shapePath = new Path();
-        Region shapeR = new Region();
-
-        // Find the shape with minimum area of divergent region.
-        int minArea = Integer.MAX_VALUE;
-        ShapeDelegate closestShape = null;
-        for (ShapeDelegate shape : getAllShapes(context)) {
-            shapePath.reset();
-            shape.addToPath(shapePath, 0, 0, size / 2f);
-            shapeR.setPath(shapePath, full);
-            shapeR.op(iconR, Op.XOR);
-
-            int area = GraphicsUtils.getArea(shapeR);
-            if (area < minArea) {
-                minArea = area;
-                closestShape = shape;
-            }
-        }
-
-        if (closestShape != null) {
-            mDelegate = closestShape;
-        }
-
-        // Initialize shape properties
-        mNormalizationScale = IconNormalizer.normalizeAdaptiveIcon(drawable, size, null);
-    }
-
-
-
-    public interface ShapeDelegate {
-
-        default boolean enableShapeDetection() {
-            return false;
-        }
-
-        void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint paint);
-
-        void addToPath(Path path, float offsetX, float offsetY, float radius);
-
-        <T extends View & ClipPathView> ValueAnimator createRevealAnimator(T target,
-                Rect startRect, Rect endRect, float endRadius, boolean isReversed);
-    }
-
-    /**
-     * Abstract shape where the reveal animation is a derivative of a round rect animation
-     */
-    private static abstract class SimpleRectShape implements ShapeDelegate {
-
-        @Override
-        public final <T extends View & ClipPathView> ValueAnimator createRevealAnimator(T target,
-                Rect startRect, Rect endRect, float endRadius, boolean isReversed) {
-            return new RoundedRectRevealOutlineProvider(
-                    getStartRadius(startRect), endRadius, startRect, endRect) {
-                @Override
-                public boolean shouldRemoveElevationDuringAnimation() {
-                    return true;
-                }
-            }.createRevealAnimator(target, isReversed);
-        }
-
-        protected abstract float getStartRadius(Rect startRect);
-    }
-
-    /**
-     * Abstract shape which draws using {@link Path}
-     */
-    private static abstract class PathShape implements ShapeDelegate {
-
-        private final Path mTmpPath = new Path();
-
-        @Override
-        public final void drawShape(Canvas canvas, float offsetX, float offsetY, float radius,
-                Paint paint) {
-            mTmpPath.reset();
-            addToPath(mTmpPath, offsetX, offsetY, radius);
-            canvas.drawPath(mTmpPath, paint);
-        }
-
-        protected abstract AnimatorUpdateListener newUpdateListener(
-                Rect startRect, Rect endRect, float endRadius, Path outPath);
-
-        @Override
-        public final <T extends View & ClipPathView> ValueAnimator createRevealAnimator(T target,
-                Rect startRect, Rect endRect, float endRadius, boolean isReversed) {
-            Path path = new Path();
-            AnimatorUpdateListener listener =
-                    newUpdateListener(startRect, endRect, endRadius, path);
-
-            ValueAnimator va =
-                    isReversed ? ValueAnimator.ofFloat(1f, 0f) : ValueAnimator.ofFloat(0f, 1f);
-            va.addListener(new AnimatorListenerAdapter() {
-                private ViewOutlineProvider mOldOutlineProvider;
-
-                public void onAnimationStart(Animator animation) {
-                    mOldOutlineProvider = target.getOutlineProvider();
-                    target.setOutlineProvider(null);
-
-                    target.setTranslationZ(-target.getElevation());
-                }
-
-                public void onAnimationEnd(Animator animation) {
-                    target.setTranslationZ(0);
-                    target.setClipPath(null);
-                    target.setOutlineProvider(mOldOutlineProvider);
-                }
-            });
-
-            va.addUpdateListener((anim) -> {
-                path.reset();
-                listener.onAnimationUpdate(anim);
-                target.setClipPath(path);
-            });
-
-            return va;
-        }
-    }
-
-    public static final class Circle extends PathShape {
-
-        private final float[] mTempRadii = new float[8];
-
-        protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect,
-                float endRadius, Path outPath) {
-            float r1 = getStartRadius(startRect);
-
-            float[] startValues = new float[] {
-                    startRect.left, startRect.top, startRect.right, startRect.bottom, r1, r1};
-            float[] endValues = new float[] {
-                    endRect.left, endRect.top, endRect.right, endRect.bottom, endRadius, endRadius};
-
-            FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[6]);
-
-            return (anim) -> {
-                float progress = (Float) anim.getAnimatedValue();
-                float[] values = evaluator.evaluate(progress, startValues, endValues);
-                outPath.addRoundRect(
-                        values[0], values[1], values[2], values[3],
-                        getRadiiArray(values[4], values[5]), Path.Direction.CW);
-            };
-        }
-
-        private float[] getRadiiArray(float r1, float r2) {
-            mTempRadii[0] = mTempRadii [1] = mTempRadii[2] = mTempRadii[3] =
-                    mTempRadii[6] = mTempRadii[7] = r1;
-            mTempRadii[4] = mTempRadii[5] = r2;
-            return mTempRadii;
-        }
-
-
-        @Override
-        public void addToPath(Path path, float offsetX, float offsetY, float radius) {
-            path.addCircle(radius + offsetX, radius + offsetY, radius, Path.Direction.CW);
-        }
-
-        protected float getStartRadius(Rect startRect) {
-            return startRect.width() / 2f;
-        }
-
-        @Override
-        public boolean enableShapeDetection() {
-            return true;
-        }
-    }
-
-    private static class RoundedSquare extends SimpleRectShape {
-
-        /**
-         * Ratio of corner radius to half size.
-         */
-        private final float mRadiusRatio;
-
-        public RoundedSquare(float radiusRatio) {
-            mRadiusRatio = radiusRatio;
-        }
-
-        @Override
-        public void drawShape(Canvas canvas, float offsetX, float offsetY, float radius, Paint p) {
-            float cx = radius + offsetX;
-            float cy = radius + offsetY;
-            float cr = radius * mRadiusRatio;
-            canvas.drawRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, p);
-        }
-
-        @Override
-        public void addToPath(Path path, float offsetX, float offsetY, float radius) {
-            float cx = radius + offsetX;
-            float cy = radius + offsetY;
-            float cr = radius * mRadiusRatio;
-            path.addRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr,
-                    Path.Direction.CW);
-        }
-
-        @Override
-        protected float getStartRadius(Rect startRect) {
-            return (startRect.width() / 2f) * mRadiusRatio;
-        }
-    }
-
-    private static class TearDrop extends PathShape {
-
-        /**
-         * Radio of short radius to large radius, based on the shape options defined in the config.
-         */
-        private final float mRadiusRatio;
-        private final float[] mTempRadii = new float[8];
-
-        public TearDrop(float radiusRatio) {
-            mRadiusRatio = radiusRatio;
-        }
-
-        @Override
-        public void addToPath(Path p, float offsetX, float offsetY, float r1) {
-            float r2 = r1 * mRadiusRatio;
-            float cx = r1 + offsetX;
-            float cy = r1 + offsetY;
-
-            p.addRoundRect(cx - r1, cy - r1, cx + r1, cy + r1, getRadiiArray(r1, r2),
-                    Path.Direction.CW);
-        }
-
-        private float[] getRadiiArray(float r1, float r2) {
-            mTempRadii[0] = mTempRadii [1] = mTempRadii[2] = mTempRadii[3] =
-                    mTempRadii[6] = mTempRadii[7] = r1;
-            mTempRadii[4] = mTempRadii[5] = r2;
-            return mTempRadii;
-        }
-
-        @Override
-        protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect,
-                float endRadius, Path outPath) {
-            float r1 = startRect.width() / 2f;
-            float r2 = r1 * mRadiusRatio;
-
-            float[] startValues = new float[] {
-                    startRect.left, startRect.top, startRect.right, startRect.bottom, r1, r2};
-            float[] endValues = new float[] {
-                    endRect.left, endRect.top, endRect.right, endRect.bottom, endRadius, endRadius};
-
-            FloatArrayEvaluator evaluator = new FloatArrayEvaluator(new float[6]);
-
-            return (anim) -> {
-                float progress = (Float) anim.getAnimatedValue();
-                float[] values = evaluator.evaluate(progress, startValues, endValues);
-                outPath.addRoundRect(
-                        values[0], values[1], values[2], values[3],
-                        getRadiiArray(values[4], values[5]), Path.Direction.CW);
-            };
-        }
-    }
-
-    private static class Squircle extends PathShape {
-
-        /**
-         * Radio of radius to circle radius, based on the shape options defined in the config.
-         */
-        private final float mRadiusRatio;
-
-        public Squircle(float radiusRatio) {
-            mRadiusRatio = radiusRatio;
-        }
-
-        @Override
-        public void addToPath(Path p, float offsetX, float offsetY, float r) {
-            float cx = r + offsetX;
-            float cy = r + offsetY;
-            float control = r - r * mRadiusRatio;
-
-            p.moveTo(cx, cy - r);
-            addLeftCurve(cx, cy, r, control, p);
-            addRightCurve(cx, cy, r, control, p);
-            addLeftCurve(cx, cy, -r, -control, p);
-            addRightCurve(cx, cy, -r, -control, p);
-            p.close();
-        }
-
-        private void addLeftCurve(float cx, float cy, float r, float control, Path path) {
-            path.cubicTo(
-                    cx - control, cy - r,
-                    cx - r, cy - control,
-                    cx - r, cy);
-        }
-
-        private void addRightCurve(float cx, float cy, float r, float control, Path path) {
-            path.cubicTo(
-                    cx - r, cy + control,
-                    cx - control, cy + r,
-                    cx, cy + r);
-        }
-
-        @Override
-        protected AnimatorUpdateListener newUpdateListener(Rect startRect, Rect endRect,
-                float endR, Path outPath) {
-
-            float startCX = startRect.exactCenterX();
-            float startCY = startRect.exactCenterY();
-            float startR = startRect.width() / 2f;
-            float startControl = startR - startR * mRadiusRatio;
-            float startHShift = 0;
-            float startVShift = 0;
-
-            float endCX = endRect.exactCenterX();
-            float endCY = endRect.exactCenterY();
-            // Approximate corner circle using bezier curves
-            // http://spencermortensen.com/articles/bezier-circle/
-            float endControl = endR * 0.551915024494f;
-            float endHShift = endRect.width() / 2f - endR;
-            float endVShift = endRect.height() / 2f - endR;
-
-            return (anim) -> {
-                float progress = (Float) anim.getAnimatedValue();
-
-                float cx = (1 - progress) * startCX + progress * endCX;
-                float cy = (1 - progress) * startCY + progress * endCY;
-                float r = (1 - progress) * startR + progress * endR;
-                float control = (1 - progress) * startControl + progress * endControl;
-                float hShift = (1 - progress) * startHShift + progress * endHShift;
-                float vShift = (1 - progress) * startVShift + progress * endVShift;
-
-                outPath.moveTo(cx, cy - vShift - r);
-                outPath.rLineTo(-hShift, 0);
-
-                addLeftCurve(cx - hShift, cy - vShift, r, control, outPath);
-                outPath.rLineTo(0, vShift + vShift);
-
-                addRightCurve(cx - hShift, cy + vShift, r, control, outPath);
-                outPath.rLineTo(hShift + hShift, 0);
-
-                addLeftCurve(cx + hShift, cy + vShift, -r, -control, outPath);
-                outPath.rLineTo(0, -vShift - vShift);
-
-                addRightCurve(cx + hShift, cy - vShift, -r, -control, outPath);
-                outPath.close();
-            };
-        }
-    }
-
-    private static ShapeDelegate getShapeDefinition(String type, float radius) {
-        switch (type) {
-            case "Circle":
-                return new Circle();
-            case "RoundedSquare":
-                return new RoundedSquare(radius);
-            case "TearDrop":
-                return new TearDrop(radius);
-            case "Squircle":
-                return new Squircle(radius);
-            default:
-                throw new IllegalArgumentException("Invalid shape type: " + type);
-        }
-    }
-
-    private static List<ShapeDelegate> getAllShapes(Context context) {
-        ArrayList<ShapeDelegate> result = new ArrayList<>();
-        try (XmlResourceParser parser = context.getResources().getXml(R.xml.folder_shapes)) {
-
-            // Find the root tag
-            int type;
-            while ((type = parser.next()) != XmlPullParser.END_TAG
-                    && type != XmlPullParser.END_DOCUMENT
-                    && !"shapes".equals(parser.getName()));
-
-            final int depth = parser.getDepth();
-            int[] radiusAttr = new int[] {R.attr.folderIconRadius};
-
-            while (((type = parser.next()) != XmlPullParser.END_TAG ||
-                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
-
-                if (type == XmlPullParser.START_TAG) {
-                    AttributeSet attrs = Xml.asAttributeSet(parser);
-                    TypedArray a = context.obtainStyledAttributes(attrs, radiusAttr);
-                    ShapeDelegate shape = getShapeDefinition(parser.getName(), a.getFloat(0, 1));
-                    a.recycle();
-
-                    result.add(shape);
-                }
-            }
-        } catch (IOException | XmlPullParserException e) {
-            throw new RuntimeException(e);
-        }
-        return result;
-    }
-
-}
diff --git a/src/com/android/launcher3/graphics/LauncherCustomizationProvider.kt b/src/com/android/launcher3/graphics/LauncherCustomizationProvider.kt
new file mode 100644
index 0000000..c949e2e
--- /dev/null
+++ b/src/com/android/launcher3/graphics/LauncherCustomizationProvider.kt
@@ -0,0 +1,29 @@
+/*
+ * 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.graphics
+
+import android.content.Context
+import android.net.Uri
+import com.android.launcher3.dagger.LauncherComponentProvider.appComponent
+import com.android.launcher3.util.ContentProviderProxy
+
+/** Provider for various Launcher customizations exposed via a ContentProvider API */
+class LauncherCustomizationProvider : ContentProviderProxy() {
+
+    override fun getProxy(ctx: Context): ProxyProvider? = ctx.appComponent.gridCustomizationsProxy
+
+    override fun getType(uri: Uri) = "vnd.android.cursor.dir/launcher_grid"
+}
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index f0e4fc4..b80238c 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -24,10 +24,12 @@
 import static com.android.launcher3.BubbleTextView.DISPLAY_WORKSPACE;
 import static com.android.launcher3.DeviceProfile.DEFAULT_SCALE;
 import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_PREVIEW_RENDERER;
+import static com.android.launcher3.LauncherPrefs.GRID_NAME;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
 import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
-import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
-import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks;
+import static com.android.launcher3.graphics.ThemeManager.PREF_ICON_SHAPE;
+import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
 
 import android.app.Fragment;
 import android.app.WallpaperColors;
@@ -36,7 +38,6 @@
 import android.appwidget.AppWidgetHostView;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.Context;
-import android.content.ContextWrapper;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.PointF;
@@ -62,12 +63,12 @@
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.InsettableFrameLayout;
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.ProxyPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.WorkspaceLayoutManager;
@@ -75,6 +76,9 @@
 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
 import com.android.launcher3.celllayout.CellPosMapper;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppModule;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
@@ -84,11 +88,13 @@
 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.util.BaseContext;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
+import com.android.launcher3.util.SandboxContext;
+import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.WindowBounds;
 import com.android.launcher3.util.window.WindowManagerProxy;
 import com.android.launcher3.views.ActivityContext;
@@ -100,12 +106,17 @@
 import com.android.launcher3.widget.util.WidgetSizes;
 import com.android.systemui.shared.Flags;
 
-import java.util.ArrayList;
+import dagger.BindsInstance;
+import dagger.Component;
+
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 
 /**
  * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile.
@@ -115,7 +126,7 @@
  *   3) Place appropriate elements like icons and first-page qsb
  *   4) Measure and draw the view on a canvas
  */
-public class LauncherPreviewRenderer extends ContextWrapper
+public class LauncherPreviewRenderer extends BaseContext
         implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 {
 
     /**
@@ -124,15 +135,26 @@
      */
     public static class PreviewContext extends SandboxContext {
 
-        public PreviewContext(Context base, InvariantDeviceProfile idp) {
+        private final String mPrefName;
+
+        public PreviewContext(Context base, String gridName, String shapeKey) {
             super(base);
-            putObject(InvariantDeviceProfile.INSTANCE, idp);
-            putObject(LauncherAppState.INSTANCE,
-                    new LauncherAppState(this, null /* iconCacheFileName */));
+            mPrefName = "preview-" + UUID.randomUUID().toString();
+            LauncherPrefs prefs =
+                    new ProxyPrefs(this, getSharedPreferences(mPrefName, MODE_PRIVATE));
+            prefs.put(GRID_NAME, gridName);
+            prefs.put(PREF_ICON_SHAPE, shapeKey);
+            initDaggerComponent(
+                    DaggerLauncherPreviewRenderer_PreviewAppComponent.builder().bindPrefs(prefs));
+        }
+
+        @Override
+        protected void cleanUpObjects() {
+            super.cleanUpObjects();
+            deleteSharedPreferences(mPrefName);
         }
     }
 
-    private final List<OnDeviceProfileChangeListener> mDpChangeListeners = new ArrayList<>();
     private final Handler mUiHandler;
     private final Context mContext;
     private final InvariantDeviceProfile mIdp;
@@ -160,7 +182,7 @@
             WallpaperColors wallpaperColorsOverride,
             @Nullable final SparseArray<Size> launcherWidgetSpanInfo) {
 
-        super(context);
+        super(context, Themes.getActivityThemeRes(context));
         mUiHandler = new Handler(Looper.getMainLooper());
         mContext = context;
         mIdp = idp;
@@ -168,8 +190,8 @@
                 this::getAppWidgetScale).build();
         if (context instanceof PreviewContext) {
             Context tempContext = ((PreviewContext) context).getBaseContext();
-            mDpOrig = new InvariantDeviceProfile(tempContext, InvariantDeviceProfile
-                    .getCurrentGridName(tempContext)).getDeviceProfile(tempContext)
+            mDpOrig = InvariantDeviceProfile.INSTANCE.get(tempContext)
+                    .getDeviceProfile(tempContext)
                     .copy(tempContext);
         } else {
             mDpOrig = mDp;
@@ -239,6 +261,13 @@
                     : null;
         }
         mAppWidgetHost = new LauncherPreviewAppWidgetHost(context);
+
+        onViewCreated();
+    }
+
+    @Override
+    public InsettableFrameLayout getRootView() {
+        return mRootView;
     }
 
     /**
@@ -325,11 +354,6 @@
     }
 
     @Override
-    public List<OnDeviceProfileChangeListener> getOnDeviceProfileChangeListeners() {
-        return mDpChangeListeners;
-    }
-
-    @Override
     public Hotseat getHotseat() {
         return mHotseat;
     }
@@ -456,54 +480,48 @@
 
     private void populate(BgDataModel dataModel,
             Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
-        // Separate the items that are on the current screen, and the other remaining items.
-        ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
-        ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
-        ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
-        ArrayList<LauncherAppWidgetInfo> otherAppWidgets = new ArrayList<>();
+        IntSet missingHotseatRank = new IntSet();
+        IntStream.range(0, mDp.numShownHotseatIcons).forEach(missingHotseatRank::add);
 
-        IntSet currentScreenIds = IntSet.wrap(mWorkspaceScreens.keySet());
-        filterCurrentWorkspaceItems(currentScreenIds, dataModel.workspaceItems,
-                currentWorkspaceItems, otherWorkspaceItems);
-        filterCurrentWorkspaceItems(currentScreenIds, dataModel.appWidgets, currentAppWidgets,
-                otherAppWidgets);
-        for (ItemInfo itemInfo : currentWorkspaceItems) {
-            switch (itemInfo.itemType) {
-                case Favorites.ITEM_TYPE_APPLICATION:
-                case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                    inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
-                    break;
-                case Favorites.ITEM_TYPE_FOLDER:
-                case Favorites.ITEM_TYPE_APP_PAIR:
-                    inflateAndAddCollectionIcon((CollectionInfo) itemInfo);
-                    break;
-                default:
-                    break;
-            }
-        }
-        Map<ComponentKey, AppWidgetProviderInfo> widgetsMap = widgetProviderInfoMap;
-        for (ItemInfo itemInfo : currentAppWidgets) {
-            switch (itemInfo.itemType) {
-                case Favorites.ITEM_TYPE_APPWIDGET:
-                case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
-                    if (widgetsMap == null) {
-                        widgetsMap = dataModel.widgetsModel.getWidgetsByComponentKey()
-                                .entrySet()
-                                .stream()
-                                .filter(entry -> entry.getValue().widgetInfo != null)
-                                .collect(Collectors.toMap(
-                                        Map.Entry::getKey,
-                                        entry -> entry.getValue().widgetInfo
-                                ));
+        Map<ComponentKey, AppWidgetProviderInfo>[] widgetsMap = new Map[] { widgetProviderInfoMap};
+
+        // Separate the items that are on the current screen, and the other remaining items.
+        dataModel.itemsIdMap.stream()
+                .filter(currentScreenContentFilter(IntSet.wrap(mWorkspaceScreens.keySet())))
+                .forEach(itemInfo -> {
+                    switch (itemInfo.itemType) {
+                        case Favorites.ITEM_TYPE_APPLICATION:
+                        case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                            inflateAndAddIcon((WorkspaceItemInfo) itemInfo);
+                            break;
+                        case Favorites.ITEM_TYPE_FOLDER:
+                        case Favorites.ITEM_TYPE_APP_PAIR:
+                            inflateAndAddCollectionIcon((CollectionInfo) itemInfo);
+                            break;
+                        case Favorites.ITEM_TYPE_APPWIDGET:
+                        case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
+                            if (widgetsMap[0] == null) {
+                                widgetsMap[0] = dataModel.widgetsModel.getWidgetsByComponentKey()
+                                        .entrySet()
+                                        .stream()
+                                        .filter(entry -> entry.getValue().widgetInfo != null)
+                                        .collect(Collectors.toMap(
+                                                Entry::getKey,
+                                                entry -> entry.getValue().widgetInfo
+                                        ));
+                            }
+                            inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, widgetsMap[0]);
+                            break;
+                        default:
+                            break;
                     }
-                    inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, widgetsMap);
-                    break;
-                default:
-                    break;
-            }
-        }
-        IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems,
-                mDp.numShownHotseatIcons);
+
+                    if (itemInfo.container == CONTAINER_HOTSEAT) {
+                        missingHotseatRank.remove(itemInfo.screenId);
+                    }
+                });
+
+        IntArray ranks = missingHotseatRank.getArray();
         FixedContainerItems hotseatPredictions =
                 dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
         List<ItemInfo> predictions = hotseatPredictions == null
@@ -582,4 +600,16 @@
             return true;
         }
     }
+
+    @LauncherAppSingleton
+    @Component(modules = LauncherAppModule.class)
+    public interface PreviewAppComponent extends LauncherAppComponent {
+
+        /** Builder for NexusLauncherAppComponent. */
+        @Component.Builder
+        interface Builder extends LauncherAppComponent.Builder {
+            @BindsInstance Builder bindPrefs(LauncherPrefs prefs);
+            PreviewAppComponent build();
+        }
+    }
 }
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index 9fffcc1..3bd9fb5 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -24,7 +24,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
 import android.graphics.Paint;
@@ -33,14 +32,15 @@
 import android.graphics.Rect;
 import android.util.Property;
 
+import androidx.annotation.VisibleForTesting;
 import androidx.core.graphics.ColorUtils;
 
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatedFloat;
 import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.FastBitmapDrawable;
-import com.android.launcher3.icons.GraphicsUtils;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.util.Themes;
 
@@ -64,8 +64,6 @@
 
     private static final int DEFAULT_PATH_SIZE = 100;
     private static final int MAX_PAINT_ALPHA = 255;
-    private static final int TRACK_ALPHA = (int) (0.27f * MAX_PAINT_ALPHA);
-    private static final int DISABLED_ICON_ALPHA = (int) (0.6f * MAX_PAINT_ALPHA);
 
     private static final long DURATION_SCALE = 500;
     private static final long SCALE_AND_ALPHA_ANIM_DURATION = 500;
@@ -121,7 +119,8 @@
                 IconPalette.getPreloadProgressColor(context, info.bitmap.color),
                 getPreloadColors(context),
                 Utilities.isDarkTheme(context),
-                GraphicsUtils.getShapePath(context, DEFAULT_PATH_SIZE));
+                ThemeManager.INSTANCE.get(context).getIconShape().getPath(DEFAULT_PATH_SIZE)
+        );
     }
 
     public PreloadIconDrawable(
@@ -284,20 +283,25 @@
                     (long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE));
             mCurrentAnim.setInterpolator(LINEAR);
             if (isFinish) {
-                if (onFinishCallback != null) {
-                    mCurrentAnim.addListener(AnimatorListeners.forEndCallback(onFinishCallback));
-                }
                 mCurrentAnim.addListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
                         mRanFinishAnimation = true;
                     }
                 });
+                if (onFinishCallback != null) {
+                    mCurrentAnim.addListener(AnimatorListeners.forEndCallback(onFinishCallback));
+                }
             }
             mCurrentAnim.start();
         }
     }
 
+    @VisibleForTesting
+    public ObjectAnimator getActiveAnimation() {
+        return mCurrentAnim;
+    }
+
     /**
      * Sets the internal progress and updates the UI accordingly
      *   for progress <= 0:
@@ -358,8 +362,7 @@
     @Override
     public FastBitmapConstantState newConstantState() {
         return new PreloadIconConstantState(
-                mBitmap,
-                mIconColor,
+                mBitmapInfo,
                 mItem,
                 mIndicatorColor,
                 new int[] {mSystemAccentColor, mSystemBackgroundColor},
@@ -377,14 +380,13 @@
         private final Path mShapePath;
 
         public PreloadIconConstantState(
-                Bitmap bitmap,
-                int iconColor,
+                BitmapInfo bitmapInfo,
                 ItemInfoWithIcon info,
                 int indicatorColor,
                 int[] preloadColors,
                 boolean isDarkMode,
                 Path shapePath) {
-            super(bitmap, iconColor);
+            super(bitmapInfo);
             mInfo = info;
             mIndicatorColor = indicatorColor;
             mPreloadColors = preloadColors;
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index a7cf1a7..d425f03 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -20,7 +20,9 @@
 import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.launcher3.LauncherPrefs.GRID_NAME;
 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.graphics.ThemeManager.PREF_ICON_SHAPE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
@@ -38,6 +40,7 @@
 import android.util.SparseIntArray;
 import android.view.ContextThemeWrapper;
 import android.view.Display;
+import android.view.Surface;
 import android.view.SurfaceControlViewHost;
 import android.view.SurfaceControlViewHost.SurfacePackage;
 import android.view.View;
@@ -52,16 +55,15 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
 import com.android.launcher3.model.BaseLauncherBinder;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.BgDataModel.Callbacks;
-import com.android.launcher3.model.GridSizeMigrationDBController;
 import com.android.launcher3.model.LoaderTask;
 import com.android.launcher3.model.ModelDbController;
-import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.Themes;
@@ -77,9 +79,7 @@
 public class PreviewSurfaceRenderer {
 
     private static final String TAG = "PreviewSurfaceRenderer";
-
     private static final int FADE_IN_ANIMATION_DURATION = 200;
-
     private static final String KEY_HOST_TOKEN = "host_token";
     private static final String KEY_VIEW_WIDTH = "width";
     private static final String KEY_VIEW_HEIGHT = "height";
@@ -88,26 +88,29 @@
     private static final String KEY_COLOR_RESOURCE_IDS = "color_resource_ids";
     private static final String KEY_COLOR_VALUES = "color_values";
     private static final String KEY_DARK_MODE = "use_dark_mode";
+    public static final String KEY_SKIP_ANIMATIONS = "skip_animations";
 
-    private Context mContext;
-    private final IBinder mHostToken;
-    private final int mWidth;
-    private final int mHeight;
-    private String mGridName;
-
-    private final int mDisplayId;
-    private final Display mDisplay;
-    private final WallpaperColors mWallpaperColors;
+    private final Context mContext;
     private SparseIntArray mPreviewColorOverride;
+    private String mGridName;
+    private String mShapeKey;
+
     @Nullable private Boolean mDarkMode;
-    private final RunnableList mLifeCycleTracker;
-
-    private final SurfaceControlViewHost mSurfaceControlViewHost;
-
     private boolean mDestroyed = false;
     private boolean mHideQsb;
     @Nullable private FrameLayout mViewRoot = null;
 
+    private final IBinder mHostToken;
+    private final int mWidth;
+    private final int mHeight;
+    private final boolean mSkipAnimations;
+    private final int mDisplayId;
+    private final Display mDisplay;
+    private final WallpaperColors mWallpaperColors;
+    private final RunnableList mLifeCycleTracker;
+    private final SurfaceControlViewHost mSurfaceControlViewHost;
+
+
     public PreviewSurfaceRenderer(
             Context context, RunnableList lifecycleTracker, Bundle bundle) throws Exception {
         mContext = context;
@@ -115,17 +118,19 @@
         mGridName = bundle.getString("name");
         bundle.remove("name");
         if (mGridName == null) {
-            mGridName = InvariantDeviceProfile.getCurrentGridName(context);
+            mGridName = LauncherPrefs.get(context).get(GRID_NAME);
         }
+        mShapeKey = LauncherPrefs.get(context).get(PREF_ICON_SHAPE);
         mWallpaperColors = bundle.getParcelable(KEY_COLORS);
         if (Flags.newCustomizationPickerUi()) {
             updateColorOverrides(bundle);
         }
-        mHideQsb = bundle.getBoolean(GridCustomizationsProvider.KEY_HIDE_BOTTOM_ROW);
+        mHideQsb = bundle.getBoolean(GridCustomizationsProxy.KEY_HIDE_BOTTOM_ROW);
 
         mHostToken = bundle.getBinder(KEY_HOST_TOKEN);
         mWidth = bundle.getInt(KEY_VIEW_WIDTH);
         mHeight = bundle.getInt(KEY_VIEW_HEIGHT);
+        mSkipAnimations = bundle.getBoolean(KEY_SKIP_ANIMATIONS, false);
         mDisplayId = bundle.getInt(KEY_DISPLAY_ID);
         mDisplay = context.getSystemService(DisplayManager.class)
                 .getDisplay(mDisplayId);
@@ -134,10 +139,10 @@
         }
 
         mSurfaceControlViewHost = MAIN_EXECUTOR.submit(() -> new MySurfaceControlViewHost(
-                mContext,
-                context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY),
-                mHostToken,
-                mLifeCycleTracker))
+                        mContext,
+                        context.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY),
+                        mHostToken,
+                        mLifeCycleTracker))
                 .get(5, TimeUnit.SECONDS);
         mLifeCycleTracker.add(this::destroy);
     }
@@ -218,6 +223,20 @@
     }
 
     /**
+     * Update the shapes of the launcher preview
+     *
+     * @param shapeKey key for the IconShape model
+     */
+    public void updateShape(String shapeKey) {
+        if (shapeKey.equals(mShapeKey)) {
+            Log.w(TAG, "Preview shape already set, skipping. shape=" + mShapeKey);
+            return;
+        }
+        mShapeKey = shapeKey;
+        loadAsync();
+    }
+
+    /**
      * Hides the components in the bottom row.
      *
      * @param hide True to hide and false to show.
@@ -270,6 +289,20 @@
             }
             context = context.createConfigurationContext(configuration);
         }
+        if (InvariantDeviceProfile.INSTANCE.get(context).isFixedLandscape) {
+            Configuration configuration = new Configuration(
+                    context.getResources().getConfiguration()
+            );
+            int width = configuration.screenWidthDp;
+            int height = configuration.screenHeightDp;
+            if (configuration.screenHeightDp > configuration.screenWidthDp) {
+                configuration.screenWidthDp = height;
+                configuration.screenHeightDp = width;
+                configuration.orientation = Surface.ROTATION_90;
+            }
+            context = context.createConfigurationContext(configuration);
+        }
+
         if (Flags.newCustomizationPickerUi()) {
             if (mPreviewColorOverride != null) {
                 LocalColorExtractor.newInstance(context)
@@ -300,20 +333,11 @@
     @WorkerThread
     private void loadModelData() {
         final Context inflationContext = getPreviewContext();
-        final InvariantDeviceProfile idp = new InvariantDeviceProfile(inflationContext, mGridName);
-        if (GridSizeMigrationDBController.needsToMigrate(inflationContext, idp)) {
+        if (!mGridName.equals(LauncherPrefs.INSTANCE.get(mContext).get(GRID_NAME))
+                || !mShapeKey.equals(LauncherPrefs.INSTANCE.get(mContext).get(PREF_ICON_SHAPE))) {
             // Start the migration
-            PreviewContext previewContext = new PreviewContext(inflationContext, idp);
-            // Copy existing data to preview DB
-            LauncherDbUtils.copyTable(LauncherAppState.getInstance(mContext)
-                            .getModel().getModelDbController().getDb(),
-                    TABLE_NAME,
-                    LauncherAppState.getInstance(previewContext)
-                            .getModel().getModelDbController().getDb(),
-                    TABLE_NAME,
-                    mContext);
-            LauncherAppState.getInstance(previewContext)
-                    .getModel().getModelDbController().clearEmptyDbFlag();
+            PreviewContext previewContext =
+                    new PreviewContext(inflationContext, mGridName, mShapeKey);
 
             BgDataModel bgModel = new BgDataModel();
             new LoaderTask(
@@ -322,12 +346,11 @@
                     bgModel,
                     LauncherAppState.getInstance(previewContext).getModel().getModelDelegate(),
                     new BaseLauncherBinder(LauncherAppState.getInstance(previewContext), bgModel,
-                            /* bgAllAppsList= */ null, new Callbacks[0]),
-                    LauncherAppState.getInstance(
-                            previewContext).getModel().getWidgetsFilterDataProvider()) {
+                            /* bgAllAppsList= */ null, new Callbacks[0])) {
 
                 @Override
                 public void run() {
+                    InvariantDeviceProfile idp = LauncherAppState.getIDP(previewContext);
                     DeviceProfile deviceProfile = idp.getDeviceProfile(previewContext);
                     String query =
                             LauncherSettings.Favorites.SCREEN + " = " + Workspace.FIRST_SCREEN_ID
@@ -351,7 +374,7 @@
             LauncherAppState.getInstance(inflationContext).getModel().loadAsync(dataModel -> {
                 if (dataModel != null) {
                     MAIN_EXECUTOR.execute(() -> renderView(inflationContext, dataModel, null,
-                            null, idp));
+                            null, LauncherAppState.getIDP(inflationContext)));
                 } else {
                     Log.e(TAG, "Model loading failed");
                 }
@@ -376,17 +399,30 @@
         }
         renderer.hideBottomRow(mHideQsb);
         View view = renderer.getRenderedView(dataModel, widgetProviderInfoMap);
-        // This aspect scales the view to fit in the surface and centers it
-        final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
-                mHeight / (float) view.getMeasuredHeight());
-        view.setScaleX(scale);
-        view.setScaleY(scale);
+
         view.setPivotX(0);
         view.setPivotY(0);
-        view.setTranslationX((mWidth - scale * view.getWidth()) / 2);
-        view.setTranslationY((mHeight - scale * view.getHeight()) / 2);
+        if (idp.isFixedLandscape) {
+            final float scale = Math.min(mHeight / (float) view.getMeasuredWidth(),
+                    mWidth / (float) view.getMeasuredHeight());
+            view.setScaleX(scale);
+            view.setScaleY(scale);
+            view.setRotation(90);
+            view.setTranslationX((mHeight - scale * view.getWidth()) / 2 + mWidth);
+            view.setTranslationY((mWidth - scale * view.getHeight()) / 2);
+        } else {
+            // This aspect scales the view to fit in the surface and centers it
+            final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(),
+                    mHeight / (float) view.getMeasuredHeight());
+            view.setScaleX(scale);
+            view.setScaleY(scale);
+            view.setTranslationX((mWidth - scale * view.getWidth()) / 2);
+            view.setTranslationY((mHeight - scale * view.getHeight()) / 2);
+        }
+
+
         if (!Flags.newCustomizationPickerUi()) {
-            view.setAlpha(0);
+            view.setAlpha(mSkipAnimations ? 1 : 0);
             view.animate().alpha(1)
                     .setInterpolator(new AccelerateDecelerateInterpolator())
                     .setDuration(FADE_IN_ANIMATION_DURATION)
@@ -407,7 +443,7 @@
             );
             mViewRoot.setLayoutParams(layoutParams);
             mViewRoot.addView(view);
-            mViewRoot.setAlpha(0);
+            mViewRoot.setAlpha(mSkipAnimations ? 1 : 0);
             mViewRoot.animate().alpha(1)
                     .setInterpolator(new AccelerateDecelerateInterpolator())
                     .setDuration(FADE_IN_ANIMATION_DURATION)
diff --git a/src/com/android/launcher3/graphics/ShapeDelegate.kt b/src/com/android/launcher3/graphics/ShapeDelegate.kt
new file mode 100644
index 0000000..7c04292
--- /dev/null
+++ b/src/com/android/launcher3/graphics/ShapeDelegate.kt
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.graphics
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Matrix
+import android.graphics.Matrix.ScaleToFit.FILL
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.Region
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.graphics.drawable.ColorDrawable
+import android.util.Log
+import android.view.View
+import android.view.ViewOutlineProvider
+import androidx.annotation.VisibleForTesting
+import androidx.core.graphics.PathParser
+import androidx.graphics.shapes.CornerRounding
+import androidx.graphics.shapes.Morph
+import androidx.graphics.shapes.RoundedPolygon
+import androidx.graphics.shapes.SvgPathParser
+import androidx.graphics.shapes.rectangle
+import androidx.graphics.shapes.toPath
+import androidx.graphics.shapes.transformed
+import com.android.launcher3.anim.RoundedRectRevealOutlineProvider
+import com.android.launcher3.icons.GraphicsUtils
+import com.android.launcher3.views.ClipPathView
+
+/** Abstract representation of the shape of an icon shape */
+interface ShapeDelegate {
+
+    fun getPath(pathSize: Float = DEFAULT_PATH_SIZE) =
+        Path().apply { addToPath(this, 0f, 0f, pathSize / 2) }
+
+    fun getPath(bounds: Rect) =
+        Path().apply {
+            addToPath(
+                this,
+                bounds.left.toFloat(),
+                bounds.top.toFloat(),
+                // Radius is half of the average size of the icon
+                (bounds.width() + bounds.height()) / 4f,
+            )
+        }
+
+    fun drawShape(canvas: Canvas, offsetX: Float, offsetY: Float, radius: Float, paint: Paint)
+
+    fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float)
+
+    fun <T> createRevealAnimator(
+        target: T,
+        startRect: Rect,
+        endRect: Rect,
+        endRadius: Float,
+        isReversed: Boolean,
+    ): ValueAnimator where T : View, T : ClipPathView
+
+    class Circle : RoundedSquare(1f) {
+
+        override fun drawShape(
+            canvas: Canvas,
+            offsetX: Float,
+            offsetY: Float,
+            radius: Float,
+            paint: Paint,
+        ) = canvas.drawCircle(radius + offsetX, radius + offsetY, radius, paint)
+
+        override fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float) =
+            path.addCircle(radius + offsetX, radius + offsetY, radius, Path.Direction.CW)
+    }
+
+    /** Rounded square with [radiusRatio] as a ratio of its half edge size */
+    @VisibleForTesting
+    open class RoundedSquare(val radiusRatio: Float) : ShapeDelegate {
+
+        override fun drawShape(
+            canvas: Canvas,
+            offsetX: Float,
+            offsetY: Float,
+            radius: Float,
+            paint: Paint,
+        ) {
+            val cx = radius + offsetX
+            val cy = radius + offsetY
+            val cr = radius * radiusRatio
+            canvas.drawRoundRect(cx - radius, cy - radius, cx + radius, cy + radius, cr, cr, paint)
+        }
+
+        override fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float) {
+            val cx = radius + offsetX
+            val cy = radius + offsetY
+            val cr = radius * radiusRatio
+            path.addRoundRect(
+                cx - radius,
+                cy - radius,
+                cx + radius,
+                cy + radius,
+                cr,
+                cr,
+                Path.Direction.CW,
+            )
+        }
+
+        override fun <T> createRevealAnimator(
+            target: T,
+            startRect: Rect,
+            endRect: Rect,
+            endRadius: Float,
+            isReversed: Boolean,
+        ): ValueAnimator where T : View, T : ClipPathView {
+            return object :
+                    RoundedRectRevealOutlineProvider(
+                        (startRect.width() / 2f) * radiusRatio,
+                        endRadius,
+                        startRect,
+                        endRect,
+                    ) {
+                    override fun shouldRemoveElevationDuringAnimation() = true
+                }
+                .createRevealAnimator(target, isReversed)
+        }
+
+        override fun equals(other: Any?) =
+            other is RoundedSquare && other.radiusRatio == radiusRatio
+
+        override fun hashCode() = radiusRatio.hashCode()
+    }
+
+    /** Generic shape delegate with pathString in bounds [0, 0, 100, 100] */
+    data class GenericPathShape(private val pathString: String) : ShapeDelegate {
+        private val poly =
+            RoundedPolygon(
+                features = SvgPathParser.parseFeatures(pathString),
+                centerX = 50f,
+                centerY = 50f,
+            )
+        // This ensures that a valid morph is possible from the provided path
+        private val basePath =
+            Path().apply {
+                Morph(poly, createRoundedRect(0f, 0f, 100f, 100f, 25f)).toPath(0f, this)
+            }
+        private val tmpPath = Path()
+        private val tmpMatrix = Matrix()
+
+        override fun drawShape(
+            canvas: Canvas,
+            offsetX: Float,
+            offsetY: Float,
+            radius: Float,
+            paint: Paint,
+        ) {
+            tmpPath.reset()
+            addToPath(tmpPath, offsetX, offsetY, radius, tmpMatrix)
+            canvas.drawPath(tmpPath, paint)
+        }
+
+        override fun addToPath(path: Path, offsetX: Float, offsetY: Float, radius: Float) {
+            addToPath(path, offsetX, offsetY, radius, Matrix())
+        }
+
+        private fun addToPath(
+            path: Path,
+            offsetX: Float,
+            offsetY: Float,
+            radius: Float,
+            matrix: Matrix,
+        ) {
+            matrix.setScale(radius / 50, radius / 50)
+            matrix.postTranslate(offsetX, offsetY)
+            basePath.transform(matrix, path)
+        }
+
+        override fun <T> createRevealAnimator(
+            target: T,
+            startRect: Rect,
+            endRect: Rect,
+            endRadius: Float,
+            isReversed: Boolean,
+        ): ValueAnimator where T : View, T : ClipPathView {
+            // End poly is defined as a rectangle starting at top/center so that the
+            // transformation has minimum motion
+            val morph =
+                Morph(
+                    start =
+                        poly.transformed(
+                            Matrix().apply {
+                                setRectToRect(
+                                    RectF(0f, 0f, DEFAULT_PATH_SIZE, DEFAULT_PATH_SIZE),
+                                    RectF(startRect),
+                                    FILL,
+                                )
+                            }
+                        ),
+                    end =
+                        createRoundedRect(
+                            left = endRect.left.toFloat(),
+                            top = endRect.top.toFloat(),
+                            right = endRect.right.toFloat(),
+                            bottom = endRect.bottom.toFloat(),
+                            cornerR = endRadius,
+                        ),
+                )
+
+            val va =
+                if (isReversed) ValueAnimator.ofFloat(1f, 0f) else ValueAnimator.ofFloat(0f, 1f)
+            va.addListener(
+                object : AnimatorListenerAdapter() {
+                    private var oldOutlineProvider: ViewOutlineProvider? = null
+
+                    override fun onAnimationStart(animation: Animator) {
+                        target.apply {
+                            oldOutlineProvider = outlineProvider
+                            outlineProvider = null
+                            translationZ = -target.elevation
+                        }
+                    }
+
+                    override fun onAnimationEnd(animation: Animator) {
+                        target.apply {
+                            translationZ = 0f
+                            setClipPath(null)
+                            outlineProvider = oldOutlineProvider
+                        }
+                    }
+                }
+            )
+
+            val path = Path()
+            va.addUpdateListener { anim: ValueAnimator ->
+                path.reset()
+                morph.toPath(anim.animatedValue as Float, path)
+                target.setClipPath(path)
+            }
+            return va
+        }
+    }
+
+    companion object {
+
+        const val TAG = "IconShape"
+        const val DEFAULT_PATH_SIZE = 100f
+        const val AREA_CALC_SIZE = 1000
+        // .1% error margin
+        const val AREA_DIFF_THRESHOLD = AREA_CALC_SIZE * AREA_CALC_SIZE / 1000
+
+        /** Returns a function to calculate area diff from [base] */
+        @VisibleForTesting
+        fun areaDiffCalculator(base: Path): (ShapeDelegate) -> Int {
+            val fullRegion = Region(0, 0, AREA_CALC_SIZE, AREA_CALC_SIZE)
+            val iconRegion = Region().apply { setPath(base, fullRegion) }
+
+            val shapePath = Path()
+            val shapeRegion = Region()
+            return fun(shape: ShapeDelegate): Int {
+                shapePath.reset()
+                shape.addToPath(shapePath, 0f, 0f, AREA_CALC_SIZE / 2f)
+                shapeRegion.setPath(shapePath, fullRegion)
+                shapeRegion.op(iconRegion, Region.Op.XOR)
+                return GraphicsUtils.getArea(shapeRegion)
+            }
+        }
+
+        fun pickBestShape(shapeStr: String): ShapeDelegate {
+            val baseShape =
+                if (shapeStr.isNotEmpty()) {
+                    PathParser.createPathFromPathData(shapeStr).apply {
+                        transform(
+                            Matrix().apply {
+                                setScale(
+                                    AREA_CALC_SIZE / DEFAULT_PATH_SIZE,
+                                    AREA_CALC_SIZE / DEFAULT_PATH_SIZE,
+                                )
+                            }
+                        )
+                    }
+                } else {
+                    AdaptiveIconDrawable(null, ColorDrawable(Color.BLACK)).let {
+                        it.setBounds(0, 0, AREA_CALC_SIZE, AREA_CALC_SIZE)
+                        it.iconMask
+                    }
+                }
+            return pickBestShape(baseShape, shapeStr)
+        }
+
+        fun pickBestShape(baseShape: Path, shapeStr: String): ShapeDelegate {
+            val calcAreaDiff = areaDiffCalculator(baseShape)
+
+            // Find the shape with minimum area of divergent region.
+            var closestShape: ShapeDelegate = Circle()
+            var minAreaDiff = calcAreaDiff(closestShape)
+
+            // Try some common rounded rect edges
+            for (f in 0..20) {
+                val rectShape = RoundedSquare(f.toFloat() / 20)
+                val rectArea = calcAreaDiff(rectShape)
+                if (rectArea < minAreaDiff) {
+                    minAreaDiff = rectArea
+                    closestShape = rectShape
+                }
+            }
+
+            // Use the generic shape only if we have more than .1% error
+            if (shapeStr.isNotEmpty() && minAreaDiff > AREA_DIFF_THRESHOLD) {
+                try {
+                    val generic = GenericPathShape(shapeStr)
+                    closestShape = generic
+                } catch (e: Exception) {
+                    Log.e(TAG, "Error converting mask to generic shape", e)
+                }
+            }
+            return closestShape
+        }
+
+        /**
+         * Create RoundedRect using RoundedPolygon API. Ensures smoother animation morphing between
+         * generic polygon by using [RoundedPolygon.Companion.rectangle] directly.
+         */
+        fun createRoundedRect(
+            left: Float,
+            top: Float,
+            right: Float,
+            bottom: Float,
+            cornerR: Float,
+        ) =
+            RoundedPolygon.rectangle(
+                width = right - left,
+                height = bottom - top,
+                centerX = (right - left) / 2,
+                centerY = (bottom - top) / 2,
+                rounding = CornerRounding(cornerR),
+            )
+    }
+}
diff --git a/src/com/android/launcher3/graphics/SysUiScrim.java b/src/com/android/launcher3/graphics/SysUiScrim.java
index d59fc19..6f1d98f 100644
--- a/src/com/android/launcher3/graphics/SysUiScrim.java
+++ b/src/com/android/launcher3/graphics/SysUiScrim.java
@@ -40,6 +40,7 @@
 import com.android.launcher3.util.ScreenOnTracker;
 import com.android.launcher3.util.ScreenOnTracker.ScreenOnListener;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
 
 /**
  * View scrim which draws behind hotseat and workspace
@@ -94,8 +95,8 @@
 
     public SysUiScrim(View view) {
         mRoot = view;
-        mContainer = StatefulContainer.fromContext(view.getContext());
-        DisplayMetrics dm = mContainer.getContext().getResources().getDisplayMetrics();
+        mContainer = ActivityContext.lookupContext(view.getContext());
+        DisplayMetrics dm = mContainer.asContext().getResources().getDisplayMetrics();
 
         mTopMaskHeight = ResourceUtils.pxFromDp(TOP_MASK_HEIGHT_DP, dm);
         mBottomMaskHeight = ResourceUtils.pxFromDp(BOTTOM_MASK_HEIGHT_DP, dm);
@@ -173,12 +174,12 @@
 
     @Override
     public void onViewAttachedToWindow(View view) {
-        ScreenOnTracker.INSTANCE.get(mContainer.getContext()).addListener(mScreenOnListener);
+        ScreenOnTracker.INSTANCE.get(mContainer.asContext()).addListener(mScreenOnListener);
     }
 
     @Override
     public void onViewDetachedFromWindow(View view) {
-        ScreenOnTracker.INSTANCE.get(mContainer.getContext()).removeListener(mScreenOnListener);
+        ScreenOnTracker.INSTANCE.get(mContainer.asContext()).removeListener(mScreenOnListener);
     }
 
     /**
@@ -213,7 +214,7 @@
     }
 
     private Bitmap createDitheredAlphaMask(int height, @ColorInt int[] colors, float[] positions) {
-        DisplayMetrics dm = mContainer.getContext().getResources().getDisplayMetrics();
+        DisplayMetrics dm = mContainer.asContext().getResources().getDisplayMetrics();
         int width = ResourceUtils.pxFromDp(ALPHA_MASK_BITMAP_WIDTH_DP, dm);
         Bitmap dst = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8);
         Canvas c = new Canvas(dst);
diff --git a/src/com/android/launcher3/graphics/ThemeManager.kt b/src/com/android/launcher3/graphics/ThemeManager.kt
new file mode 100644
index 0000000..ebb7ea0
--- /dev/null
+++ b/src/com/android/launcher3/graphics/ThemeManager.kt
@@ -0,0 +1,180 @@
+/*
+ * 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.graphics
+
+import android.content.Context
+import android.content.res.Resources
+import com.android.launcher3.EncryptionType
+import com.android.launcher3.Item
+import com.android.launcher3.LauncherPrefChangeListener
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.LauncherPrefs.Companion.backedUpItem
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.graphics.ShapeDelegate.Companion.pickBestShape
+import com.android.launcher3.icons.IconThemeController
+import com.android.launcher3.icons.mono.MonoIconThemeController
+import com.android.launcher3.shapes.ShapesProvider
+import com.android.launcher3.util.DaggerSingletonObject
+import com.android.launcher3.util.DaggerSingletonTracker
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.SimpleBroadcastReceiver
+import java.util.concurrent.CopyOnWriteArrayList
+import javax.inject.Inject
+
+/** Centralized class for managing Launcher icon theming */
+@LauncherAppSingleton
+class ThemeManager
+@Inject
+constructor(
+    @ApplicationContext private val context: Context,
+    private val prefs: LauncherPrefs,
+    private val iconControllerFactory: IconControllerFactory,
+    lifecycle: DaggerSingletonTracker,
+) {
+
+    /** Representation of the current icon state */
+    var iconState = parseIconState(null)
+        private set
+
+    var isMonoThemeEnabled
+        set(value) = prefs.put(THEMED_ICONS, value)
+        get() = prefs.get(THEMED_ICONS)
+
+    val themeController
+        get() = iconState.themeController
+
+    val isIconThemeEnabled
+        get() = themeController != null
+
+    val iconShape
+        get() = iconState.iconShape
+
+    val folderShape
+        get() = iconState.folderShape
+
+    private val listeners = CopyOnWriteArrayList<ThemeChangeListener>()
+
+    init {
+        val receiver = SimpleBroadcastReceiver(context, MAIN_EXECUTOR) { verifyIconState() }
+        receiver.registerPkgActions("android", ACTION_OVERLAY_CHANGED)
+
+        val keys = (iconControllerFactory.prefKeys + PREF_ICON_SHAPE)
+
+        val keysArray = keys.toTypedArray()
+        val prefKeySet = keys.map { it.sharedPrefKey }
+        val prefListener = LauncherPrefChangeListener { key ->
+            if (prefKeySet.contains(key)) verifyIconState()
+        }
+        prefs.addListener(prefListener, *keysArray)
+        lifecycle.addCloseable {
+            receiver.unregisterReceiverSafely()
+            prefs.removeListener(prefListener, *keysArray)
+        }
+    }
+
+    private fun verifyIconState() {
+        val newState = parseIconState(iconState)
+        if (newState == iconState) return
+        iconState = newState
+
+        listeners.forEach { it.onThemeChanged() }
+    }
+
+    fun addChangeListener(listener: ThemeChangeListener) = listeners.add(listener)
+
+    fun removeChangeListener(listener: ThemeChangeListener) = listeners.remove(listener)
+
+    private fun parseIconState(oldState: IconState?): IconState {
+        val shapeModel =
+            prefs.get(PREF_ICON_SHAPE).let { shapeOverride ->
+                ShapesProvider.iconShapes.firstOrNull { it.key == shapeOverride }
+            }
+        val iconMask =
+            when {
+                shapeModel != null -> shapeModel.pathString
+                CONFIG_ICON_MASK_RES_ID == Resources.ID_NULL -> ""
+                else -> context.resources.getString(CONFIG_ICON_MASK_RES_ID)
+            }
+
+        val iconShape =
+            if (oldState != null && oldState.iconMask == iconMask) oldState.iconShape
+            else pickBestShape(iconMask)
+
+        val folderShapeMask = shapeModel?.folderPathString ?: iconMask
+        val folderShape =
+            when {
+                oldState != null && oldState.folderShapeMask == folderShapeMask ->
+                    oldState.folderShape
+                folderShapeMask == iconMask || folderShapeMask.isEmpty() -> iconShape
+                else -> pickBestShape(folderShapeMask)
+            }
+
+        return IconState(
+            iconMask = iconMask,
+            folderShapeMask = folderShapeMask,
+            themeController = iconControllerFactory.createThemeController(),
+            iconScale = shapeModel?.iconScale ?: 1f,
+            iconShape = iconShape,
+            folderShape = folderShape,
+        )
+    }
+
+    data class IconState(
+        val iconMask: String,
+        val folderShapeMask: String,
+        val themeController: IconThemeController?,
+        val themeCode: String = themeController?.themeID ?: "no-theme",
+        val iconScale: Float = 1f,
+        val iconShape: ShapeDelegate,
+        val folderShape: ShapeDelegate,
+    ) {
+        fun toUniqueId() = "${iconMask.hashCode()},$themeCode"
+    }
+
+    /** Interface for receiving theme change events */
+    fun interface ThemeChangeListener {
+        fun onThemeChanged()
+    }
+
+    open class IconControllerFactory @Inject constructor(protected val prefs: LauncherPrefs) {
+
+        open val prefKeys: List<Item> = listOf(THEMED_ICONS)
+
+        open fun createThemeController(): IconThemeController? {
+            return if (prefs.get(THEMED_ICONS)) MONO_THEME_CONTROLLER else null
+        }
+    }
+
+    companion object {
+
+        @JvmField val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getThemeManager)
+        const val KEY_ICON_SHAPE = "icon_shape_model"
+
+        const val KEY_THEMED_ICONS = "themed_icons"
+        @JvmField val THEMED_ICONS = backedUpItem(KEY_THEMED_ICONS, false, EncryptionType.ENCRYPTED)
+        @JvmField val PREF_ICON_SHAPE = backedUpItem(KEY_ICON_SHAPE, "", EncryptionType.ENCRYPTED)
+
+        private const val ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED"
+        private val CONFIG_ICON_MASK_RES_ID: Int =
+            Resources.getSystem().getIdentifier("config_icon_mask", "string", "android")
+
+        // Use a constant to allow equality check in verifyIconState
+        private val MONO_THEME_CONTROLLER = MonoIconThemeController()
+    }
+}
diff --git a/src/com/android/launcher3/icons/CacheableShortcutInfo.kt b/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
index a78da23..50dd146 100644
--- a/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
+++ b/src/com/android/launcher3/icons/CacheableShortcutInfo.kt
@@ -30,6 +30,7 @@
 import com.android.launcher3.icons.cache.BaseIconCache
 import com.android.launcher3.icons.cache.CachingLogic
 import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.util.ApiWrapper
 import com.android.launcher3.util.ApplicationInfoWrapper
 import com.android.launcher3.util.PackageUserKey
 import com.android.launcher3.util.Themes
@@ -112,7 +113,17 @@
                 ?.let { d ->
                     li.createBadgedIconBitmap(
                         d,
-                        IconOptions().setExtractedColor(Themes.getColorAccent(context)),
+                        IconOptions()
+                            .setExtractedColor(Themes.getColorAccent(context))
+                            .setSourceHint(
+                                getSourceHint(info, cache)
+                                    .copy(
+                                        isFileDrawable =
+                                            ApiWrapper.INSTANCE[context].isFileDrawable(
+                                                info.shortcutInfo
+                                            )
+                                    )
+                            ),
                     )
                 } ?: BitmapInfo.LOW_RES_INFO
         }
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index e7c4024..119a6b1 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -17,8 +17,10 @@
 package com.android.launcher3.icons;
 
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.LooperExecutor.CALLER_ICON_CACHE;
 import static com.android.launcher3.widget.WidgetSections.NO_CATEGORY;
 
 import static java.util.stream.Collectors.groupingBy;
@@ -34,7 +36,6 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteException;
 import android.os.Looper;
-import android.os.Process;
 import android.os.Trace;
 import android.os.UserHandle;
 import android.text.TextUtils;
@@ -50,7 +51,10 @@
 import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.icons.cache.BaseIconCache;
+import com.android.launcher3.icons.cache.CacheLookupFlag;
 import com.android.launcher3.icons.cache.CachedObject;
 import com.android.launcher3.icons.cache.CachedObjectCachingLogic;
 import com.android.launcher3.icons.cache.LauncherActivityCachingLogic;
@@ -64,6 +68,7 @@
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.CancellableTask;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.InstantAppResolver;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.widget.WidgetSections;
@@ -77,9 +82,13 @@
 import java.util.function.Supplier;
 import java.util.stream.Stream;
 
+import javax.inject.Inject;
+import javax.inject.Named;
+
 /**
  * Cache of application icons.  Icons can be made from any thread.
  */
+@LauncherAppSingleton
 public class IconCache extends BaseIconCache {
 
     // Shortcut extra which can point to a packageName and can be used to indicate an alternate
@@ -94,28 +103,43 @@
 
     private final LauncherApps mLauncherApps;
     private final UserCache mUserManager;
+    private final InstallSessionHelper mInstallSessionHelper;
     private final InstantAppResolver mInstantAppResolver;
     private final CancellableTask mCancelledTask;
+    private final LauncherIcons.IconPool mIconPool;
 
     private final SparseArray<BitmapInfo> mWidgetCategoryBitmapInfos;
 
     private int mPendingIconRequestCount = 0;
 
-    public IconCache(Context context, InvariantDeviceProfile idp, String dbFileName,
-            IconProvider iconProvider) {
+    @Inject
+    public IconCache(
+            @ApplicationContext Context context,
+            InvariantDeviceProfile idp,
+            @Nullable @Named("ICONS_DB") String dbFileName,
+            UserCache userCache,
+            LauncherIconProvider iconProvider,
+            InstallSessionHelper installSessionHelper,
+            LauncherIcons.IconPool iconPool,
+            DaggerSingletonTracker lifecycle) {
         super(context, dbFileName, MODEL_EXECUTOR.getLooper(),
                 idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */, iconProvider);
-        mLauncherApps = mContext.getSystemService(LauncherApps.class);
-        mUserManager = UserCache.INSTANCE.get(mContext);
-        mInstantAppResolver = InstantAppResolver.newInstance(mContext);
+        mLauncherApps = context.getSystemService(LauncherApps.class);
+        mUserManager = userCache;
+        mInstallSessionHelper = installSessionHelper;
+        mIconPool = iconPool;
+
+        mInstantAppResolver = InstantAppResolver.newInstance(context);
         mWidgetCategoryBitmapInfos = new SparseArray<>();
 
         mCancelledTask = new CancellableTask(() -> null, MAIN_EXECUTOR, c -> { });
         mCancelledTask.cancel();
+
+        lifecycle.addCloseable(this::close);
     }
 
     @Override
-    protected long getSerialNumberForUser(@NonNull UserHandle user) {
+    public long getSerialNumberForUser(@NonNull UserHandle user) {
         return mUserManager.getSerialNumberForUser(user);
     }
 
@@ -127,7 +151,7 @@
     @NonNull
     @Override
     public BaseIconFactory getIconFactory() {
-        return LauncherIcons.obtain(mContext);
+        return mIconPool.obtain();
     }
 
     /**
@@ -149,7 +173,7 @@
         // This will clear all pending updates
         getUpdateHandler();
 
-        mIconDb.close();
+        iconDb.close();
     }
 
     /**
@@ -163,12 +187,12 @@
         Supplier<ItemInfoWithIcon> task;
         if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) {
             task = () -> {
-                getTitleAndIcon(info, false);
+                getTitleAndIcon(info, DEFAULT_LOOKUP_FLAG);
                 return info;
             };
         } else if (info instanceof PackageItemInfo pii) {
             task = () -> {
-                getTitleAndIconForApp(pii, false);
+                getTitleAndIconForApp(pii, DEFAULT_LOOKUP_FLAG);
                 return pii;
             };
         } else {
@@ -180,7 +204,7 @@
         Runnable endRunnable;
         if (Looper.myLooper() == Looper.getMainLooper()) {
             if (mPendingIconRequestCount <= 0) {
-                MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
+                MODEL_EXECUTOR.elevatePriority(CALLER_ICON_CACHE);
             }
             mPendingIconRequestCount++;
             endRunnable = this::onIconRequestEnd;
@@ -197,7 +221,7 @@
     private void onIconRequestEnd() {
         mPendingIconRequestCount--;
         if (mPendingIconRequestCount <= 0) {
-            MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+            MODEL_EXECUTOR.restorePriority(CALLER_ICON_CACHE);
         }
     }
 
@@ -207,7 +231,7 @@
     public synchronized void updateTitleAndIcon(AppInfo application) {
         CacheEntry entry = cacheLocked(application.componentName,
                 application.user, () -> null, LauncherActivityCachingLogic.INSTANCE,
-                application.usingLowResIcon() ? LookupFlag.USE_LOW_RES : LookupFlag.DEFAULT);
+                application.getMatchingLookupFlag());
         if (entry.bitmap != null || !isDefaultIcon(entry.bitmap, application.user)) {
             applyCacheEntry(entry, application);
         }
@@ -218,18 +242,18 @@
      */
     @SuppressWarnings("NewApi")
     public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
-            LauncherActivityInfo activityInfo, boolean useLowResIcon) {
+            LauncherActivityInfo activityInfo, @NonNull CacheLookupFlag lookupFlag) {
         boolean isAppArchived = Flags.enableSupportForArchiving() && activityInfo != null
                 && activityInfo.getActivityInfo().isArchived;
         // If we already have activity info, no need to use package icon
-        getTitleAndIcon(info, () -> activityInfo, isAppArchived, useLowResIcon);
+        getTitleAndIcon(info, () -> activityInfo, lookupFlag.withUsePackageIcon(isAppArchived));
     }
 
     /**
      * Fill in {@code info} with the icon for {@code si}
      */
     public void getShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) {
-        getShortcutIcon(info, new CacheableShortcutInfo(si, mContext));
+        getShortcutIcon(info, new CacheableShortcutInfo(si, context));
     }
 
     /**
@@ -252,7 +276,7 @@
                 user,
                 () -> si,
                 CacheableShortcutCachingLogic.INSTANCE,
-                LookupFlag.SKIP_ADD_TO_MEM_CACHE).bitmap;
+                DEFAULT_LOOKUP_FLAG.withSkipAddToMemCache()).bitmap;
         if (bitmapInfo.isNullOrLowRes()) {
             bitmapInfo = getDefaultIcon(user);
         }
@@ -277,8 +301,7 @@
         String override = shortcutInfo.getExtras() == null ? null
                 : shortcutInfo.getExtras().getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE);
         if (!TextUtils.isEmpty(override)
-                && InstallSessionHelper.INSTANCE.get(mContext)
-                .isTrustedPackage(pkg, shortcutInfo.getUserHandle())) {
+                && mInstallSessionHelper.isTrustedPackage(pkg, shortcutInfo.getUserHandle())) {
             pkg = override;
         } else {
             // Try component based badge before trying the normal package badge
@@ -291,12 +314,12 @@
                 appInfo.intent = new Intent(Intent.ACTION_MAIN)
                         .addCategory(Intent.CATEGORY_LAUNCHER)
                         .setComponent(cn);
-                getTitleAndIcon(appInfo, false);
+                getTitleAndIcon(appInfo, DEFAULT_LOOKUP_FLAG);
                 return appInfo;
             }
         }
         PackageItemInfo pkgInfo = new PackageItemInfo(pkg, shortcutInfo.getUserHandle());
-        getTitleAndIconForApp(pkgInfo, false);
+        getTitleAndIconForApp(pkgInfo, DEFAULT_LOOKUP_FLAG);
         return pkgInfo;
     }
 
@@ -304,7 +327,9 @@
      * Fill in {@param info} with the icon and label. If the
      * corresponding activity is not found, it reverts to the package icon.
      */
-    public synchronized void getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon) {
+    public synchronized void getTitleAndIcon(
+            @NonNull ItemInfoWithIcon info,
+            @NonNull CacheLookupFlag lookupFlag) {
         // null info means not installed, but if we have a component from the intent then
         // we should still look in the cache for restored app icons.
         if (info.getTargetComponent() == null) {
@@ -314,7 +339,7 @@
         } else {
             Intent intent = info.getIntent();
             getTitleAndIcon(info, () -> mLauncherApps.resolveActivity(intent, info.user),
-                    true, useLowResIcon);
+                    lookupFlag.withUsePackageIcon());
         }
     }
 
@@ -324,7 +349,7 @@
     public synchronized String getTitleNoCache(CachedObject info) {
         CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info,
                 CachedObjectCachingLogic.INSTANCE,
-                LookupFlag.USE_LOW_RES | LookupFlag.SKIP_ADD_TO_MEM_CACHE);
+                DEFAULT_LOOKUP_FLAG.withUseLowRes().withSkipAddToMemCache());
         return Utilities.trim(entry.title);
     }
 
@@ -334,12 +359,9 @@
     public synchronized void getTitleAndIcon(
             @NonNull ItemInfoWithIcon infoInOut,
             @NonNull Supplier<LauncherActivityInfo> activityInfoProvider,
-            boolean usePkgIcon, boolean useLowResIcon) {
-        int lookupFlags = LookupFlag.DEFAULT;
-        if (usePkgIcon) lookupFlags |= LookupFlag.USE_PACKAGE_ICON;
-        if (useLowResIcon) lookupFlags |= LookupFlag.USE_LOW_RES;
+            @NonNull CacheLookupFlag lookupFlag) {
         CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user,
-                activityInfoProvider, LauncherActivityCachingLogic.INSTANCE, lookupFlags);
+                activityInfoProvider, LauncherActivityCachingLogic.INSTANCE, lookupFlag);
         applyCacheEntry(entry, infoInOut);
     }
 
@@ -348,10 +370,10 @@
      *
      * @param iconRequestInfos List of IconRequestInfos representing titles and icons to query.
      * @param user UserHandle all the given iconRequestInfos share
-     * @param useLowResIcons whether we should exclude the icon column from the sql results.
+     * @param lookupFlag what flags to use when loading the icon.
      */
     private <T extends ItemInfoWithIcon> Cursor createBulkQueryCursor(
-            List<IconRequestInfo<T>> iconRequestInfos, UserHandle user, boolean useLowResIcons)
+            List<IconRequestInfo<T>> iconRequestInfos, UserHandle user, CacheLookupFlag lookupFlag)
             throws SQLiteException {
         String[] queryParams = Stream.concat(
                 iconRequestInfos.stream()
@@ -363,11 +385,11 @@
         String componentNameQuery = TextUtils.join(
                 ",", Collections.nCopies(queryParams.length - 1, "?"));
 
-        return mIconDb.query(
-                useLowResIcons ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES,
-                IconDB.COLUMN_COMPONENT
+        return iconDb.query(
+                toLookupColumns(lookupFlag),
+                COLUMN_COMPONENT
                         + " IN ( " + componentNameQuery + " )"
-                        + " AND " + IconDB.COLUMN_USER + " = ?",
+                        + " AND " + COLUMN_USER + " = ?",
                 queryParams);
     }
 
@@ -422,12 +444,13 @@
             List<IconRequestInfo<T>> filteredList,
             Map<ComponentName, List<IconRequestInfo<T>>> duplicateIconRequestsMap) {
         Trace.beginSection("loadIconSubsectionWithDatabase");
+        CacheLookupFlag lookupFlag = DEFAULT_LOOKUP_FLAG.withUseLowRes(sectionKey.second);
         try (Cursor c = createBulkQueryCursor(
                 filteredList,
                 /* user = */ sectionKey.first,
-                /* useLowResIcons = */ sectionKey.second)) {
+                lookupFlag)) {
             // Database title and icon loading
-            int componentNameColumnIndex = c.getColumnIndexOrThrow(IconDB.COLUMN_COMPONENT);
+            int componentNameColumnIndex = c.getColumnIndexOrThrow(COLUMN_COMPONENT);
             while (c.moveToNext()) {
                 ComponentName cn = ComponentName.unflattenFromString(
                         c.getString(componentNameColumnIndex));
@@ -441,7 +464,7 @@
                                 /* user = */ sectionKey.first,
                                 () -> duplicateIconRequests.get(0).launcherActivityInfo,
                                 LauncherActivityCachingLogic.INSTANCE,
-                                sectionKey.second ? LookupFlag.USE_LOW_RES : LookupFlag.DEFAULT,
+                                lookupFlag,
                                 c);
 
                         for (IconRequestInfo<T> iconRequest : duplicateIconRequests) {
@@ -515,17 +538,18 @@
      * Fill in {@param infoInOut} with the corresponding icon and label.
      */
     public synchronized void getTitleAndIconForApp(
-            @NonNull final PackageItemInfo infoInOut, final boolean useLowResIcon) {
+            @NonNull final PackageItemInfo infoInOut,
+            @NonNull CacheLookupFlag lookupFlag) {
         CacheEntry entry = getEntryForPackageLocked(
-                infoInOut.packageName, infoInOut.user, useLowResIcon);
+                infoInOut.packageName, infoInOut.user, lookupFlag);
         applyCacheEntry(entry, infoInOut);
         if (infoInOut.widgetCategory == NO_CATEGORY) {
             return;
         }
 
-        WidgetSection widgetSection = WidgetSections.getWidgetSections(mContext)
+        WidgetSection widgetSection = WidgetSections.getWidgetSections(context)
                 .get(infoInOut.widgetCategory);
-        infoInOut.title = mContext.getString(widgetSection.mSectionTitle);
+        infoInOut.title = context.getString(widgetSection.mSectionTitle);
         infoInOut.contentDescription = getUserBadgedLabel(infoInOut.title, infoInOut.user);
         final BitmapInfo cachedBitmap = mWidgetCategoryBitmapInfos.get(infoInOut.widgetCategory);
         if (cachedBitmap != null) {
@@ -533,9 +557,9 @@
             return;
         }
 
-        try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
+        try (LauncherIcons li = mIconPool.obtain()) {
             final BitmapInfo tempBitmap = li.createBadgedIconBitmap(
-                    mContext.getDrawable(widgetSection.mSectionDrawable),
+                    context.getDrawable(widgetSection.mSectionDrawable),
                     new BaseIconFactory.IconOptions());
             mWidgetCategoryBitmapInfos.put(infoInOut.widgetCategory, tempBitmap);
             infoInOut.bitmap = getBadgedIcon(tempBitmap, infoInOut.user);
@@ -591,7 +615,8 @@
 
     @VisibleForTesting
     synchronized boolean isItemInDb(ComponentKey cacheKey) {
-        return getEntryFromDBLocked(cacheKey, new CacheEntry(), false);
+        return getEntryFromDBLocked(cacheKey, new CacheEntry(), DEFAULT_LOOKUP_FLAG,
+                LauncherActivityCachingLogic.INSTANCE);
     }
 
     /**
@@ -604,7 +629,7 @@
 
     /** Log persistently to FileLog.d for debugging. */
     @Override
-    protected void logdPersistently(String tag, String message, @Nullable Exception e) {
-        FileLog.d(tag, message, e);
+    protected void logPersistently(@NonNull String message, @Nullable Exception e) {
+        FileLog.d(BaseIconCache.TAG, message, e);
     }
 }
diff --git a/src/com/android/launcher3/icons/LauncherIconProvider.java b/src/com/android/launcher3/icons/LauncherIconProvider.java
index 78a3128..7241198 100644
--- a/src/com/android/launcher3/icons/LauncherIconProvider.java
+++ b/src/com/android/launcher3/icons/LauncherIconProvider.java
@@ -19,25 +19,34 @@
 import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.launcher3.R;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.graphics.ShapeDelegate;
+import com.android.launcher3.graphics.ThemeManager;
 import com.android.launcher3.util.ApiWrapper;
-import com.android.launcher3.util.Themes;
 
 import org.xmlpull.v1.XmlPullParser;
 
 import java.util.Collections;
 import java.util.Map;
 
+import javax.inject.Inject;
+
 /**
  * Extension of {@link IconProvider} with support for overriding theme icons
  */
+@LauncherAppSingleton
 public class LauncherIconProvider extends IconProvider {
 
     private static final String TAG_ICON = "icon";
@@ -48,18 +57,25 @@
     private static final Map<String, ThemeData> DISABLED_MAP = Collections.emptyMap();
 
     private Map<String, ThemeData> mThemedIconMap;
-    private boolean mSupportsIconTheme;
 
-    public LauncherIconProvider(Context context) {
+    private final ApiWrapper mApiWrapper;
+    private final ThemeManager mThemeManager;
+
+    @Inject
+    public LauncherIconProvider(
+            @ApplicationContext Context context,
+            ThemeManager themeManager,
+            ApiWrapper apiWrapper) {
         super(context);
-        setIconThemeSupported(Themes.isThemedIconEnabled(context));
+        mThemeManager = themeManager;
+        mApiWrapper = apiWrapper;
+        setIconThemeSupported(mThemeManager.isMonoThemeEnabled());
     }
 
     /**
      * Enables or disables icon theme support
      */
     public void setIconThemeSupported(boolean isSupported) {
-        mSupportsIconTheme = isSupported;
         mThemedIconMap = isSupported && FeatureFlags.USE_LOCAL_ICON_OVERRIDES.get()
                 ? null : DISABLED_MAP;
     }
@@ -70,13 +86,32 @@
     }
 
     @Override
-    public String getSystemIconState() {
-        return super.getSystemIconState() + (mSupportsIconTheme ? ",with-theme" : ",no-theme");
+    public void updateSystemState() {
+        super.updateSystemState();
+        mSystemState += "," + mThemeManager.getIconState().toUniqueId();
     }
 
     @Override
     protected String getApplicationInfoHash(@NonNull ApplicationInfo appInfo) {
-        return ApiWrapper.INSTANCE.get(mContext).getApplicationInfoHash(appInfo);
+        return mApiWrapper.getApplicationInfoHash(appInfo);
+    }
+
+    @Nullable
+    @Override
+    protected Drawable loadAppInfoIcon(ApplicationInfo info, Resources resources, int density) {
+        // Tries to load the round icon res, if the app defines it as an adaptive icon
+        if (mThemeManager.getIconShape() instanceof ShapeDelegate.Circle) {
+            int roundIconRes = mApiWrapper.getRoundIconRes(info);
+            if (roundIconRes != 0 && roundIconRes != info.icon) {
+                try {
+                    Drawable d = resources.getDrawableForDensity(roundIconRes, density);
+                    if (d instanceof AdaptiveIconDrawable) {
+                        return d;
+                    }
+                } catch (Resources.NotFoundException exc) { }
+            }
+        }
+        return super.loadAppInfoIcon(info, resources, density);
     }
 
     private Map<String, ThemeData> getThemedIconMap() {
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
deleted file mode 100644
index 839dfb7..0000000
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2016 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.icons;
-
-import android.content.Context;
-import android.os.UserHandle;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.graphics.IconShape;
-import com.android.launcher3.icons.mono.MonoIconThemeController;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.launcher3.util.SafeCloseable;
-import com.android.launcher3.util.Themes;
-import com.android.launcher3.util.UserIconInfo;
-
-import java.util.concurrent.ConcurrentLinkedQueue;
-
-/**
- * Wrapper class to provide access to {@link BaseIconFactory} and also to provide pool of this class
- * that are threadsafe.
- */
-public class LauncherIcons extends BaseIconFactory implements AutoCloseable {
-
-    private static final MainThreadInitializedObject<Pool> POOL =
-            new MainThreadInitializedObject<>(Pool::new);
-
-    /**
-     * Return a new Message instance from the global pool. Allows us to
-     * avoid allocating new objects in many cases.
-     */
-    public static LauncherIcons obtain(Context context) {
-        return POOL.get(context).obtain();
-    }
-
-    public static void clearPool(Context context) {
-        POOL.get(context).close();
-    }
-
-    private final ConcurrentLinkedQueue<LauncherIcons> mPool;
-
-    protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize,
-            ConcurrentLinkedQueue<LauncherIcons> pool) {
-        super(context, fillResIconDpi, iconBitmapSize,
-                IconShape.INSTANCE.get(context).getShape().enableShapeDetection());
-        if (Themes.isThemedIconEnabled(context)) {
-            mThemeController = new MonoIconThemeController();
-        }
-        mPool = pool;
-    }
-
-    /**
-     * Recycles a LauncherIcons that may be in-use.
-     */
-    public void recycle() {
-        clear();
-        mPool.add(this);
-    }
-
-    @NonNull
-    @Override
-    protected UserIconInfo getUserInfo(@NonNull UserHandle user) {
-        return UserCache.INSTANCE.get(mContext).getUserInfo(user);
-    }
-
-    @Override
-    public void close() {
-        recycle();
-    }
-
-    private static class Pool implements SafeCloseable {
-
-        private final Context mContext;
-
-        @NonNull
-        private ConcurrentLinkedQueue<LauncherIcons> mPool = new ConcurrentLinkedQueue<>();
-
-        private Pool(Context context) {
-            mContext = context;
-        }
-
-        public LauncherIcons obtain() {
-            ConcurrentLinkedQueue<LauncherIcons> pool = mPool;
-            LauncherIcons m = pool.poll();
-
-            if (m == null) {
-                InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
-                return new LauncherIcons(mContext, idp.fillResIconDpi, idp.iconBitmapSize, pool);
-            } else {
-                return m;
-            }
-        }
-
-        @Override
-        public void close() {
-            mPool = new ConcurrentLinkedQueue<>();
-        }
-    }
-}
diff --git a/src/com/android/launcher3/icons/LauncherIcons.kt b/src/com/android/launcher3/icons/LauncherIcons.kt
new file mode 100644
index 0000000..29c0de1
--- /dev/null
+++ b/src/com/android/launcher3/icons/LauncherIcons.kt
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 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.icons
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Path
+import android.graphics.Rect
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.os.UserHandle
+import com.android.launcher3.Flags
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.dagger.LauncherComponentProvider.appComponent
+import com.android.launcher3.graphics.ThemeManager
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.UserIconInfo
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.ConcurrentLinkedQueue
+import javax.inject.Inject
+
+/**
+ * Wrapper class to provide access to [BaseIconFactory] and also to provide pool of this class that
+ * are threadsafe.
+ */
+class LauncherIcons
+@AssistedInject
+internal constructor(
+    @ApplicationContext context: Context,
+    idp: InvariantDeviceProfile,
+    private var themeManager: ThemeManager,
+    private var userCache: UserCache,
+    @Assisted private val pool: ConcurrentLinkedQueue<LauncherIcons>,
+) : BaseIconFactory(context, idp.fillResIconDpi, idp.iconBitmapSize), AutoCloseable {
+
+    private val iconScale = themeManager.iconState.iconScale
+
+    init {
+        mThemeController = themeManager.themeController
+    }
+
+    /** Recycles a LauncherIcons that may be in-use. */
+    fun recycle() {
+        clear()
+        pool.add(this)
+    }
+
+    override fun getUserInfo(user: UserHandle): UserIconInfo {
+        return userCache.getUserInfo(user)
+    }
+
+    override fun getShapePath(drawable: AdaptiveIconDrawable, iconBounds: Rect): Path {
+        if (!Flags.enableLauncherIconShapes()) return super.getShapePath(drawable, iconBounds)
+        return themeManager.iconShape.getPath(iconBounds)
+    }
+
+    override fun getIconScale(): Float {
+        if (!Flags.enableLauncherIconShapes()) return super.getIconScale()
+        return themeManager.iconState.iconScale
+    }
+
+    override fun drawAdaptiveIcon(
+        canvas: Canvas,
+        drawable: AdaptiveIconDrawable,
+        overridePath: Path,
+    ) {
+        if (!Flags.enableLauncherIconShapes()) {
+            super.drawAdaptiveIcon(canvas, drawable, overridePath)
+            return
+        }
+        canvas.clipPath(overridePath)
+        canvas.drawColor(Color.BLACK)
+        canvas.save()
+        canvas.scale(iconScale, iconScale, canvas.width / 2f, canvas.height / 2f)
+        if (drawable.background != null) {
+            drawable.background.draw(canvas)
+        }
+        if (drawable.foreground != null) {
+            drawable.foreground.draw(canvas)
+        }
+        canvas.restore()
+    }
+
+    override fun close() {
+        recycle()
+    }
+
+    @AssistedFactory
+    internal interface LauncherIconsFactory {
+        fun create(pool: ConcurrentLinkedQueue<LauncherIcons>): LauncherIcons
+    }
+
+    @LauncherAppSingleton
+    class IconPool @Inject internal constructor(private val factory: LauncherIconsFactory) {
+        private var pool = ConcurrentLinkedQueue<LauncherIcons>()
+
+        fun obtain(): LauncherIcons = pool.let { it.poll() ?: factory.create(it) }
+
+        fun clear() {
+            pool = ConcurrentLinkedQueue()
+        }
+    }
+
+    companion object {
+
+        /**
+         * Return a new LauncherIcons instance from the global pool. Allows us to avoid allocating
+         * new objects in many cases.
+         */
+        @JvmStatic
+        fun obtain(context: Context): LauncherIcons = context.appComponent.iconPool.obtain()
+
+        @JvmStatic fun clearPool(context: Context) = context.appComponent.iconPool.clear()
+    }
+}
diff --git a/src/com/android/launcher3/logging/FileLog.java b/src/com/android/launcher3/logging/FileLog.java
index 924a440..5b25418 100644
--- a/src/com/android/launcher3/logging/FileLog.java
+++ b/src/com/android/launcher3/logging/FileLog.java
@@ -1,6 +1,6 @@
 package com.android.launcher3.logging;
 
-import static com.android.launcher3.util.Executors.createAndStartNewLooper;
+import static com.android.launcher3.util.LooperExecutor.createAndStartNewLooper;
 
 import android.os.Handler;
 import android.os.HandlerThread;
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index dbab52a..74d5098 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -225,9 +225,12 @@
         @UiEvent(doc = "User tapped on desktop icon on a task menu.")
         LAUNCHER_SYSTEM_SHORTCUT_DESKTOP_TAP(1706),
 
-        @UiEvent(doc = "Use tapped on external display icon on a task menu,")
+        @UiEvent(doc = "User tapped on external display icon on a task menu,")
         LAUNCHER_SYSTEM_SHORTCUT_EXTERNAL_DISPLAY_TAP(1957),
 
+        @UiEvent(doc = "User tapped on close app on a task menu,")
+        LAUNCHER_SYSTEM_SHORTCUT_CLOSE_APP_TAP(2081),
+
         @UiEvent(doc = "User tapped on pause app system shortcut.")
         LAUNCHER_SYSTEM_SHORTCUT_PAUSE_TAP(521),
 
@@ -301,6 +304,12 @@
         @UiEvent(doc = "User swipes or fling in RIGHT direction on the bottom bazel area.")
         LAUNCHER_QUICKSWITCH_RIGHT(572),
 
+        @UiEvent(doc = "User swipes or fling on the bottom bazel area to enter Desktop mode.")
+        LAUNCHER_QUICKSWITCH_ENTER_DESKTOP_MODE(2025),
+
+        @UiEvent(doc = "User swipes or fling on the bottom bazel area to exit Desktop mode.")
+        LAUNCHER_QUICKSWITCH_EXIT_DESKTOP_MODE(2026),
+
         @UiEvent(doc = "User swipes or fling in DOWN direction on the bottom bazel area.")
         LAUNCHER_SWIPEDOWN_NAVBAR(573),
 
@@ -424,20 +433,29 @@
         @UiEvent(doc = "Notification dismissed by swiping right.")
         LAUNCHER_NOTIFICATION_DISMISSED(652),
 
-        @UiEvent(doc = "Current grid size is changed to 6.")
-        LAUNCHER_GRID_SIZE_6(930),
+        @UiEvent(doc = "Current grid size is changed to 2x2")
+        LAUNCHER_GRID_SIZE_2_BY_2(2181),
 
-        @UiEvent(doc = "Current grid size is changed to 5.")
-        LAUNCHER_GRID_SIZE_5(662),
+        @UiEvent(doc = "Current grid size is changed to 3x3")
+        LAUNCHER_GRID_SIZE_3_BY_3(2182),
 
-        @UiEvent(doc = "Current grid size is changed to 4.")
-        LAUNCHER_GRID_SIZE_4(663),
+        @UiEvent(doc = "Current grid size is changed to 4x4")
+        LAUNCHER_GRID_SIZE_4_BY_4(2183),
 
-        @UiEvent(doc = "Current grid size is changed to 3.")
-        LAUNCHER_GRID_SIZE_3(664),
+        @UiEvent(doc = "Current grid size is changed to 4x5")
+        LAUNCHER_GRID_SIZE_4_BY_5(2184),
 
-        @UiEvent(doc = "Current grid size is changed to 2.")
-        LAUNCHER_GRID_SIZE_2(665),
+        @UiEvent(doc = "Current grid size is changed to 4x6")
+        LAUNCHER_GRID_SIZE_4_BY_6(2185),
+
+        @UiEvent(doc = "Current grid size is changed to 5x5")
+        LAUNCHER_GRID_SIZE_5_BY_5(2186),
+
+        @UiEvent(doc = "Current grid size is changed to 5x6")
+        LAUNCHER_GRID_SIZE_5_BY_6(2187),
+
+        @UiEvent(doc = "Current grid size is changed to 6x5")
+        LAUNCHER_GRID_SIZE_6_BY_5(2188),
 
         @UiEvent(doc = "Launcher entered into AllApps state.")
         LAUNCHER_ALLAPPS_ENTRY(692),
@@ -850,6 +868,18 @@
 
         @UiEvent(doc = "User sets the device in Fixed Landscape")
         FIXED_LANDSCAPE_TOGGLE_DISABLED(2020),
+
+        @UiEvent(doc = "Work utility view expand animation started")
+        LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_BEGIN(2075),
+
+        @UiEvent(doc = "Work utility view expand animation ended")
+        LAUNCHER_WORK_UTILITY_VIEW_EXPAND_ANIMATION_END(2076),
+
+        @UiEvent(doc = "Work utility view shrink animation started")
+        LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_BEGIN(2077),
+
+        @UiEvent(doc = "Work utility view shrink animation ended")
+        LAUNCHER_WORK_UTILITY_VIEW_SHRINK_ANIMATION_END(2078),
         // ADD MORE
         ;
 
@@ -884,6 +914,10 @@
         @UiEvent(doc = "Time passed between Contextual Search runnable creation and execution. This"
                 + " ensures that Recent animations have finished before Contextual Search starts.")
         LAUNCHER_LATENCY_OMNI_RUNNABLE(1546),
+
+        @UiEvent(doc = "Time passed between nav handle touch down and cancellation without "
+                + "triggering Contextual Search")
+        LAUNCHER_LATENCY_CONTEXTUAL_SEARCH_LPNH_ABANDON(2171),
         ;
 
         private final int mId;
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index 55bcb70..ddbbdc7 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG;
+
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherActivityInfo;
@@ -194,8 +196,7 @@
                         WorkspaceItemInfo wii = (WorkspaceItemInfo) itemInfo;
                         wii.title = "";
                         wii.bitmap = cache.getDefaultIcon(item.user);
-                        cache.getTitleAndIcon(wii,
-                                ((WorkspaceItemInfo) itemInfo).usingLowResIcon());
+                        cache.getTitleAndIcon(wii, DESKTOP_ICON_FLAG);
                     }
                 }
 
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 7bc9273..98f9afd 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
 import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY;
 
@@ -151,7 +152,7 @@
             return;
         }
         if (loadIcon) {
-            mIconCache.getTitleAndIcon(info, activityInfo, false /* useLowResIcon */);
+            mIconCache.getTitleAndIcon(info, activityInfo, DEFAULT_LOOKUP_FLAG);
             info.sectionName = mIndex.computeSectionName(info.title);
         } else {
             info.title = "";
@@ -177,7 +178,7 @@
         AppInfo promiseAppInfo = new AppInfo(installInfo);
 
         if (loadIcon) {
-            mIconCache.getTitleAndIcon(promiseAppInfo, promiseAppInfo.usingLowResIcon());
+            mIconCache.getTitleAndIcon(promiseAppInfo, promiseAppInfo.getMatchingLookupFlag());
             promiseAppInfo.sectionName = mIndex.computeSectionName(promiseAppInfo.title);
         } else {
             promiseAppInfo.title = "";
@@ -338,7 +339,7 @@
                 } else {
                     Intent launchIntent = AppInfo.makeLaunchIntent(info);
 
-                    mIconCache.getTitleAndIcon(applicationInfo, info, false /* useLowResIcon */);
+                    mIconCache.getTitleAndIcon(applicationInfo, info, DEFAULT_LOOKUP_FLAG);
                     applicationInfo.sectionName = mIndex.computeSectionName(applicationInfo.title);
                     applicationInfo.intent = launchIntent;
                     AppInfo.updateRuntimeFlagsForActivityTarget(applicationInfo, info,
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index 014a28b..262bf67 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -19,14 +19,13 @@
 import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
 import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
 import static com.android.launcher3.Flags.enableWorkspaceInflation;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
 import static com.android.launcher3.model.ItemInstallQueue.FLAG_LOADER_RUNNING;
-import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
+import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
-import static java.util.Collections.emptyList;
-
-import android.os.Process;
 import android.os.Trace;
 import android.util.Log;
 import android.util.Pair;
@@ -44,11 +43,10 @@
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
+import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ItemInflater;
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.LooperIdleLock;
@@ -102,14 +100,12 @@
         Trace.beginSection("BaseLauncherBinder#bindWorkspace");
         try {
             // Save a copy of all the bg-thread collections
-            ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
-            ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
+            IntSparseArrayMap<ItemInfo> itemsIdMap;
             final IntArray orderedScreenIds = new IntArray();
             ArrayList<FixedContainerItems> extraItems = new ArrayList<>();
             final int workspaceItemCount;
             synchronized (mBgDataModel) {
-                workspaceItems.addAll(mBgDataModel.workspaceItems);
-                appWidgets.addAll(mBgDataModel.appWidgets);
+                itemsIdMap = mBgDataModel.itemsIdMap.clone();
                 orderedScreenIds.addAll(mBgDataModel.collectWorkspaceScreens());
                 mBgDataModel.extraItems.forEach(extraItems::add);
                 if (incrementBindId) {
@@ -122,7 +118,7 @@
 
             for (Callbacks cb : mCallbacksList) {
                 new UnifiedWorkspaceBinder(cb, mUiExecutor, mApp, mBgDataModel, mMyBindingId,
-                        workspaceItems, appWidgets, extraItems, orderedScreenIds)
+                        itemsIdMap, extraItems, orderedScreenIds)
                         .bind(isBindSync, workspaceItemCount);
             }
         } finally {
@@ -166,17 +162,10 @@
         if (!WIDGETS_ENABLED) {
             return;
         }
-        Map<PackageItemInfo, List<WidgetItem>>
-                widgetsByPackageItem = mBgDataModel.widgetsModel.getWidgetsByPackageItem();
         List<WidgetsListBaseEntry> widgets = new WidgetsListBaseEntriesBuilder(mApp.getContext())
-                .build(widgetsByPackageItem);
-        Predicate<WidgetItem> filter = mBgDataModel.widgetsModel.getDefaultWidgetsFilter();
-        List<WidgetsListBaseEntry> defaultWidgets =
-                filter != null ? new WidgetsListBaseEntriesBuilder(
-                        mApp.getContext()).build(widgetsByPackageItem,
-                        mBgDataModel.widgetsModel.getDefaultWidgetsFilter()) : emptyList();
+                .build(mBgDataModel.widgetsModel.getWidgetsByPackageItemForPicker());
 
-        executeCallbacksTask(c -> c.bindAllWidgets(widgets, defaultWidgets), mUiExecutor);
+        executeCallbacksTask(c -> c.bindAllWidgets(widgets), mUiExecutor);
     }
 
     /**
@@ -258,8 +247,7 @@
         private final BgDataModel mBgDataModel;
 
         private final int mMyBindingId;
-        private final ArrayList<ItemInfo> mWorkspaceItems;
-        private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
+        private final IntSparseArrayMap<ItemInfo> mItemIdMap;
         private final IntArray mOrderedScreenIds;
         private final ArrayList<FixedContainerItems> mExtraItems;
 
@@ -268,8 +256,7 @@
                 LauncherAppState app,
                 BgDataModel bgDataModel,
                 int myBindingId,
-                ArrayList<ItemInfo> workspaceItems,
-                ArrayList<LauncherAppWidgetInfo> appWidgets,
+                IntSparseArrayMap<ItemInfo> itemIdMap,
                 ArrayList<FixedContainerItems> extraItems,
                 IntArray orderedScreenIds) {
             mCallbacks = callbacks;
@@ -277,8 +264,7 @@
             mApp = app;
             mBgDataModel = bgDataModel;
             mMyBindingId = myBindingId;
-            mWorkspaceItems = workspaceItems;
-            mAppWidgets = appWidgets;
+            mItemIdMap = itemIdMap;
             mExtraItems = extraItems;
             mOrderedScreenIds = orderedScreenIds;
         }
@@ -294,10 +280,15 @@
             ArrayList<ItemInfo> currentAppWidgets = new ArrayList<>();
             ArrayList<ItemInfo> otherAppWidgets = new ArrayList<>();
 
-            filterCurrentWorkspaceItems(currentScreenIds, mWorkspaceItems, currentWorkspaceItems,
-                    otherWorkspaceItems);
-            filterCurrentWorkspaceItems(currentScreenIds, mAppWidgets, currentAppWidgets,
-                    otherAppWidgets);
+            Predicate<ItemInfo> currentScreenCheck = currentScreenContentFilter(currentScreenIds);
+            mItemIdMap.forEach(item -> {
+                if (currentScreenCheck.test(item)) {
+                    (WIDGET_FILTER.test(item) ? currentAppWidgets : currentWorkspaceItems)
+                            .add(item);
+                } else if (item.container == CONTAINER_DESKTOP) {
+                    (WIDGET_FILTER.test(item) ? otherAppWidgets : otherWorkspaceItems).add(item);
+                }
+            });
             final InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
             sortWorkspaceItemsSpatially(idp, currentWorkspaceItems);
             sortWorkspaceItemsSpatially(idp, otherWorkspaceItems);
@@ -350,14 +341,8 @@
                 onCompleteSignal.executeAllAndDestroy();
             }
 
-            executeCallbacksTask(
-                    c -> {
-                        if (!enableWorkspaceInflation()) {
-                            MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-                        }
-                        c.onInitialBindComplete(currentScreenIds, pendingTasks, onCompleteSignal,
-                                workspaceItemCount, isBindSync);
-                    }, mUiExecutor);
+            executeCallbacksTask(c -> c.onInitialBindComplete(currentScreenIds, pendingTasks,
+                    onCompleteSignal, workspaceItemCount, isBindSync), mUiExecutor);
         }
 
         private void setupPendingBind(
@@ -367,12 +352,8 @@
             executeCallbacksTask(c -> c.bindStringCache(cacheClone), pendingExecutor);
 
             executeCallbacksTask(c -> c.finishBindingItems(currentScreenIds), pendingExecutor);
-            pendingExecutor.execute(
-                    () -> {
-                        MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
-                        ItemInstallQueue.INSTANCE.get(mApp.getContext())
-                                .resumeModelPush(FLAG_LOADER_RUNNING);
-                    });
+            pendingExecutor.execute(() -> ItemInstallQueue.INSTANCE.get(mApp.getContext())
+                    .resumeModelPush(FLAG_LOADER_RUNNING));
         }
 
         /**
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index b9b1e98..d9eccaf 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -20,6 +20,11 @@
 import static com.android.launcher3.BuildConfig.QSB_ON_FIRST_SCREEN;
 import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
 import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
 import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
 import static com.android.launcher3.shortcuts.ShortcutRequest.PINNED;
 
@@ -31,7 +36,6 @@
 import android.content.pm.ShortcutInfo;
 import android.os.UserHandle;
 import android.text.TextUtils;
-import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 import android.view.View;
@@ -39,16 +43,13 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.BuildConfig;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.AppPairInfo;
 import com.android.launcher3.model.data.CollectionInfo;
-import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.shortcuts.ShortcutKey;
@@ -69,7 +70,6 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -93,22 +93,6 @@
     public final IntSparseArrayMap<ItemInfo> itemsIdMap = new IntSparseArrayMap<>();
 
     /**
-     * List of all the folders and shortcuts directly on the home screen (no widgets
-     * or shortcuts within folders).
-     */
-    public final ArrayList<ItemInfo> workspaceItems = new ArrayList<>();
-
-    /**
-     * All LauncherAppWidgetInfo created by LauncherModel.
-     */
-    public final ArrayList<LauncherAppWidgetInfo> appWidgets = new ArrayList<>();
-
-    /**
-     * Map of id to CollectionInfos of all the folders or app pairs created by LauncherModel
-     */
-    public final IntSparseArrayMap<CollectionInfo> collections = new IntSparseArrayMap<>();
-
-    /**
      * Extra container based items
      */
     public final IntSparseArrayMap<FixedContainerItems> extraItems = new IntSparseArrayMap<>();
@@ -144,9 +128,6 @@
      * Clears all the data
      */
     public synchronized void clear() {
-        workspaceItems.clear();
-        appWidgets.clear();
-        collections.clear();
         itemsIdMap.clear();
         deepShortcutMap.clear();
         extraItems.clear();
@@ -158,7 +139,7 @@
     public synchronized IntArray collectWorkspaceScreens() {
         IntSet screenSet = new IntSet();
         for (ItemInfo item: itemsIdMap) {
-            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
+            if (item.container == CONTAINER_DESKTOP) {
                 screenSet.add(item.screenId);
             }
         }
@@ -173,26 +154,14 @@
     public synchronized void dump(String prefix, FileDescriptor fd, PrintWriter writer,
             String[] args) {
         writer.println(prefix + "Data Model:");
-        writer.println(prefix + " ---- workspace items ");
-        for (int i = 0; i < workspaceItems.size(); i++) {
-            writer.println(prefix + '\t' + workspaceItems.get(i).toString());
-        }
-        writer.println(prefix + " ---- appwidget items ");
-        for (int i = 0; i < appWidgets.size(); i++) {
-            writer.println(prefix + '\t' + appWidgets.get(i).toString());
-        }
-        writer.println(prefix + " ---- collection items ");
-        for (int i = 0; i < collections.size(); i++) {
-            writer.println(prefix + '\t' + collections.valueAt(i).toString());
+        writer.println(prefix + " ---- items id map ");
+        for (int i = 0; i < itemsIdMap.size(); i++) {
+            writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
         }
         writer.println(prefix + " ---- extra items ");
         for (int i = 0; i < extraItems.size(); i++) {
             writer.println(prefix + '\t' + extraItems.valueAt(i).toString());
         }
-        writer.println(prefix + " ---- items id map ");
-        for (int i = 0; i < itemsIdMap.size(); i++) {
-            writer.println(prefix + '\t' + itemsIdMap.valueAt(i).toString());
-        }
 
         if (args.length > 0 && TextUtils.equals(args[0], "--all")) {
             writer.println(prefix + "shortcut counts ");
@@ -207,98 +176,42 @@
         removeItem(context, Arrays.asList(items));
     }
 
-    public synchronized void removeItem(Context context, Iterable<? extends ItemInfo> items) {
-        ArraySet<UserHandle> updatedDeepShortcuts = new ArraySet<>();
-        for (ItemInfo item : items) {
-            switch (item.itemType) {
-                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
-                    collections.remove(item.id);
-                    if (FeatureFlags.IS_STUDIO_BUILD) {
-                        for (ItemInfo info : itemsIdMap) {
-                            if (info.container == item.id) {
-                                // We are deleting a collection which still contains items that
-                                // think they are contained by that collection.
-                                String msg = "deleting a collection (" + item + ") which still "
-                                        + "contains items (" + info + ")";
-                                Log.e(TAG, msg);
-                            }
-                        }
-                    }
-                    workspaceItems.remove(item);
-                    break;
-                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
-                    updatedDeepShortcuts.add(item.user);
-                    // Fall through.
-                }
-                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                    workspaceItems.remove(item);
-                    break;
-                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-                case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
-                    appWidgets.remove(item);
-                    break;
-            }
-            itemsIdMap.remove(item.id);
+    public synchronized void removeItem(Context context, List<? extends ItemInfo> items) {
+        if (BuildConfig.IS_STUDIO_BUILD) {
+            items.stream()
+                    .filter(item -> item.itemType == ITEM_TYPE_FOLDER
+                            || item.itemType == ITEM_TYPE_APP_PAIR)
+                    .forEach(item -> itemsIdMap.stream()
+                            .filter(info -> info.container == item.id)
+                            // We are deleting a collection which still contains items that
+                            // think they are contained by that collection.
+                            .forEach(info -> Log.e(TAG,
+                                    "deleting a collection (" + item + ") which still contains"
+                                            + " items (" + info + ")")));
         }
-        updatedDeepShortcuts.forEach(user -> updateShortcutPinnedState(context, user));
+
+        items.forEach(item -> itemsIdMap.remove(item.id));
+        items.stream().map(info -> info.user).distinct().forEach(
+                user -> updateShortcutPinnedState(context, user));
     }
 
     public synchronized void addItem(Context context, ItemInfo item, boolean newItem) {
-        addItem(context, item, newItem, null);
-    }
-
-    public synchronized void addItem(
-            Context context, ItemInfo item, boolean newItem, @Nullable LoaderMemoryLogger logger) {
-        if (logger != null) {
-            logger.addLog(
-                    Log.DEBUG,
-                    TAG,
-                    String.format("Adding item to ID map: %s", item.toString()),
-                    /* stackTrace= */ null);
-        }
         itemsIdMap.put(item.id, item);
-        switch (item.itemType) {
-            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
-                collections.put(item.id, (FolderInfo) item);
-                workspaceItems.add(item);
-                break;
-            case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
-                collections.put(item.id, (AppPairInfo) item);
-                // Fall through here. App pairs are both containers (like folders) and containable
-                // items (can be placed in folders). So we need to add app pairs to the folders
-                // array (above) but also verify the existence of their container, like regular
-                // apps (below).
-            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
-                if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
-                        item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                    workspaceItems.add(item);
-                } else {
-                    if (newItem) {
-                        if (!collections.containsKey(item.container)) {
-                            // Adding an item to a nonexistent collection.
-                            String msg = "attempted to add item: " + item + " to a nonexistent app"
-                                    + " collection";
-                            Log.e(TAG, msg);
-                        }
-                    } else {
-                        findOrMakeFolder(item.container).add(item);
-                    }
-                }
-                break;
-            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
-            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
-                appWidgets.add((LauncherAppWidgetInfo) item);
-                break;
-        }
-        if (newItem && item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+        if (newItem && item.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
             updateShortcutPinnedState(context, item.user);
         }
+        if (BuildConfig.IS_DEBUG_DEVICE
+                && newItem
+                && item.container != CONTAINER_DESKTOP
+                && item.container != CONTAINER_HOTSEAT
+                && !(itemsIdMap.get(item.container) instanceof CollectionInfo)) {
+            // Adding an item to a nonexistent collection.
+            Log.e(TAG, "attempted to add item: " + item + " to a nonexistent app collection");
+        }
     }
 
     /**
-     * Updates the deep shortucts state in system to match out internal model, pinning any missing
+     * Updates the deep shortcuts state in system to match out internal model, pinning any missing
      * shortcuts and unpinning any extra shortcuts.
      */
     public void updateShortcutPinnedState(Context context) {
@@ -334,7 +247,7 @@
         Map<String, Set<String>> modelMap = Stream.concat(
                     // Model shortcuts
                     itemStream.build()
-                        .filter(wi -> wi.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
+                        .filter(wi -> wi.itemType == ITEM_TYPE_DEEP_SHORTCUT)
                         .map(ShortcutKey::fromItemInfo),
                     // Pending shortcuts
                     ItemInstallQueue.INSTANCE.get(context).getPendingShortcuts(user))
@@ -354,6 +267,8 @@
                     || !systemShortcuts.containsAll(modelShortcuts)) {
                 // Update system state for this package
                 try {
+                    FileLog.d(TAG, "updateShortcutPinnedState:"
+                            + " Pinning Shortcuts: " + entry.getKey() + ": " + modelShortcuts);
                     context.getSystemService(LauncherApps.class).pinShortcuts(
                             entry.getKey(), new ArrayList<>(modelShortcuts), user);
                 } catch (SecurityException | IllegalStateException e) {
@@ -366,6 +281,9 @@
         systemMap.keySet().forEach(packageName -> {
             // Update system state
             try {
+                FileLog.d(TAG, "updateShortcutPinnedState:"
+                        + " Unpinning extra Shortcuts for package: " + packageName
+                        + ": " + systemMap.get(packageName));
                 context.getSystemService(LauncherApps.class).pinShortcuts(
                         packageName, Collections.emptyList(), user);
             } catch (SecurityException | IllegalStateException e) {
@@ -375,24 +293,6 @@
     }
 
     /**
-     * Return an existing FolderInfo object if we have encountered this ID previously,
-     * or make a new one.
-     */
-    public synchronized CollectionInfo findOrMakeFolder(int id) {
-        // See if a placeholder was created for us already
-        CollectionInfo collectionInfo = collections.get(id);
-        if (collectionInfo == null) {
-            // No placeholder -- create a new blank folder instance. At this point, we don't know
-            // if the desired container is supposed to be a folder or an app pair. In the case that
-            // it is an app pair, the blank folder will be replaced by a blank app pair when the app
-            // pair is getting processed, in WorkspaceItemProcessor.processFolderOrAppPair().
-            collectionInfo = new FolderInfo();
-            collections.put(id, collectionInfo);
-        }
-        return collectionInfo;
-    }
-
-    /**
      * Clear all the deep shortcut counts for the given package, and re-add the new shortcut counts.
      */
     public synchronized void updateDeepShortcutCounts(
@@ -424,16 +324,6 @@
     }
 
     /**
-     * Returns a list containing all workspace items including widgets.
-     */
-    public synchronized ArrayList<ItemInfo> getAllWorkspaceItems() {
-        ArrayList<ItemInfo> items = new ArrayList<>(workspaceItems.size() + appWidgets.size());
-        items.addAll(workspaceItems);
-        items.addAll(appWidgets);
-        return items;
-    }
-
-    /**
      * Calls the provided {@code op} for all workspaceItems in the in-memory model (both persisted
      * items and dynamic/predicted items for the provided {@code userHandle}.
      * Note the call is not synchronized over the model, that should be handled by the called.
@@ -533,17 +423,16 @@
          * Binds updated incremental download progress
          */
         default void bindIncrementalDownloadProgressUpdated(AppInfo app) { }
-        default void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) { }
-        default void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
-        default void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
+
+        /** Called when a runtime property of the ItemInfo is updated due to some system event */
+        default void bindItemsUpdated(Set<ItemInfo> updates) { }
         default void bindWorkspaceComponentsRemoved(Predicate<ItemInfo> matcher) { }
 
         /**
          * Binds the app widgets to the providers that share widgets with the UI.
          */
-        default void bindAllWidgets(@NonNull List<WidgetsListBaseEntry> widgets,
-                @NonNull List<WidgetsListBaseEntry> defaultWidgets) {
-        }
+        default void bindAllWidgets(@NonNull List<WidgetsListBaseEntry> widgets) { }
+
         default void bindSmartspaceWidget() { }
 
         /** Called when workspace has been bound. */
diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
index 66b4fd9..48934e2 100644
--- a/src/com/android/launcher3/model/CacheDataUpdatedTask.java
+++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
+
 import android.content.ComponentName;
 import android.os.UserHandle;
 
@@ -23,6 +26,8 @@
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.icons.IconCache;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 
 import java.util.ArrayList;
@@ -55,7 +60,7 @@
     public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
             @NonNull AllAppsList apps) {
         IconCache iconCache = taskController.getApp().getIconCache();
-        ArrayList<WorkspaceItemInfo> updatedShortcuts = new ArrayList<>();
+        ArrayList<ItemInfo> updatedItems = new ArrayList<>();
 
         synchronized (dataModel) {
             dataModel.forAllWorkspaceItemInfos(mUser, si -> {
@@ -63,13 +68,26 @@
                 if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
                         && isValidShortcut(si) && cn != null
                         && mPackages.contains(cn.getPackageName())) {
-                    iconCache.getTitleAndIcon(si, si.usingLowResIcon());
-                    updatedShortcuts.add(si);
+                    iconCache.getTitleAndIcon(si, si.getMatchingLookupFlag());
+                    updatedItems.add(si);
                 }
             });
+
+            dataModel.itemsIdMap.stream()
+                    .filter(WIDGET_FILTER)
+                    .filter(item -> mUser.equals(item.user))
+                    .map(item -> (LauncherAppWidgetInfo) item)
+                    .filter(widget -> mPackages.contains(widget.providerName.getPackageName())
+                            && widget.pendingItemInfo != null)
+                    .forEach(widget -> {
+                        iconCache.getTitleAndIconForApp(
+                                widget.pendingItemInfo, DEFAULT_LOOKUP_FLAG);
+                        updatedItems.add(widget);
+                    });
+
             apps.updateIconsAndLabels(mPackages, mUser);
         }
-        taskController.bindUpdatedWorkspaceItems(updatedShortcuts);
+        taskController.bindUpdatedWorkspaceItems(updatedItems);
         taskController.bindApplicationsIfNeeded();
     }
 
diff --git a/src/com/android/launcher3/model/DbEntry.kt b/src/com/android/launcher3/model/DbEntry.kt
index b79d312..da9779e 100644
--- a/src/com/android/launcher3/model/DbEntry.kt
+++ b/src/com/android/launcher3/model/DbEntry.kt
@@ -30,7 +30,6 @@
 import com.android.launcher3.LauncherSettings.Favorites.SPANY
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.util.ContentWriter
-import java.net.URISyntaxException
 import java.util.Objects
 
 class DbEntry : ItemInfo(), Comparable<DbEntry> {
@@ -129,8 +128,8 @@
     private fun cleanIntentString(intentStr: String): String {
         try {
             return Intent.parseUri(intentStr, 0).apply { sourceBounds = null }.toURI()
-        } catch (e: URISyntaxException) {
-            Log.e(TAG, "Unable to parse Intent string", e)
+        } catch (e: Exception) {
+            Log.e(TAG, "Unable to parse Intent string: $intentStr", e)
             return intentStr
         }
     }
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 90af215..96ce4c8 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -21,11 +21,6 @@
 import static com.android.launcher3.LauncherPrefs.DEVICE_TYPE;
 import static com.android.launcher3.LauncherPrefs.HOTSEAT_COUNT;
 import static com.android.launcher3.LauncherPrefs.WORKSPACE_SIZE;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_2;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_3;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_4;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_5;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_6;
 
 import android.content.Context;
 import android.text.TextUtils;
@@ -33,7 +28,6 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.logging.StatsLogManager.LauncherEvent;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
 
 import java.util.Locale;
 import java.util.Objects;
@@ -68,7 +62,10 @@
     }
 
     public DeviceGridState(Context context) {
-        LauncherPrefs lp = LauncherPrefs.get(context);
+        this(LauncherPrefs.get(context));
+    }
+
+    public DeviceGridState(LauncherPrefs lp) {
         mGridSizeString = lp.get(WORKSPACE_SIZE);
         mNumHotseat = lp.get(HOTSEAT_COUNT);
         mDeviceType = lp.get(DEVICE_TYPE);
@@ -100,9 +97,6 @@
      * Stores the device state to shared preferences
      */
     public void writeToPrefs(Context context) {
-        if (context instanceof SandboxContext) {
-            return;
-        }
         LauncherPrefs.get(context).put(
                 WORKSPACE_SIZE.to(mGridSizeString),
                 HOTSEAT_COUNT.to(mNumHotseat),
@@ -115,17 +109,23 @@
      */
     public LauncherEvent getWorkspaceSizeEvent() {
         if (!TextUtils.isEmpty(mGridSizeString)) {
-            switch (getColumns()) {
-                case 6:
-                    return LAUNCHER_GRID_SIZE_6;
-                case 5:
-                    return LAUNCHER_GRID_SIZE_5;
-                case 4:
-                    return LAUNCHER_GRID_SIZE_4;
-                case 3:
-                    return LAUNCHER_GRID_SIZE_3;
-                case 2:
-                    return LAUNCHER_GRID_SIZE_2;
+            switch (mGridSizeString) {
+                case "2,2":
+                    return LauncherEvent.LAUNCHER_GRID_SIZE_2_BY_2;
+                case "3,3":
+                    return LauncherEvent.LAUNCHER_GRID_SIZE_3_BY_3;
+                case "4,4":
+                    return LauncherEvent.LAUNCHER_GRID_SIZE_4_BY_4;
+                case "4,5":
+                    return LauncherEvent.LAUNCHER_GRID_SIZE_4_BY_5;
+                case "4,6":
+                    return LauncherEvent.LAUNCHER_GRID_SIZE_4_BY_6;
+                case "5,5":
+                    return LauncherEvent.LAUNCHER_GRID_SIZE_5_BY_5;
+                case "5,6":
+                    return LauncherEvent.LAUNCHER_GRID_SIZE_5_BY_6;
+                case "6,5":
+                    return LauncherEvent.LAUNCHER_GRID_SIZE_6_BY_5;
             }
         }
         return null;
diff --git a/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
index aa62c32..6ad52ea 100644
--- a/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
+++ b/src/com/android/launcher3/model/FirstScreenBroadcastHelper.kt
@@ -30,7 +30,6 @@
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.model.data.LauncherAppWidgetInfo
 import com.android.launcher3.model.data.WorkspaceItemInfo
-import com.android.launcher3.pm.InstallSessionHelper
 import com.android.launcher3.util.Executors
 import com.android.launcher3.util.PackageManagerHelper
 import com.android.launcher3.util.PackageUserKey
@@ -80,21 +79,22 @@
         packageManagerHelper: PackageManagerHelper,
         firstScreenItems: List<ItemInfo>,
         userKeyToSessionMap: Map<PackageUserKey, SessionInfo>,
-        allWidgets: List<LauncherAppWidgetInfo>
+        allWidgets: List<ItemInfo>,
     ): List<FirstScreenBroadcastModel> {
 
         // installers for installing items
-        val pendingItemInstallerMap: Map<String, MutableSet<String>> =
+        val pendingItemInstallerMap: Map<String, Set<String>> =
             createPendingItemsMap(userKeyToSessionMap)
+
         val installingPackages = pendingItemInstallerMap.values.flatten().toSet()
 
         // installers for installed items on first screen
-        val installedItemInstallerMap: Map<String, MutableSet<ItemInfo>> =
+        val installedItemInstallerMap: Map<String, List<ItemInfo>> =
             createInstalledItemsMap(firstScreenItems, installingPackages, packageManagerHelper)
 
         // installers for widgets on all screens
-        val allInstalledWidgetsMap: Map<String, MutableSet<LauncherAppWidgetInfo>> =
-            createAllInstalledWidgetsMap(allWidgets, installingPackages, packageManagerHelper)
+        val allInstalledWidgetsMap: Map<String, List<ItemInfo>> =
+            createInstalledItemsMap(allWidgets, installingPackages, packageManagerHelper)
 
         val allInstallers: Set<String> =
             pendingItemInstallerMap.keys +
@@ -131,39 +131,39 @@
                             context,
                             0 /* requestCode */,
                             Intent(),
-                            PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
-                        )
+                            PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE,
+                        ),
                     )
                     .putStringArrayListExtra(
                         PENDING_COLLECTION_ITEM_EXTRA,
-                        ArrayList(model.pendingCollectionItems)
+                        ArrayList(model.pendingCollectionItems),
                     )
                     .putStringArrayListExtra(
                         PENDING_WORKSPACE_ITEM_EXTRA,
-                        ArrayList(model.pendingWorkspaceItems)
+                        ArrayList(model.pendingWorkspaceItems),
                     )
                     .putStringArrayListExtra(
                         PENDING_HOTSEAT_ITEM_EXTRA,
-                        ArrayList(model.pendingHotseatItems)
+                        ArrayList(model.pendingHotseatItems),
                     )
                     .putStringArrayListExtra(
                         PENDING_WIDGET_ITEM_EXTRA,
-                        ArrayList(model.pendingWidgetItems)
+                        ArrayList(model.pendingWidgetItems),
                     )
                     .putStringArrayListExtra(
                         INSTALLED_WORKSPACE_ITEMS_EXTRA,
-                        ArrayList(model.installedWorkspaceItems)
+                        ArrayList(model.installedWorkspaceItems),
                     )
                     .putStringArrayListExtra(
                         INSTALLED_HOTSEAT_ITEMS_EXTRA,
-                        ArrayList(model.installedHotseatItems)
+                        ArrayList(model.installedHotseatItems),
                     )
                     .putStringArrayListExtra(
                         ALL_INSTALLED_WIDGETS_ITEM_EXTRA,
                         ArrayList(
                             model.firstScreenInstalledWidgets +
                                 model.secondaryScreenInstalledWidgets
-                        )
+                        ),
                     )
             context.sendBroadcast(intent)
         }
@@ -172,66 +172,46 @@
     /** Maps Installer packages to Set of app packages from install sessions */
     private fun createPendingItemsMap(
         userKeyToSessionMap: Map<PackageUserKey, SessionInfo>
-    ): Map<String, MutableSet<String>> {
+    ): Map<String, Set<String>> {
         val myUser = Process.myUserHandle()
-        val result = mutableMapOf<String, MutableSet<String>>()
-        userKeyToSessionMap.forEach { entry ->
-            if (!myUser.equals(InstallSessionHelper.getUserHandle(entry.value))) return@forEach
-            val installer = entry.value.installerPackageName
-            val appPackage = entry.value.appPackageName
-            if (installer.isNullOrEmpty() || appPackage.isNullOrEmpty()) return@forEach
-            result.getOrPut(installer) { mutableSetOf() }.add(appPackage)
-        }
-        return result
-    }
-
-    /**
-     * Maps Installer packages to Set of ItemInfo from first screen. Filter out installing packages.
-     */
-    private fun createInstalledItemsMap(
-        firstScreenItems: List<ItemInfo>,
-        installingPackages: Set<String>,
-        packageManagerHelper: PackageManagerHelper
-    ): Map<String, MutableSet<ItemInfo>> {
-        val result = mutableMapOf<String, MutableSet<ItemInfo>>()
-        firstScreenItems.forEach { item ->
-            val appPackage = getPackageName(item) ?: return@forEach
-            if (installingPackages.contains(appPackage)) return@forEach
-            val installer = packageManagerHelper.getAppInstallerPackage(appPackage)
-            if (installer.isNullOrEmpty()) return@forEach
-            result.getOrPut(installer) { mutableSetOf() }.add(item)
-        }
-        return result
-    }
-
-    /**
-     * Maps Installer packages to Set of AppWidget packages installed on all screens. Filter out
-     * installing packages.
-     */
-    private fun createAllInstalledWidgetsMap(
-        allWidgets: List<LauncherAppWidgetInfo>,
-        installingPackages: Set<String>,
-        packageManagerHelper: PackageManagerHelper
-    ): Map<String, MutableSet<LauncherAppWidgetInfo>> {
-        val result = mutableMapOf<String, MutableSet<LauncherAppWidgetInfo>>()
-        allWidgets
-            .sortedBy { widget -> widget.screenId }
-            .forEach { widget ->
-                val appPackage = getPackageName(widget) ?: return@forEach
-                if (installingPackages.contains(appPackage)) return@forEach
-                val installer = packageManagerHelper.getAppInstallerPackage(appPackage)
-                if (installer.isNullOrEmpty()) return@forEach
-                result.getOrPut(installer) { mutableSetOf() }.add(widget)
+        return userKeyToSessionMap.values
+            .filter {
+                it.user == myUser &&
+                    !it.installerPackageName.isNullOrEmpty() &&
+                    !it.appPackageName.isNullOrEmpty()
             }
-        return result
+            .groupBy(
+                keySelector = { it.installerPackageName },
+                valueTransform = { it.appPackageName },
+            )
+            .mapValues { it.value.filterNotNull().toSet() } as Map<String, Set<String>>
     }
 
+    /** Maps Installer packages to Set of ItemInfos. Filter out installing packages. */
+    private fun createInstalledItemsMap(
+        allItems: Iterable<ItemInfo>,
+        installingPackages: Set<String>,
+        packageManagerHelper: PackageManagerHelper,
+    ): Map<String, List<ItemInfo>> =
+        allItems
+            .sortedBy { it.screenId }
+            .groupByTo(mutableMapOf()) {
+                getPackageName(it)?.let { pkg ->
+                    if (installingPackages.contains(pkg)) {
+                        null
+                    } else {
+                        packageManagerHelper.getAppInstallerPackage(pkg)
+                    }
+                }
+            }
+            .apply { remove(null) } as Map<String, List<ItemInfo>>
+
     /**
      * Add first screen Pending Items from Map to [FirstScreenBroadcastModel] for given installer
      */
     private fun FirstScreenBroadcastModel.addPendingItems(
         installingItems: Set<String>?,
-        firstScreenItems: List<ItemInfo>
+        firstScreenItems: List<ItemInfo>,
     ) {
         if (installingItems == null) return
         for (info in firstScreenItems) {
@@ -251,7 +231,7 @@
      */
     private fun FirstScreenBroadcastModel.addInstalledItems(
         installer: String,
-        installedItemInstallerMap: Map<String, Set<ItemInfo>>,
+        installedItemInstallerMap: Map<String, List<ItemInfo>>,
     ) {
         installedItemInstallerMap[installer]?.forEach { info ->
             val packageName: String = getPackageName(info) ?: return@forEach
@@ -265,7 +245,7 @@
     /** Add Widgets on every screen from Map to [FirstScreenBroadcastModel] for given installer */
     private fun FirstScreenBroadcastModel.addAllScreenWidgets(
         installer: String,
-        allInstalledWidgetsMap: Map<String, Set<LauncherAppWidgetInfo>>
+        allInstalledWidgetsMap: Map<String, List<ItemInfo>>,
     ) {
         allInstalledWidgetsMap[installer]?.forEach { widget ->
             val packageName: String = getPackageName(widget) ?: return@forEach
@@ -279,7 +259,7 @@
 
     private fun FirstScreenBroadcastModel.addCollectionItems(
         info: ItemInfo,
-        installingPackages: Set<String>
+        installingPackages: Set<String>,
     ) {
         if (info !is CollectionInfo) return
         pendingCollectionItems.addAll(
@@ -336,7 +316,7 @@
             Log.d(
                 TAG,
                 "Sending First Screen Broadcast for installer=$installerPackage" +
-                    ", total packages=${getTotalItemCount()}"
+                    ", total packages=${getTotalItemCount()}",
             )
             pendingCollectionItems.forEach {
                 Log.d(TAG, "$installerPackage:Pending Collection item:$it")
@@ -361,15 +341,7 @@
         }
     }
 
-    private fun getPackageName(info: ItemInfo): String? {
-        var packageName: String? = null
-        if (info is LauncherAppWidgetInfo) {
-            info.providerName?.let { packageName = info.providerName.packageName }
-        } else if (info.targetComponent != null) {
-            packageName = info.targetComponent?.packageName
-        }
-        return packageName
-    }
+    private fun getPackageName(info: ItemInfo): String? = info.targetComponent?.packageName
 
     /**
      * Clone the provided list on UI thread. This is used for [FolderInfo.getContents] which is
diff --git a/src/com/android/launcher3/model/GridSizeMigrationDBController.java b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
index 03f5727..5d0a7bd 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationDBController.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationDBController.java
@@ -17,14 +17,13 @@
 package com.android.launcher3.model;
 
 import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
-import static com.android.launcher3.Flags.oneGridSpecs;
 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE;
 import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
 import static com.android.launcher3.model.LoaderTask.SMARTSPACE_ON_HOME_SCREEN;
 import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
-import static com.android.launcher3.provider.LauncherDbUtils.shiftTableByXCells;
+import static com.android.launcher3.provider.LauncherDbUtils.shiftWorkspaceByXCells;
 
 import android.content.ComponentName;
 import android.content.ContentValues;
@@ -39,6 +38,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.LauncherSettings;
@@ -120,51 +120,52 @@
             @NonNull DeviceGridState destDeviceState,
             @NonNull DatabaseHelper target,
             @NonNull SQLiteDatabase source,
-            boolean isDestNewDb) {
+            boolean isDestNewDb,
+            ModelDelegate modelDelegate) {
 
         if (!needsToMigrate(srcDeviceState, destDeviceState)) {
             return true;
         }
 
-        if (isDestNewDb
+        boolean shouldMigrateToStrictlyTallerGrid = (Flags.oneGridSpecs() || isDestNewDb)
                 && srcDeviceState.getColumns().equals(destDeviceState.getColumns())
-                && srcDeviceState.getRows() < destDeviceState.getRows()) {
-            // Only use this strategy when comparing the previous grid to the new grid and the
-            // columns are the same and the destination has more rows
+                && srcDeviceState.getRows() < destDeviceState.getRows();
+        if (shouldMigrateToStrictlyTallerGrid) {
             copyTable(source, TABLE_NAME, target.getWritableDatabase(), TABLE_NAME, context);
+        } else {
+            copyTable(source, TABLE_NAME, target.getWritableDatabase(), TMP_TABLE, context);
+        }
 
-            if (oneGridSpecs()) {
-                DbReader destReader = new DbReader(
-                        target.getWritableDatabase(), TABLE_NAME, context);
-                boolean shouldShiftCells = shouldShiftCells(destReader, srcDeviceState.getRows());
-                if (shouldShiftCells) {
-                    shiftTableByXCells(
+        long migrationStartTime = System.currentTimeMillis();
+        try (SQLiteTransaction t = new SQLiteTransaction(target.getWritableDatabase())) {
+
+            if (shouldMigrateToStrictlyTallerGrid) {
+                // We want to add the extra row(s) to the top of the screen, so we shift the grid
+                // down.
+                if (Flags.oneGridSpecs()) {
+                    shiftWorkspaceByXCells(
                             target.getWritableDatabase(),
                             (destDeviceState.getRows() - srcDeviceState.getRows()),
                             TABLE_NAME);
                 }
+
+                // Save current configuration, so that the migration does not run again.
+                destDeviceState.writeToPrefs(context);
+                t.commit();
+                return true;
             }
 
-            // Save current configuration, so that the migration does not run again.
-            destDeviceState.writeToPrefs(context);
-            return true;
-        }
-        copyTable(source, TABLE_NAME, target.getWritableDatabase(), TMP_TABLE, context);
-
-        long migrationStartTime = System.currentTimeMillis();
-        try (SQLiteTransaction t = new SQLiteTransaction(target.getWritableDatabase())) {
             DbReader srcReader = new DbReader(t.getDb(), TMP_TABLE, context);
             DbReader destReader = new DbReader(t.getDb(), TABLE_NAME, context);
 
             Point targetSize = new Point(destDeviceState.getColumns(), destDeviceState.getRows());
-            migrate(target, srcReader, destReader, destDeviceState.getNumHotseat(),
-                    targetSize, srcDeviceState, destDeviceState);
+            migrate(target, srcReader, destReader, srcDeviceState.getNumHotseat(),
+                    destDeviceState.getNumHotseat(), targetSize, srcDeviceState, destDeviceState);
             dropTable(t.getDb(), TMP_TABLE);
             t.commit();
             return true;
         } catch (Exception e) {
             Log.e(TAG, "Error during grid migration", e);
-
             return false;
         } finally {
             Log.v(TAG, "Workspace migration completed in "
@@ -172,25 +173,35 @@
 
             // Save current configuration, so that the migration does not run again.
             destDeviceState.writeToPrefs(context);
+            // Notify if we've migrated successfully
+            modelDelegate.gridMigrationComplete(srcDeviceState, destDeviceState);
         }
     }
 
     public static boolean migrate(
             @NonNull DatabaseHelper helper,
             @NonNull final DbReader srcReader, @NonNull final DbReader destReader,
-            final int destHotseatSize, @NonNull final Point targetSize,
+            final int srcHotseatSize, final int destHotseatSize, @NonNull final Point targetSize,
             @NonNull final DeviceGridState srcDeviceState,
             @NonNull final DeviceGridState destDeviceState) {
 
         final List<DbEntry> srcHotseatItems = srcReader.loadHotseatEntries();
         final List<DbEntry> srcWorkspaceItems = srcReader.loadAllWorkspaceEntries();
         final List<DbEntry> dstHotseatItems = destReader.loadHotseatEntries();
+        // We want to filter out the hotseat items that are placed beyond the size of the source
+        // grid as we always want to keep those extra items from the destination grid.
+        List<DbEntry> filteredDstHotseatItems = dstHotseatItems;
+        if (srcHotseatSize < destHotseatSize) {
+            filteredDstHotseatItems = filteredDstHotseatItems.stream()
+                    .filter(entry -> entry.screenId < srcHotseatSize)
+                    .collect(Collectors.toList());
+        }
         final List<DbEntry> dstWorkspaceItems = destReader.loadAllWorkspaceEntries();
         final List<DbEntry> hotseatToBeAdded = new ArrayList<>(1);
         final List<DbEntry> workspaceToBeAdded = new ArrayList<>(1);
         final IntArray toBeRemoved = new IntArray();
 
-        calcDiff(srcHotseatItems, dstHotseatItems, hotseatToBeAdded, toBeRemoved);
+        calcDiff(srcHotseatItems, filteredDstHotseatItems, hotseatToBeAdded, toBeRemoved);
         calcDiff(srcWorkspaceItems, dstWorkspaceItems, workspaceToBeAdded, toBeRemoved);
 
         final int trgX = targetSize.x;
@@ -227,16 +238,19 @@
         Collections.sort(hotseatToBeAdded);
         Collections.sort(workspaceToBeAdded);
 
-        List<Integer> idsInUse = dstWorkspaceItems.stream()
+        List<DbEntry> remainingDstHotseatItems = destReader.loadHotseatEntries();
+        List<DbEntry> remainingDstWorkspaceItems = destReader.loadAllWorkspaceEntries();
+        List<Integer> idsInUse = remainingDstHotseatItems.stream()
                 .map(entry -> entry.id)
                 .collect(Collectors.toList());
-        idsInUse.addAll(dstHotseatItems.stream()
+        idsInUse.addAll(remainingDstWorkspaceItems.stream()
                 .map(entry -> entry.id)
                 .collect(Collectors.toList()));
 
+
         // Migrate hotseat
         solveHotseatPlacement(helper, destHotseatSize,
-                srcReader, destReader, dstHotseatItems, hotseatToBeAdded, idsInUse);
+                srcReader, destReader, remainingDstHotseatItems, hotseatToBeAdded, idsInUse);
 
         // Migrate workspace.
         // First we create a collection of the screens
@@ -422,12 +436,13 @@
     }
 
     private static void solveHotseatPlacement(
-            @NonNull final DatabaseHelper helper, final int hotseatSize,
+            @NonNull final DatabaseHelper helper,
+            final int dstHotseatSize,
             @NonNull final DbReader srcReader, @NonNull final DbReader destReader,
             @NonNull final List<DbEntry> placedHotseatItems,
             @NonNull final List<DbEntry> itemsToPlace, List<Integer> idsInUse) {
 
-        final boolean[] occupied = new boolean[hotseatSize];
+        final boolean[] occupied = new boolean[dstHotseatSize];
         for (DbEntry entry : placedHotseatItems) {
             occupied[entry.screenId] = true;
         }
@@ -447,22 +462,6 @@
         }
     }
 
-    private static boolean shouldShiftCells(final DbReader destReader, final int srcGridRowCount) {
-        List<DbEntry> workspaceItems = destReader.loadAllWorkspaceEntries();
-        int firstPageItemsRowPosSum = workspaceItems.stream()
-                .filter(entry -> entry.screenId == 0)
-                .mapToInt(entry -> entry.cellY).sum();
-        int firstPageWorkspaceItemsCount = (int) workspaceItems.stream()
-                .filter(entry -> entry.screenId == 0).count();
-        if (firstPageWorkspaceItemsCount == 0) {
-            return false;
-        }
-        float srcGridMidPoint = srcGridRowCount / 2f;
-        float firstPageItemPosAvg = (float) firstPageItemsRowPosSum / firstPageWorkspaceItemsCount;
-        return (firstPageItemPosAvg >= srcGridMidPoint);
-    }
-
-
     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
     public static class DbReader {
 
@@ -471,7 +470,7 @@
         final Context mContext;
         int mLastScreenId = -1;
 
-        final Map<Integer, List<DbEntry>> mWorkspaceEntriesByScreenId =
+        Map<Integer, List<DbEntry>> mWorkspaceEntriesByScreenId =
                 new ArrayMap<>();
 
         public DbReader(SQLiteDatabase db, String tableName, Context context) {
@@ -543,6 +542,7 @@
         }
 
         protected List<DbEntry> loadAllWorkspaceEntries() {
+            mWorkspaceEntriesByScreenId.clear();
             final List<DbEntry> workspaceEntries = new ArrayList<>();
             Cursor c = queryWorkspace(
                     new String[]{
diff --git a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
index 75fd31e..5df135a 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
+++ b/src/com/android/launcher3/model/GridSizeMigrationLogic.kt
@@ -21,7 +21,6 @@
 import android.util.Log
 import androidx.annotation.VisibleForTesting
 import com.android.launcher3.Flags
-import com.android.launcher3.Flags.oneGridSpecs
 import com.android.launcher3.LauncherPrefs
 import com.android.launcher3.LauncherPrefs.Companion.get
 import com.android.launcher3.LauncherPrefs.Companion.getPrefs
@@ -30,11 +29,12 @@
 import com.android.launcher3.LauncherSettings.Favorites.TMP_TABLE
 import com.android.launcher3.Utilities
 import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.logging.FileLog
 import com.android.launcher3.model.GridSizeMigrationDBController.DbReader
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction
 import com.android.launcher3.provider.LauncherDbUtils.copyTable
 import com.android.launcher3.provider.LauncherDbUtils.dropTable
-import com.android.launcher3.provider.LauncherDbUtils.shiftTableByXCells
+import com.android.launcher3.provider.LauncherDbUtils.shiftWorkspaceByXCells
 import com.android.launcher3.util.CellAndSpan
 import com.android.launcher3.util.GridOccupancy
 import com.android.launcher3.util.IntArray
@@ -52,45 +52,47 @@
         target: DatabaseHelper,
         source: SQLiteDatabase,
         isDestNewDb: Boolean,
+        modelDelegate: ModelDelegate,
     ) {
         if (!GridSizeMigrationDBController.needsToMigrate(srcDeviceState, destDeviceState)) {
             return
         }
 
-        val isFirstLoad = get(context).get(LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE)
-        Log.d(
+        val isAfterRestore = get(context).get(LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE)
+        FileLog.d(
             TAG,
-            "Begin grid migration. First load: $isFirstLoad\n srcDeviceState: " +
+            "Begin grid migration. isAfterRestore: $isAfterRestore\nsrcDeviceState: " +
                 "$srcDeviceState\ndestDeviceState: $destDeviceState\nisDestNewDb: $isDestNewDb",
         )
 
-        // This is a special case where if the grid is the same amount of columns but a larger
-        // amount of rows we simply copy over the source grid to the destination grid, rather
-        // than undergoing the general grid migration.
-        if (shouldMigrateToStrictlyTallerGrid(isDestNewDb, srcDeviceState, destDeviceState)) {
-            Log.d(TAG, "Migrating to strictly taller grid")
+        val shouldMigrateToStrtictlyTallerGrid =
+            shouldMigrateToStrictlyTallerGrid(isDestNewDb, srcDeviceState, destDeviceState)
+        if (shouldMigrateToStrtictlyTallerGrid) {
             copyTable(source, TABLE_NAME, target.writableDatabase, TABLE_NAME, context)
-            if (oneGridSpecs()) {
-                val destReader = DbReader(target.writableDatabase, TABLE_NAME, context)
-                val shouldShiftCells = shouldShiftCells(destReader, srcDeviceState.rows)
-                if (shouldShiftCells) {
-                    shiftTableByXCells(
-                        target.writableDatabase,
-                        (destDeviceState.rows - srcDeviceState.rows),
-                        TABLE_NAME,
-                    )
-                }
-            }
-            // Save current configuration, so that the migration does not run again.
-            destDeviceState.writeToPrefs(context)
-            return
+        } else {
+            copyTable(source, TABLE_NAME, target.writableDatabase, TMP_TABLE, context)
         }
 
-        copyTable(source, TABLE_NAME, target.writableDatabase, TMP_TABLE, context)
-
         val migrationStartTime = System.currentTimeMillis()
         try {
             SQLiteTransaction(target.writableDatabase).use { t ->
+                // We want to add the extra row(s) to the top of the screen, so we shift the grid
+                // down.
+                if (shouldMigrateToStrtictlyTallerGrid) {
+                    Log.d(TAG, "Migrating to strictly taller grid")
+                    if (Flags.oneGridSpecs()) {
+                        shiftWorkspaceByXCells(
+                            target.writableDatabase,
+                            (destDeviceState.rows - srcDeviceState.rows),
+                            TABLE_NAME,
+                        )
+                    }
+                    // Save current configuration, so that the migration does not run again.
+                    destDeviceState.writeToPrefs(context)
+                    t.commit()
+                    return
+                }
+
                 val srcReader = DbReader(t.db, TMP_TABLE, context)
                 val destReader = DbReader(t.db, TABLE_NAME, context)
 
@@ -104,7 +106,14 @@
                 val idsInUse = mutableListOf<Int>()
 
                 // Migrate hotseat.
-                migrateHotseat(destDeviceState.numHotseat, srcReader, destReader, target, idsInUse)
+                migrateHotseat(
+                    srcDeviceState.numHotseat,
+                    destDeviceState.numHotseat,
+                    srcReader,
+                    destReader,
+                    target,
+                    idsInUse,
+                )
                 // Migrate workspace.
                 migrateWorkspace(srcReader, destReader, target, targetSize, idsInUse)
 
@@ -112,7 +121,7 @@
                 t.commit()
             }
         } catch (e: Exception) {
-            Log.e(TAG, "Error during grid migration", e)
+            FileLog.e(TAG, "Error during grid migration", e)
         } finally {
             Log.v(
                 TAG,
@@ -122,25 +131,16 @@
 
             // Save current configuration, so that the migration does not run again.
             destDeviceState.writeToPrefs(context)
-        }
-    }
 
-    private fun shouldShiftCells(destReader: DbReader, srcGridRowCount: Int): Boolean {
-        val workspaceItems = destReader.loadAllWorkspaceEntries()
-        val firstPageItemsRowPosSum =
-            workspaceItems.sumOf { entry -> if (entry.screenId == 0) entry.cellY else 0 }
-        val firstPageWorkspaceItemsCount = workspaceItems.count { entry -> entry.screenId == 0 }
-        if (firstPageWorkspaceItemsCount == 0) {
-            return false
+            // Notify if we've migrated successfully
+            modelDelegate.gridMigrationComplete(srcDeviceState, destDeviceState)
         }
-        val srcGridMidPoint = srcGridRowCount / 2f
-        val firstPageItemPosAvg = firstPageItemsRowPosSum / firstPageWorkspaceItemsCount.toFloat()
-        return (firstPageItemPosAvg >= srcGridMidPoint)
     }
 
     /** Handles hotseat migration. */
     @VisibleForTesting
     fun migrateHotseat(
+        srcHotseatSize: Int,
         destHotseatSize: Int,
         srcReader: DbReader,
         destReader: DbReader,
@@ -150,17 +150,24 @@
         val srcHotseatItems = srcReader.loadHotseatEntries()
         val dstHotseatItems = destReader.loadHotseatEntries()
 
-        val hotseatToBeAdded = getItemsToBeAdded(srcHotseatItems, dstHotseatItems)
-        val toBeRemoved = IntArray()
-        toBeRemoved.addAll(getItemsToBeRemoved(srcHotseatItems, dstHotseatItems))
+        // We want to filter out the hotseat items that are placed beyond the size of the source
+        // grid as we always want to keep those extra items from the destination grid.
+        var filteredDstHotseatItems = dstHotseatItems
+        if (srcHotseatSize < destHotseatSize) {
+            filteredDstHotseatItems =
+                filteredDstHotseatItems.filter { entry -> entry.screenId < srcHotseatSize }
+        }
+
+        val itemsToBeAdded = getItemsToBeAdded(srcHotseatItems, filteredDstHotseatItems)
+        val itemsToBeRemoved = getItemsToBeRemoved(srcHotseatItems, filteredDstHotseatItems)
 
         if (DEBUG) {
             Log.d(
                 TAG,
                 """Start hotseat migration:
-            |Removing Hotseat Items: [${dstHotseatItems.filter { toBeRemoved.contains(it.id) }
+            |Removing Hotseat Items: [${filteredDstHotseatItems.filter { itemsToBeRemoved.contains(it.id) }
                 .joinToString(",\n") { it.toString() }}]
-            |Adding Hotseat Items: [${hotseatToBeAdded
+            |Adding Hotseat Items: [${itemsToBeAdded
                 .joinToString(",\n") { it.toString() }}]
             |"""
                     .trimMargin(),
@@ -168,17 +175,19 @@
         }
 
         // Removes the items that we need to remove from the destination DB.
-        if (!toBeRemoved.isEmpty) {
+        if (!itemsToBeRemoved.isEmpty) {
             GridSizeMigrationDBController.removeEntryFromDb(
                 destReader.mDb,
                 destReader.mTableName,
-                toBeRemoved,
+                itemsToBeRemoved,
             )
         }
 
+        val remainingDstHotseatItems = destReader.loadHotseatEntries()
+
         placeHotseatItems(
-            hotseatToBeAdded,
-            dstHotseatItems,
+            itemsToBeAdded,
+            remainingDstHotseatItems,
             destHotseatSize,
             helper,
             srcReader,
@@ -258,9 +267,10 @@
             )
         }
 
+        val remainingDstWorkspaceItems = destReader.loadAllWorkspaceEntries()
         placeWorkspaceItems(
             workspaceToBeAdded,
-            dstWorkspaceItems,
+            remainingDstWorkspaceItems,
             targetSize.x,
             targetSize.y,
             helper,
@@ -366,7 +376,7 @@
         srcDeviceState: DeviceGridState,
         destDeviceState: DeviceGridState,
     ): Boolean {
-        return isDestNewDb &&
+        return (Flags.oneGridSpecs() || isDestNewDb) &&
             srcDeviceState.columns == destDeviceState.columns &&
             srcDeviceState.rows < destDeviceState.rows
     }
diff --git a/src/com/android/launcher3/model/ItemInstallQueue.java b/src/com/android/launcher3/model/ItemInstallQueue.java
index f9c6e96..8acd7e2 100644
--- a/src/com/android/launcher3/model/ItemInstallQueue.java
+++ b/src/com/android/launcher3/model/ItemInstallQueue.java
@@ -18,6 +18,7 @@
 
 import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
 
+import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
@@ -308,7 +309,8 @@
                         }
                     }
                     LauncherAppState.getInstance(context).getIconCache()
-                            .getTitleAndIcon(si, () -> lai, usePackageIcon, false);
+                            .getTitleAndIcon(si, () -> lai,
+                                    DESKTOP_ICON_FLAG.withUsePackageIcon(usePackageIcon));
                     return Pair.create(si, null);
                 }
                 case ITEM_TYPE_DEEP_SHORTCUT: {
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index c01b1b6..bd8c36b 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -16,8 +16,15 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.Utilities.SHOULD_SHOW_FIRST_PAGE_WIDGET;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
 
 import android.content.ComponentName;
 import android.content.ContentValues;
@@ -36,6 +43,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings.Favorites;
@@ -47,6 +55,8 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.CollectionInfo;
+import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.IconRequestInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
@@ -83,6 +93,11 @@
     private final IntArray mRestoredRows = new IntArray();
     private final IntSparseArrayMap<GridOccupancy> mOccupied = new IntSparseArrayMap<>();
 
+    // CollectionInfo objects, which have not yet been loaded from the DB, but are expected to
+    // found eventually as the loading progresses
+    private final IntSparseArrayMap<CollectionInfo> mPendingCollectionInfo =
+            new IntSparseArrayMap<>();
+
     private final int mIconIndex;
     public final int mTitleIndex;
 
@@ -188,7 +203,7 @@
         info.itemType = itemType;
         info.title = getTitle();
         // the fallback icon
-        if (!loadIcon(info)) {
+        if (!loadIconFromDb(info)) {
             info.bitmap = mIconCache.getDefaultIcon(info.user);
         }
 
@@ -200,15 +215,15 @@
     /**
      * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource.
      */
-    protected boolean loadIcon(WorkspaceItemInfo info) {
-        return createIconRequestInfo(info, false).loadWorkspaceIcon(mContext);
+    protected boolean loadIconFromDb(WorkspaceItemInfo info) {
+        return createIconRequestInfo(info, false).loadIconFromDbBlob(mContext);
     }
 
     public IconRequestInfo<WorkspaceItemInfo> createIconRequestInfo(
             WorkspaceItemInfo wai, boolean useLowResIcon) {
         byte[] iconBlob = itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT || restoreFlag != 0
+                || (wai.isInactiveArchive() && Flags.restoreArchivedAppIconsFromDb())
                 ? getIconBlob() : null;
-
         return new IconRequestInfo<>(wai, mActivityInfo, iconBlob, useLowResIcon);
     }
 
@@ -293,17 +308,17 @@
      * Make an WorkspaceItemInfo object for a restored application or shortcut item that points
      * to a package that is not yet installed on the system.
      */
-    public WorkspaceItemInfo getRestoredItemInfo(Intent intent) {
+    public WorkspaceItemInfo getRestoredItemInfo(Intent intent, boolean isArchived) {
         final WorkspaceItemInfo info = new WorkspaceItemInfo();
         info.user = user;
         info.intent = intent;
 
         // the fallback icon
-        if (!loadIcon(info)) {
-            mIconCache.getTitleAndIcon(info, false /* useLowResIcon */);
+        if (!loadIconFromDb(info)) {
+            mIconCache.getTitleAndIcon(info, DEFAULT_LOOKUP_FLAG);
         }
 
-        if (hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORED_ICON)) {
+        if (hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORED_ICON) || isArchived) {
             String title = getTitle();
             if (!TextUtils.isEmpty(title)) {
                 info.title = Utilities.trim(title);
@@ -319,6 +334,7 @@
         info.contentDescription = mIconCache.getUserBadgedLabel(info.title, info.user);
         info.itemType = itemType;
         info.status = restoreFlag;
+        if (isArchived) info.runtimeStatusFlags |= FLAG_ARCHIVED;
         return info;
     }
 
@@ -362,19 +378,11 @@
         info.intent = newIntent;
         UserCache userCache = UserCache.getInstance(mContext);
         UserIconInfo userIconInfo = userCache.getUserInfo(user);
-
-        if (loadIcon) {
-            mIconCache.getTitleAndIcon(info, mActivityInfo, useLowResIcon);
-            if (mIconCache.isDefaultIcon(info.bitmap, user)) {
-                loadIcon(info);
-            }
-        }
-
         if (mActivityInfo != null) {
             AppInfo.updateRuntimeFlagsForActivityTarget(info, mActivityInfo, userIconInfo,
                     ApiWrapper.INSTANCE.get(mContext), mPmHelper);
         }
-
+        loadWorkspaceTitleAndIcon(useLowResIcon, loadIcon, info);
         // from the db
         if (TextUtils.isEmpty(info.title)) {
             if (loadIcon) {
@@ -393,6 +401,32 @@
         return info;
     }
 
+    @VisibleForTesting
+    void loadWorkspaceTitleAndIcon(
+            boolean useLowResIcon,
+            boolean loadIconFromCache,
+            WorkspaceItemInfo info
+    ) {
+        boolean isPreArchived = Flags.enableSupportForArchiving()
+                && Flags.restoreArchivedAppIconsFromDb()
+                && info.isInactiveArchive();
+        boolean preArchivedIconNotFound = isPreArchived && !loadIconFromDb(info);
+        if (preArchivedIconNotFound) {
+            Log.d(TAG, "loadIconFromDb failed for pre-archived icon, loading from cache."
+                    + " Component=" + info.getTargetComponent());
+            mIconCache.getTitleAndIcon(info, mActivityInfo,
+                    DEFAULT_LOOKUP_FLAG.withUseLowRes(useLowResIcon));
+        } else if (loadIconFromCache && !info.isInactiveArchive()) {
+            mIconCache.getTitleAndIcon(info, mActivityInfo,
+                    DEFAULT_LOOKUP_FLAG.withUseLowRes(useLowResIcon));
+            if (mIconCache.isDefaultIcon(info.bitmap, user)) {
+                Log.d(TAG, "Default Icon found in cache, trying DB instead. "
+                        + " Component=" + info.getTargetComponent());
+                loadIconFromDb(info);
+            }
+        }
+    }
+
     /**
      * Returns a {@link ContentWriter} which can be used to update the current item.
      */
@@ -477,8 +511,26 @@
         info.cellY = getInt(mCellYIndex);
     }
 
-    public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) {
-        checkAndAddItem(info, dataModel, null);
+    /**
+     * Return an existing FolderInfo object if we have encountered this ID previously,
+     * or make a new one.
+     */
+    public CollectionInfo findOrMakeFolder(int id, BgDataModel dataModel) {
+        // See if a placeholder was created for us already
+        ItemInfo info = dataModel.itemsIdMap.get(id);
+        if (info instanceof CollectionInfo c) return c;
+
+        CollectionInfo pending = mPendingCollectionInfo.get(id);
+        if (pending != null) return pending;
+
+        // No placeholder -- create a new blank folder instance. At this point, we don't know
+        // if the desired container is supposed to be a folder or an app pair. In the case that
+        // it is an app pair, the blank folder will be replaced by a blank app pair when the app
+        // pair is getting processed, in WorkspaceItemProcessor.processFolderOrAppPair().
+        pending = new FolderInfo();
+        pending.id = id;
+        mPendingCollectionInfo.put(id, pending);
+        return pending;
     }
 
     /**
@@ -493,7 +545,21 @@
             ShortcutKey.fromItemInfo(info);
         }
         if (checkItemPlacement(info, dataModel.isFirstPagePinnedItemEnabled)) {
-            dataModel.addItem(mContext, info, false, logger);
+            if (logger != null) {
+                logger.addLog(
+                        Log.DEBUG,
+                        TAG,
+                        String.format("Adding item to ID map: %s", info),
+                        /* stackTrace= */ null);
+            }
+            dataModel.addItem(mContext, info, false);
+            if ((info.itemType == ITEM_TYPE_APP_PAIR
+                    || info.itemType == ITEM_TYPE_DEEP_SHORTCUT
+                    || info.itemType == ITEM_TYPE_APPLICATION)
+                    && info.container != CONTAINER_DESKTOP
+                    && info.container != CONTAINER_HOTSEAT) {
+                findOrMakeFolder(info.container, dataModel).add(info);
+            }
             if (mRestoreEventLogger != null) {
                 mRestoreEventLogger.logSingleFavoritesItemRestored(itemType);
             }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index f96e959..fb1ebaf 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -20,19 +20,22 @@
 import static com.android.launcher3.Flags.enableLauncherBrMetricsFixed;
 import static com.android.launcher3.Flags.enableSmartspaceAsAWidget;
 import static com.android.launcher3.Flags.enableSmartspaceRemovalToggle;
-import static com.android.launcher3.Flags.enableTieredWidgetsByDefaultInPicker;
 import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
 import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE;
+import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG;
 import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
 import static com.android.launcher3.icons.CacheableShortcutInfo.convertShortcutsToCacheableShortcuts;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
-import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
+import static com.android.launcher3.model.ModelUtils.currentScreenContentFilter;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
+import static com.android.launcher3.util.LooperExecutor.CALLER_LOADER_TASK;
 import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
 
 import android.appwidget.AppWidgetProviderInfo;
@@ -81,7 +84,6 @@
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.AppPairInfo;
-import com.android.launcher3.model.data.CollectionInfo;
 import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.IconRequestInfo;
 import com.android.launcher3.model.data.ItemInfo;
@@ -105,11 +107,13 @@
 import com.android.launcher3.widget.WidgetInflater;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CancellationException;
 
@@ -143,7 +147,6 @@
     private final UserManager mUserManager;
     private final UserCache mUserCache;
     private final PackageManagerHelper mPmHelper;
-    private final WidgetsFilterDataProvider mWidgetsFilterDataProvider;
 
     private final InstallSessionHelper mSessionHelper;
     private final IconCache mIconCache;
@@ -153,6 +156,8 @@
     private Map<ShortcutKey, ShortcutInfo> mShortcutKeyToPinnedShortcuts;
     private HashMap<PackageUserKey, SessionInfo> mInstallingPkgsCached;
 
+    private List<IconRequestInfo<WorkspaceItemInfo>> mWorkspaceIconRequestInfos = new ArrayList<>();
+
     private boolean mStopped;
 
     private final Set<PackageUserKey> mPendingPackages = new HashSet<>();
@@ -160,16 +165,13 @@
     private String mDbName;
 
     public LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel,
-            ModelDelegate modelDelegate, @NonNull BaseLauncherBinder launcherBinder,
-            @NonNull WidgetsFilterDataProvider widgetsFilterDataProvider) {
-        this(app, bgAllAppsList, bgModel, modelDelegate, launcherBinder, widgetsFilterDataProvider,
-                new UserManagerState());
+            ModelDelegate modelDelegate, @NonNull BaseLauncherBinder launcherBinder) {
+        this(app, bgAllAppsList, bgModel, modelDelegate, launcherBinder, new UserManagerState());
     }
 
     @VisibleForTesting
     LoaderTask(@NonNull LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel bgModel,
             ModelDelegate modelDelegate, @NonNull BaseLauncherBinder launcherBinder,
-            WidgetsFilterDataProvider widgetsFilterDataProvider,
             UserManagerState userManagerState) {
         mApp = app;
         mBgAllAppsList = bgAllAppsList;
@@ -184,7 +186,6 @@
         mIconCache = mApp.getIconCache();
         mUserManagerState = userManagerState;
         mInstallingPkgsCached = null;
-        mWidgetsFilterDataProvider = widgetsFilterDataProvider;
     }
 
     protected synchronized void waitForIdle() {
@@ -209,28 +210,29 @@
         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 */);
-        final int launcherBroadcastInstalledApps = Settings.Secure.getInt(
+        List<ItemInfo> firstScreenItems =
+                mBgDataModel.itemsIdMap.stream()
+                        .filter(currentScreenContentFilter(firstScreens))
+                        .toList();
+        final int disableArchivingLauncherBroadcast = Settings.Secure.getInt(
                 mApp.getContext().getContentResolver(),
-                "launcher_broadcast_installed_apps",
-                /* def= */ 0);
+                "disable_launcher_broadcast_installed_apps",
+                /* default */ 0);
         boolean shouldAttachArchivingExtras = mIsRestoreFromBackup
-                && (launcherBroadcastInstalledApps == 1
-                        || Flags.enableFirstScreenBroadcastArchivingExtras());
+                && disableArchivingLauncherBroadcast == 0
+                && Flags.enableFirstScreenBroadcastArchivingExtras();
         if (shouldAttachArchivingExtras) {
             List<FirstScreenBroadcastModel> broadcastModels =
                     FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
                             mPmHelper,
                             firstScreenItems,
                             mInstallingPkgsCached,
-                            mBgDataModel.appWidgets
+                            mBgDataModel.itemsIdMap.stream().filter(WIDGET_FILTER).toList()
                     );
             logASplit("Sending first screen broadcast with additional archiving Extras");
             FirstScreenBroadcastHelper.sendBroadcastsForModels(mApp.getContext(), broadcastModels);
         } else {
+            logASplit("Sending first screen broadcast");
             mFirstScreenBroadcast.sendBroadcasts(mApp.getContext(), firstScreenItems);
         }
     }
@@ -244,6 +246,7 @@
         }
 
         TraceHelper.INSTANCE.beginSection(TAG);
+        MODEL_EXECUTOR.elevatePriority(CALLER_LOADER_TASK);
         LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
         mIsRestoreFromBackup =
                 LauncherPrefs.get(mApp.getContext()).get(IS_FIRST_LOAD_AFTER_RESTORE);
@@ -261,6 +264,9 @@
             // sanitizeData should not be invoked if the workspace is loaded from a db different
             // from the main db as defined in the invariant device profile.
             // (e.g. both grid preview and minimal device mode uses a different db)
+            // TODO(b/384731096): Write Unit Test to make sure sanitizeWidgetsShortcutsAndPackages
+            //  actually re-pins shortcuts that are in model but not in ShortcutManager, if possible
+            //  after a simulated restore.
             if (Objects.equals(mApp.getInvariantDeviceProfile().dbFile, mDbName)) {
                 verifyNotStopped();
                 sanitizeFolders(mItemsDeleted);
@@ -276,7 +282,6 @@
             mModelDelegate.workspaceLoadComplete();
             // Notify the installer packages of packages with active installs on the first screen.
             sendFirstScreenActiveInstallsBroadcast();
-            logASplit("sendFirstScreenBroadcast finished");
 
             // Take a break
             waitForIdle();
@@ -337,13 +342,6 @@
 
             // fourth step
             WidgetsModel widgetsModel = mBgDataModel.widgetsModel;
-            if (enableTieredWidgetsByDefaultInPicker()) {
-                // Begin periodic refresh of filters
-                mWidgetsFilterDataProvider.initPeriodicDataRefresh(
-                        mApp.getModel()::onWidgetFiltersLoaded);
-                // And, update model with currently cached data.
-                widgetsModel.updateWidgetFilters(mWidgetsFilterDataProvider);
-            }
             List<CachedObject> allWidgetsList = widgetsModel.update(mApp, /*packageUser=*/null);
             logASplit("load widgets finished");
 
@@ -389,16 +387,17 @@
             }
         } catch (CancellationException e) {
             // Loader stopped, ignore
-            FileLog.w(TAG, "LoaderTask cancelled:", e);
+            FileLog.w(TAG, "LoaderTask cancelled");
         } catch (Exception e) {
             memoryLogger.printLogs();
             throw e;
         }
+        MODEL_EXECUTOR.restorePriority(CALLER_LOADER_TASK);
         TraceHelper.INSTANCE.endSection();
     }
 
     public synchronized void stopLocked() {
-        FileLog.w(TAG, "LoaderTask#stopLocked:", new Exception());
+        FileLog.w(TAG, "stopLocked: Loader stopping");
         mStopped = true;
         this.notify();
     }
@@ -406,7 +405,7 @@
     protected void loadWorkspace(
             List<CacheableShortcutInfo> allDeepShortcuts,
             String selection,
-            LoaderMemoryLogger memoryLogger,
+            @Nullable LoaderMemoryLogger memoryLogger,
             @Nullable LauncherRestoreEventLogger restoreEventLogger
     ) {
         Trace.beginSection("LoadWorkspace");
@@ -434,12 +433,12 @@
         ModelDbController dbController = mApp.getModel().getModelDbController();
         if (Flags.gridMigrationRefactor()) {
             try {
-                dbController.attemptMigrateDb(restoreEventLogger);
+                dbController.attemptMigrateDb(restoreEventLogger, mModelDelegate);
             } catch (Exception e) {
                 FileLog.e(TAG, "Failed to migrate grid", e);
             }
         } else {
-            dbController.tryMigrateDB(restoreEventLogger);
+            dbController.tryMigrateDB(restoreEventLogger, mModelDelegate);
         }
         Log.d(TAG, "loadWorkspace: loading default favorites if necessary");
         dbController.loadDefaultFavoritesIfNecessary();
@@ -470,13 +469,12 @@
                 final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
                 queryPinnedShortcutsForUnlockedUsers(context, unlockedUsers);
 
-                List<IconRequestInfo<WorkspaceItemInfo>> iconRequestInfos = new ArrayList<>();
-
+                mWorkspaceIconRequestInfos = new ArrayList<>();
                 WorkspaceItemProcessor itemProcessor = new WorkspaceItemProcessor(c, memoryLogger,
                         mUserCache, mUserManagerState, mLauncherApps, mPendingPackages,
                         mShortcutKeyToPinnedShortcuts, mApp, mBgDataModel,
                         mWidgetProvidersMap, installingPkgs, isSdCardReady,
-                        widgetInflater, mPmHelper, iconRequestInfos, unlockedUsers,
+                        widgetInflater, mPmHelper, mWorkspaceIconRequestInfos, unlockedUsers,
                         allDeepShortcuts);
 
                 if (mStopped) {
@@ -486,7 +484,7 @@
                         itemProcessor.processItem();
                     }
                 }
-                tryLoadWorkspaceIconsInBulk(iconRequestInfos);
+                tryLoadWorkspaceIconsInBulk(mWorkspaceIconRequestInfos);
             } finally {
                 IOUtils.closeSilently(c);
             }
@@ -519,14 +517,13 @@
      * requests high-res icons for the items that are part of an app pair.
      */
     private void processAppPairItems() {
-        for (CollectionInfo collection : mBgDataModel.collections) {
-            if (!(collection instanceof AppPairInfo appPair)) {
-                continue;
-            }
-
-            appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR);
-            appPair.fetchHiResIconsIfNeeded(mIconCache);
-        }
+        mBgDataModel.itemsIdMap.stream()
+                .filter(item -> item instanceof AppPairInfo)
+                .forEach(item -> {
+                    AppPairInfo appPair = (AppPairInfo) item;
+                    appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR);
+                    appPair.fetchHiResIconsIfNeeded(mIconCache);
+                });
     }
 
     /**
@@ -582,8 +579,8 @@
         // Sort the folder items, update ranks, and make sure all preview items are high res.
         List<FolderGridOrganizer> verifiers = mApp.getInvariantDeviceProfile().supportedProfiles
                 .stream().map(FolderGridOrganizer::createFolderGridOrganizer).toList();
-        for (CollectionInfo collection : mBgDataModel.collections) {
-            if (!(collection instanceof FolderInfo folder)) {
+        for (ItemInfo itemInfo : mBgDataModel.itemsIdMap) {
+            if (!(itemInfo instanceof FolderInfo folder)) {
                 continue;
             }
 
@@ -599,10 +596,10 @@
                 info.rank = rank;
 
                 if (info instanceof WorkspaceItemInfo wii
-                        && wii.usingLowResIcon()
+                        && wii.getMatchingLookupFlag().isVisuallyLessThan(DESKTOP_ICON_FLAG)
                         && wii.itemType == Favorites.ITEM_TYPE_APPLICATION
                         && verifiers.stream().anyMatch(it -> it.isItemInPreview(info.rank))) {
-                    mIconCache.getTitleAndIcon(wii, false);
+                    mIconCache.getTitleAndIcon(wii, DESKTOP_ICON_FLAG);
                 } else if (info instanceof AppPairInfo api) {
                     api.fetchHiResIconsIfNeeded(mIconCache);
                 }
@@ -618,7 +615,9 @@
             for (IconRequestInfo<WorkspaceItemInfo> iconRequestInfo : iconRequestInfos) {
                 WorkspaceItemInfo wai = iconRequestInfo.itemInfo;
                 if (mIconCache.isDefaultIcon(wai.bitmap, wai.user)) {
-                    iconRequestInfo.loadWorkspaceIcon(mApp.getContext());
+                    logASplit("tryLoadWorkspaceIconsInBulk: default icon found for "
+                            + wai.getTargetComponent() + ", will attempt to load from iconBlob");
+                    iconRequestInfo.loadIconFromDbBlob(mApp.getContext());
                 }
             }
         } finally {
@@ -653,8 +652,6 @@
             IntArray deletedFolderIds = mApp.getModel().getModelDbController().deleteEmptyFolders();
             synchronized (mBgDataModel) {
                 for (int folderId : deletedFolderIds) {
-                    mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(folderId));
-                    mBgDataModel.collections.remove(folderId);
                     mBgDataModel.itemsIdMap.remove(folderId);
                 }
             }
@@ -672,8 +669,6 @@
 
         synchronized (mBgDataModel) {
             for (int id : deleted) {
-                mBgDataModel.workspaceItems.remove(mBgDataModel.collections.get(id));
-                mBgDataModel.collections.remove(id);
                 mBgDataModel.itemsIdMap.remove(id);
             }
         }
@@ -703,7 +698,7 @@
         // Clear the list of apps
         mBgAllAppsList.clear();
 
-        List<IconRequestInfo<AppInfo>> iconRequestInfos = new ArrayList<>();
+        List<IconRequestInfo<AppInfo>> allAppsItemRequestInfos = new ArrayList<>();
         boolean isWorkProfileQuiet = false;
         boolean isPrivateProfileQuiet = false;
         for (UserHandle user : profiles) {
@@ -743,15 +738,14 @@
                     }
                 }
 
-                iconRequestInfos.add(new IconRequestInfo<>(
-                        appInfo, app, /* useLowResIcon= */ false));
-                mBgAllAppsList.add(
-                        appInfo, app, false);
+                IconRequestInfo<AppInfo> iconRequestInfo = getAppInfoIconRequestInfo(
+                        appInfo, app, mWorkspaceIconRequestInfos);
+                allAppsItemRequestInfos.add(iconRequestInfo);
+                mBgAllAppsList.add(appInfo, app, false);
             }
             allActivityList.addAll(apps);
         }
 
-
         if (FeatureFlags.PROMISE_APPS_IN_ALL_APPS.get()) {
             // get all active sessions and add them to the all apps list
             for (PackageInstaller.SessionInfo info :
@@ -762,18 +756,31 @@
                         false);
 
                 if (promiseAppInfo != null) {
-                    iconRequestInfos.add(new IconRequestInfo<>(
+                    allAppsItemRequestInfos.add(new IconRequestInfo<>(
                             promiseAppInfo,
                             /* launcherActivityInfo= */ null,
-                            promiseAppInfo.usingLowResIcon()));
+                            promiseAppInfo.getMatchingLookupFlag().useLowRes()));
                 }
             }
         }
 
         Trace.beginSection("LoadAllAppsIconsInBulk");
+
         try {
-            mIconCache.getTitlesAndIconsInBulk(iconRequestInfos);
-            iconRequestInfos.forEach(iconRequestInfo ->
+            mIconCache.getTitlesAndIconsInBulk(allAppsItemRequestInfos);
+            if (Flags.restoreArchivedAppIconsFromDb()) {
+                for (IconRequestInfo<AppInfo> iconRequestInfo : allAppsItemRequestInfos) {
+                    AppInfo appInfo = iconRequestInfo.itemInfo;
+                    if (mIconCache.isDefaultIcon(appInfo.bitmap, appInfo.user)) {
+                        logASplit("LoadAllAppsIconsInBulk: default icon found for "
+                                + appInfo.getTargetComponent()
+                                + ", will attempt to load from iconBlob: "
+                                + Arrays.toString(iconRequestInfo.iconBlob));
+                        iconRequestInfo.loadIconFromDbBlob(mApp.getContext());
+                    }
+                }
+            }
+            allAppsItemRequestInfos.forEach(iconRequestInfo ->
                     mBgAllAppsList.updateSectionName(iconRequestInfo.itemInfo));
         } finally {
             Trace.endSection();
@@ -796,6 +803,49 @@
         return allActivityList;
     }
 
+    @NonNull
+    @VisibleForTesting
+    IconRequestInfo<AppInfo> getAppInfoIconRequestInfo(
+            AppInfo appInfo,
+            LauncherActivityInfo activityInfo,
+            List<IconRequestInfo<WorkspaceItemInfo>> workspaceRequestInfos
+    ) {
+        if (Flags.restoreArchivedAppIconsFromDb()) {
+            Optional<IconRequestInfo<WorkspaceItemInfo>> workspaceIconRequest =
+                    workspaceRequestInfos.stream()
+                            .filter(request -> appInfo.getTargetComponent().equals(
+                                    request.itemInfo.getTargetComponent()))
+                            .findFirst();
+
+            if (workspaceIconRequest.isPresent() && activityInfo.getApplicationInfo().isArchived) {
+                logASplit("getAppInfoIconRequestInfo:"
+                            + " matching archived info found, loading icon blob into icon request."
+                            + " Component=" + appInfo.getTargetComponent());
+                IconRequestInfo<AppInfo> iconRequestInfo = new IconRequestInfo<>(
+                        appInfo,
+                        activityInfo,
+                        workspaceIconRequest.get().iconBlob,
+                        false /* useLowResIcon= */
+                );
+                if (!iconRequestInfo.loadIconFromDbBlob(mApp.getContext())) {
+                    Log.d(TAG, "AppInfo Icon failed to load from blob, using cache.");
+                    mIconCache.getTitleAndIcon(
+                            appInfo,
+                            iconRequestInfo.launcherActivityInfo,
+                            DEFAULT_LOOKUP_FLAG
+                    );
+                }
+                return iconRequestInfo;
+            } else {
+                Log.d(TAG, "App not archived or workspace info not found"
+                        + ", creating IconRequestInfo without icon blob."
+                        + " Component:" + appInfo.getTargetComponent()
+                        + ", isArchived: " + activityInfo.getApplicationInfo().isArchived);
+            }
+        }
+        return new IconRequestInfo<>(appInfo, activityInfo, false /* useLowResIcon= */);
+    }
+
     private List<ShortcutInfo> loadDeepShortcuts() {
         List<ShortcutInfo> allShortcuts = new ArrayList<>();
         mBgDataModel.deepShortcutMap.clear();
@@ -815,18 +865,19 @@
 
     private void loadFolderNames() {
         FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext(),
-                mBgAllAppsList.data, mBgDataModel.collections);
+                mBgAllAppsList.data, FolderNameProvider.getCollectionForSuggestions(mBgDataModel));
 
         synchronized (mBgDataModel) {
-            for (int i = 0; i < mBgDataModel.collections.size(); i++) {
-                FolderNameInfos suggestionInfos = new FolderNameInfos();
-                CollectionInfo info = mBgDataModel.collections.valueAt(i);
-                if (info instanceof FolderInfo fi && fi.suggestedFolderNames == null) {
-                    provider.getSuggestedFolderName(mApp.getContext(), fi.getAppContents(),
-                            suggestionInfos);
-                    fi.suggestedFolderNames = suggestionInfos;
-                }
-            }
+            mBgDataModel.itemsIdMap.stream()
+                    .filter(item ->
+                            item instanceof FolderInfo fi && fi.suggestedFolderNames == null)
+                    .forEach(info -> {
+                        FolderInfo fi = (FolderInfo) info;
+                        FolderNameInfos suggestionInfos = new FolderNameInfos();
+                        provider.getSuggestedFolderName(mApp.getContext(), fi.getAppContents(),
+                                suggestionInfos);
+                        fi.suggestedFolderNames = suggestionInfos;
+                    });
         }
     }
 
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index 6ff8547..feae632 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -80,7 +80,6 @@
 import com.android.launcher3.provider.RestoreDbTask;
 import com.android.launcher3.util.IOUtils;
 import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
 import com.android.launcher3.util.Partner;
 import com.android.launcher3.widget.LauncherWidgetHolder;
 
@@ -90,13 +89,14 @@
 import java.io.InputStream;
 import java.io.StringReader;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * Utility class which maintains an instance of Launcher database and provides utility methods
  * around it.
  */
 public class ModelDbController {
-    private static final String TAG = "LauncherProvider";
+    private static final String TAG = "ModelDbController";
 
     private static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
     public static final String EXTRA_DB_NAME = "db_name";
@@ -142,14 +142,11 @@
     }
 
     protected DatabaseHelper createDatabaseHelper(boolean forMigration, String dbFile) {
-        boolean isSandbox = mContext instanceof SandboxContext;
-        String dbName = isSandbox ? null : dbFile;
-
         // Set the flag for empty DB
         Runnable onEmptyDbCreateCallback = forMigration ? () -> { }
-                : () -> LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey(dbName).to(true));
+                : () -> LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey(dbFile).to(true));
 
-        DatabaseHelper databaseHelper = new DatabaseHelper(mContext, dbName,
+        DatabaseHelper databaseHelper = new DatabaseHelper(mContext, dbFile,
                 this::getSerialNumberForUser, onEmptyDbCreateCallback);
         // Table creation sometimes fails silently, which leads to a crash loop.
         // This way, we will try to create a table every time after crash, so the device
@@ -303,7 +300,7 @@
         if (restoreEventLogger != null) {
             sendMetricsForFailedMigration(restoreEventLogger, getDb());
         }
-        FileLog.d(TAG, "Migration failed: resetting launcher database");
+        FileLog.d(TAG, "resetLauncherDb: Migration failed: resetting launcher database");
         createEmptyDB();
         LauncherPrefs.get(mContext).putSync(
                 getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()).to(true));
@@ -331,7 +328,7 @@
     private boolean isThereExistingDb() {
         if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) {
             // If we already have a new DB, ignore migration
-            FileLog.d(TAG, "migrateGridIfNeeded: new DB already created, skipping migration");
+            FileLog.d(TAG, "isThereExistingDb: new DB already created, skipping migration");
             return true;
         }
         return false;
@@ -342,7 +339,7 @@
         if (GridSizeMigrationDBController.needsToMigrate(mContext, idp)) {
             return true;
         }
-        FileLog.d(TAG, "migrateGridIfNeeded: no grid migration needed");
+        FileLog.d(TAG, "isGridMigrationNecessary: no grid migration needed");
         return false;
     }
 
@@ -350,7 +347,9 @@
         InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
         String targetDbName = new DeviceGridState(idp).getDbFile();
         if (TextUtils.equals(targetDbName, mOpenHelper.getDatabaseName())) {
-            FileLog.e(TAG, "migrateGridIfNeeded: target db is same as current: " + targetDbName);
+            FileLog.e(TAG, "isCurrentDbSameAsTarget: target db is same as current"
+                    + " current db: " + mOpenHelper.getDatabaseName()
+                    + " target db: " + targetDbName);
             return true;
         }
         return false;
@@ -359,7 +358,8 @@
     /**
      * Migrates the DB. If the migration failed, it clears the DB.
      */
-    public void attemptMigrateDb(LauncherRestoreEventLogger restoreEventLogger) throws Exception {
+    public void attemptMigrateDb(LauncherRestoreEventLogger restoreEventLogger,
+            ModelDelegate modelDelegate) throws Exception {
         createDbIfNotExists();
 
         if (shouldResetDb()) {
@@ -374,10 +374,9 @@
         // to run in grid migration based on if that grid already existed before migration or not.
         List<String> existingDBs = LauncherFiles.GRID_DB_FILES.stream()
                 .filter(dbName -> mContext.getDatabasePath(dbName).exists())
-                .toList();
+                .collect(Collectors.toList());
 
-        mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
-                : createDatabaseHelper(true, new DeviceGridState(idp).getDbFile());
+        mOpenHelper = createDatabaseHelper(true, new DeviceGridState(idp).getDbFile());
         try {
             // This is the current grid we have, given by the mContext
             DeviceGridState srcDeviceState = new DeviceGridState(mContext);
@@ -385,13 +384,12 @@
             DeviceGridState destDeviceState = new DeviceGridState(idp);
 
             boolean isDestNewDb = !existingDBs.contains(destDeviceState.getDbFile());
-
             GridSizeMigrationLogic gridSizeMigrationLogic = new GridSizeMigrationLogic();
             gridSizeMigrationLogic.migrateGrid(mContext, srcDeviceState, destDeviceState,
-                    mOpenHelper, oldHelper.getWritableDatabase(), isDestNewDb);
+                    mOpenHelper, oldHelper.getWritableDatabase(), isDestNewDb, modelDelegate);
         } catch (Exception e) {
             resetLauncherDb(restoreEventLogger);
-            throw new Exception("Failed to migrate grid", e);
+            throw new Exception("attemptMigrateDb: Failed to migrate grid", e);
         } finally {
             if (mOpenHelper != oldHelper) {
                 oldHelper.close();
@@ -402,8 +400,9 @@
     /**
      * Migrates the DB if needed. If the migration failed, it clears the DB.
      */
-    public void tryMigrateDB(@Nullable LauncherRestoreEventLogger restoreEventLogger) {
-        if (!migrateGridIfNeeded()) {
+    public void tryMigrateDB(@Nullable LauncherRestoreEventLogger restoreEventLogger,
+            ModelDelegate modelDelegate) {
+        if (!migrateGridIfNeeded(modelDelegate)) {
             if (restoreEventLogger != null) {
                 if (LauncherPrefs.get(mContext).get(NO_DB_FILES_RESTORED)) {
                     restoreEventLogger.logLauncherItemsRestoreFailed(DATA_TYPE_DB_FILE, 1,
@@ -415,7 +414,7 @@
                     sendMetricsForFailedMigration(restoreEventLogger, getDb());
                 }
             }
-            FileLog.d(TAG, "Migration failed: resetting launcher database");
+            FileLog.d(TAG, "tryMigrateDB: Migration failed: resetting launcher database");
             createEmptyDB();
             LauncherPrefs.get(mContext).putSync(
                     getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()).to(true));
@@ -433,7 +432,7 @@
      * @return true if migration was success or ignored, false if migration failed
      * and the DB should be reset.
      */
-    private boolean migrateGridIfNeeded() {
+    private boolean migrateGridIfNeeded(ModelDelegate modelDelegate) {
         createDbIfNotExists();
         if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) {
             // If we have already create a new DB, ignore migration
@@ -447,31 +446,29 @@
         }
         String targetDbName = new DeviceGridState(idp).getDbFile();
         if (TextUtils.equals(targetDbName, mOpenHelper.getDatabaseName())) {
-            FileLog.e(TAG, "migrateGridIfNeeded: target db is same as current: " + targetDbName);
+            FileLog.e(TAG, "migrateGridIfNeeded: target db is same as current"
+                    + " current db: " + mOpenHelper.getDatabaseName()
+                    + " target db: " + targetDbName);
             return false;
         }
         DatabaseHelper oldHelper = mOpenHelper;
-
         // We save the existing db's before creating the destination db helper so we know what logic
         // to run in grid migration based on if that grid already existed before migration or not.
         List<String> existingDBs = LauncherFiles.GRID_DB_FILES.stream()
                 .filter(dbName -> mContext.getDatabasePath(dbName).exists())
-                .toList();
-
-        mOpenHelper = (mContext instanceof SandboxContext) ? oldHelper
-                : createDatabaseHelper(true /* forMigration */, targetDbName);
+                .collect(Collectors.toList());
+        mOpenHelper = createDatabaseHelper(true /* forMigration */, targetDbName);
         try {
             // This is the current grid we have, given by the mContext
             DeviceGridState srcDeviceState = new DeviceGridState(mContext);
             // This is the state we want to migrate to that is given by the idp
             DeviceGridState destDeviceState = new DeviceGridState(idp);
-
             boolean isDestNewDb = !existingDBs.contains(destDeviceState.getDbFile());
-
             return GridSizeMigrationDBController.migrateGridIfNeeded(mContext, srcDeviceState,
-                    destDeviceState, mOpenHelper, oldHelper.getWritableDatabase(), isDestNewDb);
+                    destDeviceState, mOpenHelper, oldHelper.getWritableDatabase(), isDestNewDb,
+                    modelDelegate);
         } catch (Exception e) {
-            FileLog.e(TAG, "Failed to migrate grid", e);
+            FileLog.e(TAG, "migrateGridIfNeeded: Failed to migrate grid", e);
             return false;
         } finally {
             if (mOpenHelper != oldHelper) {
@@ -760,10 +757,6 @@
      * string will be "EMPTY_DATABASE_CREATED@minimal.db".
      */
     private ConstantItem<Boolean> getEmptyDbCreatedKey(String dbName) {
-        if (mContext instanceof SandboxContext) {
-            return LauncherPrefs.nonRestorableItem(EMPTY_DATABASE_CREATED,
-                    false /* default value */, EncryptionType.ENCRYPTED);
-        }
         String key = TextUtils.equals(dbName, LauncherFiles.LAUNCHER_DB)
                 ? EMPTY_DATABASE_CREATED : EMPTY_DATABASE_CREATED + "@" + dbName;
         return LauncherPrefs.backedUpItem(key, false /* default value */, EncryptionType.ENCRYPTED);
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
index 2264d35..52a2188 100644
--- a/src/com/android/launcher3/model/ModelDelegate.java
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -23,62 +23,45 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.WorkerThread;
 
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.R;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.dagger.ApplicationContext;
 import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.util.PackageManagerHelper;
-import com.android.launcher3.util.ResourceBasedOverride;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Map;
 
+import javax.inject.Inject;
+
 /**
  * Class to extend LauncherModel functionality to provide extra data
  */
-public class ModelDelegate implements ResourceBasedOverride {
-
-    /**
-     * Creates and initializes a new instance of the delegate
-     */
-    public static ModelDelegate newInstance(
-            Context context, LauncherAppState app, PackageManagerHelper pmHelper,
-            AllAppsList appsList, BgDataModel dataModel, boolean isPrimaryInstance) {
-        ModelDelegate delegate = Overrides.getObject(
-                ModelDelegate.class, context, R.string.model_delegate_class);
-        delegate.init(app, pmHelper, appsList, dataModel, isPrimaryInstance);
-        return delegate;
-    }
+public class ModelDelegate {
 
     protected final Context mContext;
-    protected PackageManagerHelper mPmHelper;
-    protected LauncherAppState mApp;
+    protected LauncherModel mModel;
     protected AllAppsList mAppsList;
     protected BgDataModel mDataModel;
-    protected boolean mIsPrimaryInstance;
 
-    public ModelDelegate(Context context) {
+    @Inject
+    public ModelDelegate(@ApplicationContext Context context) {
         mContext = context;
     }
 
     /**
      * Initializes the object with the given params.
      */
-    private void init(LauncherAppState app, PackageManagerHelper pmHelper, AllAppsList appsList,
-            BgDataModel dataModel, boolean isPrimaryInstance) {
-        this.mApp = app;
-        this.mPmHelper = pmHelper;
+    public void init(LauncherModel model, AllAppsList appsList, BgDataModel dataModel) {
+        this.mModel = model;
         this.mAppsList = appsList;
         this.mDataModel = dataModel;
-        this.mIsPrimaryInstance = isPrimaryInstance;
     }
 
     /** Called periodically to validate and update any data */
     @WorkerThread
     public void validateData() {
-        if (hasShortcutsPermission(mApp.getContext())
-                != mAppsList.hasShortcutHostPermission()) {
-            mApp.getModel().forceReload();
+        if (hasShortcutsPermission(mContext) != mAppsList.hasShortcutHostPermission()) {
+            mModel.forceReload();
         }
     }
 
@@ -123,6 +106,11 @@
     @WorkerThread
     public void modelLoadComplete() { }
 
+    /** Called when grid migration has completed as part of grid size refactor. */
+    @WorkerThread
+    public void gridMigrationComplete(
+            @NonNull DeviceGridState src, @NonNull DeviceGridState dest) { }
+
     /**
      * Called when the delegate is no loner needed
      */
diff --git a/src/com/android/launcher3/model/ModelInitializer.kt b/src/com/android/launcher3/model/ModelInitializer.kt
new file mode 100644
index 0000000..735a52a
--- /dev/null
+++ b/src/com/android/launcher3/model/ModelInitializer.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model
+
+import android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED
+import android.content.ComponentName
+import android.content.Context
+import android.content.SharedPreferences
+import android.content.pm.LauncherApps
+import android.content.pm.LauncherApps.ArchiveCompatibilityParams
+import com.android.launcher3.BuildConfig
+import com.android.launcher3.Flags
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener
+import com.android.launcher3.LauncherModel
+import com.android.launcher3.LauncherPrefs.Companion.getPrefs
+import com.android.launcher3.Utilities
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.graphics.ThemeManager
+import com.android.launcher3.graphics.ThemeManager.ThemeChangeListener
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.icons.LauncherIconProvider
+import com.android.launcher3.icons.LauncherIcons.IconPool
+import com.android.launcher3.notification.NotificationListener
+import com.android.launcher3.pm.InstallSessionHelper
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.DaggerSingletonTracker
+import com.android.launcher3.util.Executors.MODEL_EXECUTOR
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import com.android.launcher3.util.SettingsCache
+import com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI
+import com.android.launcher3.util.SettingsCache.PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI
+import com.android.launcher3.util.SimpleBroadcastReceiver
+import com.android.launcher3.widget.custom.CustomWidgetManager
+import javax.inject.Inject
+
+/** Utility class for initializing all model callbacks */
+class ModelInitializer
+@Inject
+constructor(
+    @ApplicationContext private val context: Context,
+    private val iconPool: IconPool,
+    private val iconCache: IconCache,
+    private val idp: InvariantDeviceProfile,
+    private val themeManager: ThemeManager,
+    private val userCache: UserCache,
+    private val settingsCache: SettingsCache,
+    private val iconProvider: LauncherIconProvider,
+    private val customWidgetManager: CustomWidgetManager,
+    private val installSessionHelper: InstallSessionHelper,
+    private val lifeCycle: DaggerSingletonTracker,
+) {
+
+    fun initialize(model: LauncherModel) {
+        fun refreshAndReloadLauncher() {
+            iconPool.clear()
+            iconCache.updateIconParams(idp.fillResIconDpi, idp.iconBitmapSize)
+            model.forceReload()
+        }
+
+        // IDP changes
+        val idpChangeListener = OnIDPChangeListener { modelChanged ->
+            if (modelChanged) refreshAndReloadLauncher()
+        }
+        idp.addOnChangeListener(idpChangeListener)
+        lifeCycle.addCloseable { idp.removeOnChangeListener(idpChangeListener) }
+
+        // Theme changes
+        val themeChangeListener = ThemeChangeListener { refreshAndReloadLauncher() }
+        themeManager.addChangeListener(themeChangeListener)
+        lifeCycle.addCloseable { themeManager.removeChangeListener(themeChangeListener) }
+
+        // System changes
+        val modelCallbacks = model.newModelCallbacks()
+        val launcherApps = context.getSystemService(LauncherApps::class.java)!!
+        launcherApps.registerCallback(modelCallbacks, MODEL_EXECUTOR.handler)
+        lifeCycle.addCloseable { launcherApps.unregisterCallback(modelCallbacks) }
+
+        if (Utilities.ATLEAST_V && Flags.enableSupportForArchiving()) {
+            launcherApps.setArchiveCompatibility(
+                ArchiveCompatibilityParams().apply {
+                    setEnableUnarchivalConfirmation(false)
+                    setEnableIconOverlay(!Flags.useNewIconForArchivedApps())
+                }
+            )
+        }
+
+        // Device profile policy changes
+        val dpUpdateReceiver =
+            SimpleBroadcastReceiver(context, UI_HELPER_EXECUTOR) { model.reloadStringCache() }
+        dpUpdateReceiver.register(ACTION_DEVICE_POLICY_RESOURCE_UPDATED)
+        lifeCycle.addCloseable { dpUpdateReceiver.unregisterReceiverSafely() }
+
+        // Development helper
+        if (BuildConfig.IS_STUDIO_BUILD) {
+            val reloadReceiver =
+                SimpleBroadcastReceiver(context, UI_HELPER_EXECUTOR) { model.forceReload() }
+            reloadReceiver.register(Context.RECEIVER_EXPORTED, ACTION_FORCE_RELOAD)
+            lifeCycle.addCloseable { reloadReceiver.unregisterReceiverSafely() }
+        }
+
+        // User changes
+        lifeCycle.addCloseable(userCache.addUserEventListener(model::onUserEvent))
+
+        // Private space settings changes
+        val psSettingsListener = SettingsCache.OnChangeListener { model.forceReload() }
+        settingsCache.register(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, psSettingsListener)
+        lifeCycle.addCloseable {
+            settingsCache.unregister(PRIVATE_SPACE_HIDE_WHEN_LOCKED_URI, psSettingsListener)
+        }
+
+        // Notification dots changes
+        val notificationChanges =
+            SettingsCache.OnChangeListener { dotsEnabled ->
+                if (dotsEnabled)
+                    NotificationListener.requestRebind(
+                        ComponentName(context, NotificationListener::class.java)
+                    )
+            }
+        settingsCache.register(NOTIFICATION_BADGING_URI, notificationChanges)
+        notificationChanges.onSettingsChanged(settingsCache.getValue(NOTIFICATION_BADGING_URI))
+        lifeCycle.addCloseable {
+            settingsCache.unregister(NOTIFICATION_BADGING_URI, notificationChanges)
+        }
+
+        // removable smartspace
+        if (Flags.enableSmartspaceRemovalToggle()) {
+            val smartSpacePrefChanges =
+                SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
+                    if (LoaderTask.SMARTSPACE_ON_HOME_SCREEN == key) model.forceReload()
+                }
+            getPrefs(context).registerOnSharedPreferenceChangeListener(smartSpacePrefChanges)
+            lifeCycle.addCloseable {
+                getPrefs(context).unregisterOnSharedPreferenceChangeListener(smartSpacePrefChanges)
+            }
+        }
+
+        // Custom widgets
+        lifeCycle.addCloseable(customWidgetManager.addWidgetRefreshCallback(model::rebindCallbacks))
+
+        // Icon changes
+        lifeCycle.addCloseable(
+            iconProvider.registerIconChangeListener(model::onAppIconChanged, MODEL_EXECUTOR.handler)
+        )
+
+        // Install session changes
+        lifeCycle.addCloseable(installSessionHelper.registerInstallTracker(modelCallbacks))
+    }
+
+    companion object {
+        private const val ACTION_FORCE_RELOAD = "force-reload-launcher"
+    }
+}
diff --git a/src/com/android/launcher3/model/ModelTaskController.kt b/src/com/android/launcher3/model/ModelTaskController.kt
index fc53343..5566482 100644
--- a/src/com/android/launcher3/model/ModelTaskController.kt
+++ b/src/com/android/launcher3/model/ModelTaskController.kt
@@ -22,7 +22,6 @@
 import com.android.launcher3.celllayout.CellPosMapper
 import com.android.launcher3.model.BgDataModel.FixedContainerItems
 import com.android.launcher3.model.data.ItemInfo
-import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.util.PackageUserKey
 import com.android.launcher3.widget.model.WidgetsListBaseEntriesBuilder
 import java.util.Objects
@@ -51,18 +50,17 @@
      */
     fun getModelWriter() = model.getWriter(false /* verifyChanges */, CellPosMapper.DEFAULT, null)
 
-    fun bindUpdatedWorkspaceItems(allUpdates: List<WorkspaceItemInfo>) {
+    fun bindUpdatedWorkspaceItems(allUpdates: Collection<ItemInfo>) {
         // Bind workspace items
-        val workspaceUpdates =
-            allUpdates.stream().filter { info -> info.id != ItemInfo.NO_ID }.toList()
+        val workspaceUpdates = allUpdates.filter { it.id != ItemInfo.NO_ID }.toSet()
         if (workspaceUpdates.isNotEmpty()) {
-            scheduleCallbackTask { it.bindWorkspaceItemsChanged(workspaceUpdates) }
+            scheduleCallbackTask { it.bindItemsUpdated(workspaceUpdates) }
         }
 
         // Bind extra items if any
         allUpdates
             .stream()
-            .mapToInt { info: WorkspaceItemInfo -> info.container }
+            .mapToInt { it.container }
             .distinct()
             .mapToObj { dataModel.extraItems.get(it) }
             .filter { Objects.nonNull(it) }
@@ -79,19 +77,10 @@
     }
 
     fun bindUpdatedWidgets(dataModel: BgDataModel) {
-        val widgetsByPackageItem = dataModel.widgetsModel.widgetsByPackageItem
-        val allWidgets = WidgetsListBaseEntriesBuilder(app.context).build(widgetsByPackageItem)
-
-        val defaultWidgetsFilter = dataModel.widgetsModel.defaultWidgetsFilter
-        val defaultWidgets =
-            if (defaultWidgetsFilter != null) {
-                WidgetsListBaseEntriesBuilder(app.context)
-                    .build(widgetsByPackageItem, defaultWidgetsFilter)
-            } else {
-                emptyList()
-            }
-
-        scheduleCallbackTask { it.bindAllWidgets(allWidgets, defaultWidgets) }
+        val allWidgets =
+            WidgetsListBaseEntriesBuilder(app.context)
+                .build(dataModel.widgetsModel.widgetsByPackageItemForPicker)
+        scheduleCallbackTask { it.bindAllWidgets(allWidgets) }
     }
 
     fun deleteAndBindComponentsRemoved(matcher: Predicate<ItemInfo?>, reason: String?) {
diff --git a/src/com/android/launcher3/model/ModelUtils.java b/src/com/android/launcher3/model/ModelUtils.java
index 9e72e28..da79982 100644
--- a/src/com/android/launcher3/model/ModelUtils.java
+++ b/src/com/android/launcher3/model/ModelUtils.java
@@ -15,15 +15,15 @@
  */
 package com.android.launcher3.model;
 
-import com.android.launcher3.LauncherSettings;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
+
 import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.stream.IntStream;
+import java.util.function.Predicate;
 
 /**
  * Utils class for {@link com.android.launcher3.LauncherModel}.
@@ -31,54 +31,17 @@
 public class ModelUtils {
 
     /**
-     * Filters the set of items who are directly or indirectly (via another container) on the
-     * specified screen.
+     * Returns a filter for items on hotseat or current screens
      */
-    public static <T extends ItemInfo> void filterCurrentWorkspaceItems(
-            final IntSet currentScreenIds,
-            List<? extends T> allWorkspaceItems,
-            List<T> currentScreenItems,
-            List<T> otherScreenItems) {
-        // Purge any null ItemInfos
-        allWorkspaceItems.removeIf(Objects::isNull);
-        // Order the set of items by their containers first, this allows use to walk through the
-        // list sequentially, build up a list of containers that are in the specified screen,
-        // as well as all items in those containers.
-        IntSet itemsOnScreen = new IntSet();
-        Collections.sort(allWorkspaceItems,
-                (lhs, rhs) -> Integer.compare(lhs.container, rhs.container));
-        for (T info : allWorkspaceItems) {
-            if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
-                if (currentScreenIds.contains(info.screenId)) {
-                    currentScreenItems.add(info);
-                    itemsOnScreen.add(info.id);
-                } else {
-                    otherScreenItems.add(info);
-                }
-            } else if (info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
-                currentScreenItems.add(info);
-                itemsOnScreen.add(info.id);
-            } else {
-                if (itemsOnScreen.contains(info.container)) {
-                    currentScreenItems.add(info);
-                    itemsOnScreen.add(info.id);
-                } else {
-                    otherScreenItems.add(info);
-                }
-            }
-        }
+    public static Predicate<ItemInfo> currentScreenContentFilter(IntSet currentScreenIds) {
+        return item -> item.container == CONTAINER_HOTSEAT
+                || (item.container == CONTAINER_DESKTOP
+                        && currentScreenIds.contains(item.screenId));
     }
 
     /**
-     * Iterates though current workspace items and returns available hotseat ranks for prediction.
+     * Returns a filter for widget items
      */
-    public static IntArray getMissingHotseatRanks(List<ItemInfo> items, int len) {
-        IntSet seen = new IntSet();
-        items.stream().filter(
-                info -> info.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT)
-                .forEach(i -> seen.add(i.screenId));
-        IntArray result = new IntArray(len);
-        IntStream.range(0, len).filter(i -> !seen.contains(i)).forEach(result::add);
-        return result;
-    }
+    public static final Predicate<ItemInfo> WIDGET_FILTER = item ->
+            item.itemType == ITEM_TYPE_APPWIDGET || item.itemType == ITEM_TYPE_CUSTOM_APPWIDGET;
 }
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index b477cb1..0332775 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -459,37 +459,14 @@
                 if (item.container != Favorites.CONTAINER_DESKTOP &&
                         item.container != Favorites.CONTAINER_HOTSEAT) {
                     // Item is in a collection, make sure this collection exists
-                    if (!mBgDataModel.collections.containsKey(item.container)) {
+                    if (!(mBgDataModel.itemsIdMap.get(item.container) instanceof CollectionInfo)) {
                         // An items container is being set to a that of an item which is not in
-                        // the list of Folders.
+                        // the list of collections.
                         String msg = "item: " + item + " container being set to: " +
                                 item.container + ", not in the list of collections";
                         Log.e(TAG, msg);
                     }
                 }
-
-                // Items are added/removed from the corresponding FolderInfo elsewhere, such
-                // as in Workspace.onDrop. Here, we just add/remove them from the list of items
-                // that are on the desktop, as appropriate
-                ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
-                if (modelItem != null &&
-                        (modelItem.container == Favorites.CONTAINER_DESKTOP ||
-                                modelItem.container == Favorites.CONTAINER_HOTSEAT)) {
-                    switch (modelItem.itemType) {
-                        case Favorites.ITEM_TYPE_APPLICATION:
-                        case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                        case Favorites.ITEM_TYPE_FOLDER:
-                        case Favorites.ITEM_TYPE_APP_PAIR:
-                            if (!mBgDataModel.workspaceItems.contains(modelItem)) {
-                                mBgDataModel.workspaceItems.add(modelItem);
-                            }
-                            break;
-                        default:
-                            break;
-                    }
-                } else {
-                    mBgDataModel.workspaceItems.remove(modelItem);
-                }
                 mVerifier.verifyModel();
             }
         }
diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
index d238213..a216042 100644
--- a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
+++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
+
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -85,16 +87,19 @@
                 }
             });
 
-            for (LauncherAppWidgetInfo widget : dataModel.appWidgets) {
-                if (widget.providerName.getPackageName().equals(mInstallInfo.packageName)) {
-                    widget.installProgress = mInstallInfo.progress;
-                    updates.add(widget);
-                }
-            }
+            dataModel.itemsIdMap.stream()
+                    .filter(WIDGET_FILTER)
+                    .filter(item -> mInstallInfo.user.equals(item.user))
+                    .map(item -> (LauncherAppWidgetInfo) item)
+                    .filter(widget -> widget.providerName.getPackageName()
+                            .equals(mInstallInfo.packageName))
+                    .forEach(widget -> {
+                        widget.installProgress = mInstallInfo.progress;
+                        updates.add(widget);
+                    });
 
             if (!updates.isEmpty()) {
-                taskController.scheduleCallbackTask(
-                        callbacks -> callbacks.bindRestoreItemsChange(updates));
+                taskController.bindUpdatedWorkspaceItems(updates);
             }
         }
     }
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 5464afe..3cdb250 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -15,10 +15,13 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
+import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
+import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
 import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
 import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_ICON;
 
@@ -37,7 +40,6 @@
 import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel.ModelUpdateTask;
-import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.IconCache;
@@ -212,8 +214,7 @@
 
         // Update shortcut infos
         if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
-            final ArrayList<WorkspaceItemInfo> updatedWorkspaceItems = new ArrayList<>();
-            final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>();
+            final ArrayList<ItemInfo> updatedWorkspaceItems = new ArrayList<>();
 
             // For system apps, package manager send OP_UPDATE when an app is enabled.
             final boolean isNewApkAvailable = mOp == OP_ADD || mOp == OP_UPDATE;
@@ -237,23 +238,32 @@
                         if (itemInfo.isPromise() && isNewApkAvailable) {
                             boolean isTargetValid = !cn.getClassName().equals(
                                     IconCache.EMPTY_CLASS_NAME);
-                            if (itemInfo.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+                            if (itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
+                                int requestQuery = ShortcutRequest.PINNED;
+                                if (Flags.restoreArchivedShortcuts()) {
+                                    // Avoid race condition where shortcut service has no record of
+                                    // unarchived shortcut being pinned after restore.
+                                    // Launcher should be source-of-truth for if shortcut is pinned.
+                                    requestQuery = ShortcutRequest.ALL;
+                                }
                                 List<ShortcutInfo> shortcut =
                                         new ShortcutRequest(context, mUser)
                                                 .forPackage(cn.getPackageName(),
                                                         itemInfo.getDeepShortcutId())
-                                                .query(ShortcutRequest.PINNED);
+                                                .query(requestQuery);
                                 if (shortcut.isEmpty()) {
                                     isTargetValid = false;
                                     if (DEBUG) {
-                                        Log.d(TAG, "Pinned Shortcut not found for updated"
-                                                + " package=" + itemInfo.getTargetPackage());
+                                        Log.d(TAG, "Shortcut not found for updated"
+                                                + " package=" + itemInfo.getTargetPackage()
+                                                + ", isArchived=" + itemInfo.isArchived());
                                     }
                                 } else {
                                     if (DEBUG) {
-                                        Log.d(TAG, "Found pinned shortcut for updated"
+                                        Log.d(TAG, "Found shortcut for updated"
                                                 + " package=" + itemInfo.getTargetPackage()
-                                                + ", isTargetValid=" + isTargetValid);
+                                                + ", isTargetValid=" + isTargetValid
+                                                + ", isArchived=" + itemInfo.isArchived());
                                     }
                                     itemInfo.updateFromDeepShortcutInfo(shortcut.get(0), context);
                                     infoUpdated = true;
@@ -268,7 +278,7 @@
                                     || itemInfo.isArchived())) {
                                 if (updateWorkspaceItemIntent(context, itemInfo, packageName)) {
                                     infoUpdated = true;
-                                } else if (itemInfo.hasPromiseIconUi()) {
+                                } else if (shouldRemoveRestoredShortcut(itemInfo)) {
                                     removedShortcuts.add(itemInfo.id);
                                     if (DEBUG) {
                                         FileLog.w(TAG, "Removing restored shortcut promise icon"
@@ -326,7 +336,8 @@
                                     itemInfo.setNonResizeable(ApiWrapper.INSTANCE.get(context)
                                             .isNonResizeableActivity(activities.get(0)));
                                 }
-                                iconCache.getTitleAndIcon(itemInfo, itemInfo.usingLowResIcon());
+                                iconCache.getTitleAndIcon(
+                                        itemInfo, itemInfo.getMatchingLookupFlag());
                                 infoUpdated = true;
                             }
                         }
@@ -346,24 +357,25 @@
                     }
                 });
 
-                for (LauncherAppWidgetInfo widgetInfo : dataModel.appWidgets) {
-                    if (mUser.equals(widgetInfo.user)
-                            && widgetInfo.hasRestoreFlag(
-                            LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
-                            && packageSet.contains(widgetInfo.providerName.getPackageName())) {
-                        widgetInfo.restoreStatus &=
-                                ~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY
-                                        & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
+                dataModel.itemsIdMap.stream()
+                        .filter(WIDGET_FILTER)
+                        .filter(item -> mUser.equals(item.user))
+                        .map(item -> (LauncherAppWidgetInfo) item)
+                        .filter(widget -> widget.hasRestoreFlag(FLAG_PROVIDER_NOT_READY)
+                                && packageSet.contains(widget.providerName.getPackageName()))
+                        .forEach(widgetInfo -> {
+                            widgetInfo.restoreStatus &=
+                                    ~FLAG_PROVIDER_NOT_READY
+                                            & ~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
 
-                        // adding this flag ensures that launcher shows 'click to setup'
-                        // if the widget has a config activity. In case there is no config
-                        // activity, it will be marked as 'restored' during bind.
-                        widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
-
-                        widgets.add(widgetInfo);
-                        taskController.getModelWriter().updateItemInDatabase(widgetInfo);
-                    }
-                }
+                            // adding this flag ensures that launcher shows 'click to setup'
+                            // if the widget has a config activity. In case there is no config
+                            // activity, it will be marked as 'restored' during bind.
+                            widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
+                            widgetInfo.installProgress = 100;
+                            updatedWorkspaceItems.add(widgetInfo);
+                            taskController.getModelWriter().updateItemInDatabase(widgetInfo);
+                        });
             }
 
             taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItems);
@@ -373,10 +385,6 @@
                         "removing shortcuts with invalid target components."
                                 + " ids=" + removedShortcuts);
             }
-
-            if (!widgets.isEmpty()) {
-                taskController.scheduleCallbackTask(c -> c.bindWidgetsRestored(widgets));
-            }
         }
 
         final HashSet<String> removedPackages = new HashSet<>();
@@ -437,7 +445,7 @@
      */
     private boolean updateWorkspaceItemIntent(Context context,
             WorkspaceItemInfo si, String packageName) {
-        if (si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
+        if (si.itemType == ITEM_TYPE_DEEP_SHORTCUT) {
             // Do not update intent for deep shortcuts as they contain additional information
             // about the shortcut.
             return false;
@@ -453,6 +461,15 @@
         return false;
     }
 
+    private boolean shouldRemoveRestoredShortcut(WorkspaceItemInfo itemInfo) {
+        if (itemInfo.hasPromiseIconUi() && !Flags.restoreArchivedShortcuts()) {
+            return true;
+        }
+        return Flags.restoreArchivedShortcuts()
+                && !itemInfo.isArchived()
+                && itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT;
+    }
+
     private String getOpString() {
         return switch (mOp) {
             case OP_NONE -> "NONE";
diff --git a/src/com/android/launcher3/model/SessionFailureTask.kt b/src/com/android/launcher3/model/SessionFailureTask.kt
index 0d006fa..8baf568 100644
--- a/src/com/android/launcher3/model/SessionFailureTask.kt
+++ b/src/com/android/launcher3/model/SessionFailureTask.kt
@@ -48,7 +48,7 @@
                 for (info in dataModel.itemsIdMap) {
                     if (info is WorkspaceItemInfo && info.isArchived && user == info.user) {
                         // Refresh icons on the workspace for archived apps.
-                        iconCache.getTitleAndIcon(info, info.usingLowResIcon())
+                        iconCache.getTitleAndIcon(info, info.matchingLookupFlag)
                         updatedItems.add(info)
                     }
                 }
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
deleted file mode 100644
index b5a7382..0000000
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2016 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.content.Context;
-import android.content.pm.ShortcutInfo;
-import android.os.UserHandle;
-
-import androidx.annotation.NonNull;
-
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherModel.ModelUpdateTask;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.icons.CacheableShortcutInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.shortcuts.ShortcutKey;
-import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.util.ApplicationInfoWrapper;
-import com.android.launcher3.util.ItemInfoMatcher;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Handles changes due to shortcut manager updates (deep shortcut changes)
- */
-public class ShortcutsChangedTask implements ModelUpdateTask {
-
-    @NonNull
-    private final String mPackageName;
-
-    @NonNull
-    private final List<ShortcutInfo> mShortcuts;
-
-    @NonNull
-    private final UserHandle mUser;
-
-    private final boolean mUpdateIdMap;
-
-    public ShortcutsChangedTask(@NonNull final String packageName,
-            @NonNull final List<ShortcutInfo> shortcuts, @NonNull final UserHandle user,
-            final boolean updateIdMap) {
-        mPackageName = packageName;
-        mShortcuts = shortcuts;
-        mUser = user;
-        mUpdateIdMap = updateIdMap;
-    }
-
-    @Override
-    public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
-            @NonNull AllAppsList apps) {
-        final LauncherAppState app = taskController.getApp();
-        final Context context = app.getContext();
-        // Find WorkspaceItemInfo's that have changed on the workspace.
-        ArrayList<WorkspaceItemInfo> matchingWorkspaceItems = new ArrayList<>();
-
-        synchronized (dataModel) {
-            dataModel.forAllWorkspaceItemInfos(mUser, si -> {
-                if ((si.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)
-                        && mPackageName.equals(si.getIntent().getPackage())) {
-                    matchingWorkspaceItems.add(si);
-                }
-            });
-        }
-
-        if (!matchingWorkspaceItems.isEmpty()) {
-            ApplicationInfoWrapper infoWrapper =
-                    new ApplicationInfoWrapper(context, mPackageName, mUser);
-            if (mShortcuts.isEmpty()) {
-                // Verify that the app is indeed installed.
-                if (!infoWrapper.isInstalled() && !infoWrapper.isArchived()) {
-                    // App is not installed or archived, ignoring package events
-                    return;
-                }
-            }
-            // Update the workspace to reflect the changes to updated shortcuts residing on it.
-            List<String> allLauncherKnownIds = matchingWorkspaceItems.stream()
-                    .map(WorkspaceItemInfo::getDeepShortcutId)
-                    .distinct()
-                    .collect(Collectors.toList());
-            List<ShortcutInfo> shortcuts = new ShortcutRequest(context, mUser)
-                    .forPackage(mPackageName, allLauncherKnownIds)
-                    .query(ShortcutRequest.ALL);
-
-            Set<String> nonPinnedIds = new HashSet<>(allLauncherKnownIds);
-            ArrayList<WorkspaceItemInfo> updatedWorkspaceItemInfos = new ArrayList<>();
-            for (ShortcutInfo fullDetails : shortcuts) {
-                if (!fullDetails.isPinned()) {
-                    continue;
-                }
-                String sid = fullDetails.getId();
-                nonPinnedIds.remove(sid);
-                matchingWorkspaceItems
-                        .stream()
-                        .filter(itemInfo -> sid.equals(itemInfo.getDeepShortcutId()))
-                        .forEach(workspaceItemInfo -> {
-                            workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context);
-                            app.getIconCache().getShortcutIcon(workspaceItemInfo,
-                                    new CacheableShortcutInfo(fullDetails, infoWrapper));
-                            updatedWorkspaceItemInfos.add(workspaceItemInfo);
-                        });
-            }
-
-            taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos);
-            if (!nonPinnedIds.isEmpty()) {
-                taskController.deleteAndBindComponentsRemoved(ItemInfoMatcher.ofShortcutKeys(
-                        nonPinnedIds.stream()
-                                .map(id -> new ShortcutKey(mPackageName, mUser, id))
-                                .collect(Collectors.toSet())),
-                        "removed because the shortcut is no longer available in shortcut service");
-            }
-        }
-
-        if (mUpdateIdMap) {
-            // Update the deep shortcut map if the list of ids has changed for an activity.
-            dataModel.updateDeepShortcutCounts(mPackageName, mUser, mShortcuts);
-            taskController.bindDeepShortcuts(dataModel);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.kt b/src/com/android/launcher3/model/ShortcutsChangedTask.kt
new file mode 100644
index 0000000..56e9e43
--- /dev/null
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.model
+
+import android.content.pm.ShortcutInfo
+import android.os.UserHandle
+import com.android.launcher3.Flags
+import com.android.launcher3.LauncherModel.ModelUpdateTask
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+import com.android.launcher3.icons.CacheableShortcutInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.shortcuts.ShortcutRequest
+import com.android.launcher3.util.ApplicationInfoWrapper
+import com.android.launcher3.util.ItemInfoMatcher
+
+/** Handles changes due to shortcut manager updates (deep shortcut changes) */
+class ShortcutsChangedTask(
+    private val packageName: String,
+    private val shortcuts: List<ShortcutInfo>,
+    private val user: UserHandle,
+    private val shouldUpdateIdMap: Boolean,
+) : ModelUpdateTask {
+
+    override fun execute(
+        taskController: ModelTaskController,
+        dataModel: BgDataModel,
+        apps: AllAppsList,
+    ) {
+        val app = taskController.app
+        val context = app.context
+        // Find WorkspaceItemInfo's that have changed on the workspace.
+        val matchingWorkspaceItems = ArrayList<WorkspaceItemInfo>()
+
+        synchronized(dataModel) {
+            dataModel.forAllWorkspaceItemInfos(user) { wai: WorkspaceItemInfo ->
+                if (
+                    (wai.itemType == ITEM_TYPE_DEEP_SHORTCUT) &&
+                        packageName == wai.getIntent().getPackage()
+                ) {
+                    matchingWorkspaceItems.add(wai)
+                }
+            }
+        }
+
+        if (matchingWorkspaceItems.isNotEmpty()) {
+            val infoWrapper = ApplicationInfoWrapper(context, packageName, user)
+            if (shortcuts.isEmpty()) {
+                // Verify that the app is indeed installed.
+                if (
+                    (!infoWrapper.isInstalled() && !infoWrapper.isArchived()) ||
+                        (Flags.restoreArchivedShortcuts() && infoWrapper.isArchived())
+                ) {
+                    // App is not installed or is archived, ignoring package events
+                    return
+                }
+            }
+            // Update the workspace to reflect the changes to updated shortcuts residing on it.
+            val allLauncherKnownIds =
+                matchingWorkspaceItems.map { item -> item.deepShortcutId }.distinct()
+            val shortcuts: List<ShortcutInfo> =
+                ShortcutRequest(context, user)
+                    .forPackage(packageName, allLauncherKnownIds)
+                    .query(ShortcutRequest.ALL)
+
+            val nonPinnedIds: MutableSet<String> = HashSet(allLauncherKnownIds)
+            val updatedWorkspaceItemInfos = ArrayList<WorkspaceItemInfo>()
+            for (fullDetails in shortcuts) {
+                if (!fullDetails.isPinned && !Flags.restoreArchivedShortcuts()) {
+                    continue
+                }
+                val shortcutId = fullDetails.id
+                nonPinnedIds.remove(shortcutId)
+                matchingWorkspaceItems
+                    .filter { itemInfo: WorkspaceItemInfo -> shortcutId == itemInfo.deepShortcutId }
+                    .forEach { workspaceItemInfo: WorkspaceItemInfo ->
+                        workspaceItemInfo.updateFromDeepShortcutInfo(fullDetails, context)
+                        app.iconCache.getShortcutIcon(
+                            workspaceItemInfo,
+                            CacheableShortcutInfo(fullDetails, infoWrapper),
+                        )
+                        updatedWorkspaceItemInfos.add(workspaceItemInfo)
+                    }
+            }
+
+            taskController.bindUpdatedWorkspaceItems(updatedWorkspaceItemInfos)
+            if (nonPinnedIds.isNotEmpty()) {
+                taskController.deleteAndBindComponentsRemoved(
+                    ItemInfoMatcher.ofShortcutKeys(
+                        nonPinnedIds
+                            .map { id: String? -> ShortcutKey(packageName, user, id) }
+                            .toSet()
+                    ),
+                    "removed because the shortcut is no longer available in shortcut service",
+                )
+            }
+        }
+
+        if (shouldUpdateIdMap) {
+            // Update the deep shortcut map if the list of ids has changed for an activity.
+            dataModel.updateDeepShortcutCounts(packageName, user, shortcuts)
+            taskController.bindDeepShortcuts(dataModel)
+        }
+    }
+}
diff --git a/src/com/android/launcher3/model/WidgetsFilterDataProvider.kt b/src/com/android/launcher3/model/WidgetsFilterDataProvider.kt
index 0571de3..90d6fb2 100644
--- a/src/com/android/launcher3/model/WidgetsFilterDataProvider.kt
+++ b/src/com/android/launcher3/model/WidgetsFilterDataProvider.kt
@@ -16,55 +16,38 @@
 
 package com.android.launcher3.model
 
-import android.content.Context
-import androidx.annotation.WorkerThread
-import com.android.launcher3.R
-import com.android.launcher3.util.ResourceBasedOverride
+import com.android.launcher3.dagger.LauncherAppSingleton
 import java.util.function.Predicate
+import javax.inject.Inject
 
 /** Helper for the widgets model to load the filters that can be applied to available widgets. */
-open class WidgetsFilterDataProvider(val context: Context) : ResourceBasedOverride {
+@LauncherAppSingleton
+open class WidgetsFilterDataProvider @Inject constructor() {
+
+    /** Filter that should be applied to the widget predictions */
+    open val predictedWidgetsFilter: Predicate<WidgetItem>? = null
+
     /**
-     * Start regular periodic refresh of widget filtering data starting now (if not started
-     * already).
+     * Filter that should be applied to the widgets list to see which widgets can be shown by
+     * default.
      */
-    @WorkerThread
-    open fun initPeriodicDataRefresh(callback: WidgetsFilterLoadedCallback? = null) {
-        // no-op
+    open val defaultWidgetsFilter: Predicate<WidgetItem>? = null
+
+    protected val listeners = mutableListOf<WidgetsFilterLoadedCallback>()
+
+    /** Adds a callback for listening to filter changes */
+    fun addFilterChangeCallback(callback: WidgetsFilterLoadedCallback) {
+        listeners.add(callback)
     }
 
-    /**
-     * Returns a filter that should be applied to the widget predictions.
-     *
-     * @return null if no filter needs to be applied
-     */
-    @WorkerThread open fun getPredictedWidgetsFilter(): Predicate<WidgetItem>? = null
-
-    /**
-     * Returns a filter that should be applied to the widgets list to see which widgets can be shown
-     * by default.
-     *
-     * @return null if no separate "default" list is supported
-     */
-    @WorkerThread open fun getDefaultWidgetsFilter(): Predicate<WidgetItem>? = null
-
-    /** Called when filter data provider is no longer needed. */
-    open fun destroy() {}
-
-    companion object {
-        /** Returns a new instance of the [WidgetsFilterDataProvider] based on resource override. */
-        fun newInstance(context: Context?): WidgetsFilterDataProvider {
-            return ResourceBasedOverride.Overrides.getObject(
-                WidgetsFilterDataProvider::class.java,
-                context,
-                R.string.widgets_filter_data_provider_class,
-            )
-        }
+    /** Removes a previously added callback */
+    fun removeFilterChangeCallback(callback: WidgetsFilterLoadedCallback) {
+        listeners.remove(callback)
     }
-}
 
-/** Interface for the model callback to be invoked when filters are loaded. */
-interface WidgetsFilterLoadedCallback {
-    /** Method called back when widget filters are loaded */
-    fun onWidgetsFilterLoaded()
+    /** Interface for the model callback to be invoked when filters are loaded. */
+    interface WidgetsFilterLoadedCallback {
+        /** Method called back when widget filters are loaded */
+        fun onWidgetsFilterLoaded()
+    }
 }
diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java
index a27d2f1..52b142d 100644
--- a/src/com/android/launcher3/model/WidgetsModel.java
+++ b/src/com/android/launcher3/model/WidgetsModel.java
@@ -4,6 +4,7 @@
 import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER;
 
 import static com.android.launcher3.BuildConfig.WIDGETS_ENABLED;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.pm.ShortcutConfigActivityInfo.queryList;
 import static com.android.launcher3.widget.WidgetSections.NO_CATEGORY;
 
@@ -18,8 +19,6 @@
 import android.util.Log;
 import android.util.Pair;
 
-import androidx.annotation.AnyThread;
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.collection.ArrayMap;
 
@@ -67,8 +66,7 @@
 
     /* Map of widgets and shortcuts that are tracked per package. */
     private final Map<PackageItemInfo, List<WidgetItem>> mWidgetsByPackageItem = new HashMap<>();
-    @Nullable private Predicate<WidgetItem> mDefaultWidgetsFilter = null;
-    @Nullable private Predicate<WidgetItem> mPredictedWidgetsFilter = null;
+    @Nullable private WidgetValidityCheckForPicker mWidgetValidityCheckForPicker = null;
 
     /**
      * Returns all widgets keyed by their component key.
@@ -86,44 +84,44 @@
     }
 
     /**
-     * Returns widgets grouped by the package item that they should belong to.
+     * Returns widgets (eligible for display in picker) keyed by their component key.
      */
-    public synchronized Map<PackageItemInfo, List<WidgetItem>> getWidgetsByPackageItem() {
-        if (!WIDGETS_ENABLED) {
+    public synchronized Map<ComponentKey, WidgetItem> getWidgetsByComponentKeyForPicker() {
+        if (!WIDGETS_ENABLED || mWidgetValidityCheckForPicker == null) {
             return Collections.emptyMap();
         }
-        return new HashMap<>(mWidgetsByPackageItem);
+
+        return mWidgetsByPackageItem.values().stream()
+                .flatMap(Collection::stream).distinct()
+                .filter(widgetItem -> mWidgetValidityCheckForPicker.test(widgetItem))
+                .collect(Collectors.toMap(
+                        widget -> new ComponentKey(widget.componentName, widget.user),
+                        Function.identity()
+                ));
     }
 
     /**
-     * Returns widget filter that can be applied to {@link WidgetItem}s to check if they can be
-     * shown in the default widgets list.
-     * <p>Returns null if filtering isn't available</p>
+     * Returns widgets (displayable in the widget picker) grouped by the package item that
+     * they should belong to.
      */
-    @AnyThread
-    public @Nullable Predicate<WidgetItem> getDefaultWidgetsFilter() {
-        return mDefaultWidgetsFilter;
-    }
-
-    /**
-     * Returns widget filter that can be applied to {@link WidgetItem}s to check if they can be
-     * part of widget predictions.
-     * <p>Returns null if filter isn't available</p>
-     */
-    @AnyThread
-    public @Nullable  Predicate<WidgetItem> getPredictedWidgetsFilter() {
-        return mPredictedWidgetsFilter;
-    }
-
-    /**
-     * Updates model with latest filter data in cache.
-     */
-    public void updateWidgetFilters(@NonNull WidgetsFilterDataProvider widgetsFilterDataProvider) {
-        if (!WIDGETS_ENABLED) {
-            return;
+    public synchronized Map<PackageItemInfo, List<WidgetItem>> getWidgetsByPackageItemForPicker() {
+        if (!WIDGETS_ENABLED || mWidgetValidityCheckForPicker == null) {
+            return Collections.emptyMap();
         }
-        mDefaultWidgetsFilter = widgetsFilterDataProvider.getDefaultWidgetsFilter();
-        mPredictedWidgetsFilter = widgetsFilterDataProvider.getPredictedWidgetsFilter();
+
+        return mWidgetsByPackageItem.entrySet().stream()
+                .collect(
+                        Collectors.toMap(
+                                Map.Entry::getKey,
+                                entry -> entry.getValue().stream()
+                                        .filter(widgetItem ->
+                                                mWidgetValidityCheckForPicker.test(widgetItem))
+                                        .collect(Collectors.toList())
+                        )
+                )
+                .entrySet().stream()
+                .filter(entry -> !entry.getValue().isEmpty())
+                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
     }
 
     /**
@@ -180,6 +178,9 @@
             Log.d(TAG, "addWidgetsAndShortcuts, widgetsShortcuts#=" + rawWidgetsShortcuts.size());
         }
 
+        // Refresh the validity checker with latest app state.
+        mWidgetValidityCheckForPicker = new WidgetValidityCheckForPicker(app);
+
         // Temporary cache for {@link PackageItemInfos} to avoid having to go through
         // {@link mPackageItemInfos} to locate the key to be used for {@link #mWidgetsList}
         PackageItemInfoCache packageItemInfoCache = new PackageItemInfoCache();
@@ -194,7 +195,6 @@
 
         // add and update.
         mWidgetsByPackageItem.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)))
@@ -203,7 +203,7 @@
         // Update each package entry
         IconCache iconCache = app.getIconCache();
         for (PackageItemInfo p : packageItemInfoCache.values()) {
-            iconCache.getTitleAndIconForApp(p, true /* userLowResIcon */);
+            iconCache.getTitleAndIconForApp(p, DEFAULT_LOOKUP_FLAG.withUseLowRes());
         }
     }
 
@@ -269,12 +269,15 @@
         return packageUserKeys;
     }
 
-    private static class WidgetValidityCheck implements Predicate<WidgetItem> {
+    /**
+     * Checks if widgets are eligible for displaying in widget picker / tray.
+     */
+    private static class WidgetValidityCheckForPicker implements Predicate<WidgetItem> {
 
         private final InvariantDeviceProfile mIdp;
         private final AppFilter mAppFilter;
 
-        WidgetValidityCheck(LauncherAppState app) {
+        WidgetValidityCheckForPicker(LauncherAppState app) {
             mIdp = app.getInvariantDeviceProfile();
             mAppFilter = new AppFilter(app.getContext());
         }
@@ -309,6 +312,10 @@
         }
     }
 
+    /**
+     * Checks if certain widgets that are available behind flag can be used across all surfaces in
+     * launcher.
+     */
     private static class WidgetFlagCheck implements Predicate<WidgetItem> {
 
         private static final String BUBBLES_SHORTCUT_WIDGET =
diff --git a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
index e86b592..99f2837 100644
--- a/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
+++ b/src/com/android/launcher3/model/WorkspaceItemProcessor.kt
@@ -20,6 +20,7 @@
 import android.content.ComponentName
 import android.content.Intent
 import android.content.pm.LauncherApps
+import android.content.pm.LauncherApps.ShortcutQuery
 import android.content.pm.PackageInstaller
 import android.content.pm.ShortcutInfo
 import android.graphics.Point
@@ -31,8 +32,8 @@
 import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherSettings.Favorites
 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
-import com.android.launcher3.config.FeatureFlags
 import com.android.launcher3.icons.CacheableShortcutInfo
+import com.android.launcher3.icons.cache.CacheLookupFlag.Companion.DEFAULT_LOOKUP_FLAG
 import com.android.launcher3.logging.FileLog
 import com.android.launcher3.model.data.AppInfo
 import com.android.launcher3.model.data.AppPairInfo
@@ -44,6 +45,7 @@
 import com.android.launcher3.pm.PackageInstallInfo
 import com.android.launcher3.pm.UserCache
 import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.shortcuts.ShortcutRequest
 import com.android.launcher3.util.ApiWrapper
 import com.android.launcher3.util.ApplicationInfoWrapper
 import com.android.launcher3.util.ComponentKey
@@ -192,27 +194,42 @@
         if (intent.`package` == null) {
             intent.`package` = targetPkg
         }
+
+        val isPreArchivedShortcut =
+            Flags.restoreArchivedShortcuts() &&
+                appInfoWrapper.isArchived() &&
+                c.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
+                c.restoreFlag != 0
+
         // else if cn == null => can't infer much, leave it
         // else if !validPkg => could be restored icon or missing sd-card
         when {
-            !TextUtils.isEmpty(targetPkg) && !validTarget -> {
+            !TextUtils.isEmpty(targetPkg) && (!validTarget || isPreArchivedShortcut) -> {
                 // Points to a valid app (superset of cn != null) but the apk
                 // is not available.
                 when {
-                    c.restoreFlag != 0 -> {
+                    c.restoreFlag != 0 || isPreArchivedShortcut -> {
                         // Package is not yet available but might be
                         // installed later.
-                        FileLog.d(TAG, "package not yet restored: $targetPkg")
+                        FileLog.d(
+                            TAG,
+                            "package not yet restored: $targetPkg, itemType=${c.itemType}" +
+                                ", isPreArchivedShortcut=$isPreArchivedShortcut" +
+                                ", restoreFlag=${c.restoreFlag}",
+                        )
                         tempPackageKey.update(targetPkg, c.user)
                         when {
                             c.hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORE_STARTED) -> {
                                 // Restore has started once.
                             }
-                            installingPkgs.containsKey(tempPackageKey) -> {
+                            installingPkgs.containsKey(tempPackageKey) || isPreArchivedShortcut -> {
                                 // App restore has started. Update the flag
                                 c.restoreFlag =
                                     c.restoreFlag or WorkspaceItemInfo.FLAG_RESTORE_STARTED
-                                FileLog.d(TAG, "restore started for installing app: $targetPkg")
+                                FileLog.d(
+                                    TAG,
+                                    "restore started for installing app: $targetPkg, itemType=${c.itemType}",
+                                )
                                 c.updater().put(Favorites.RESTORED, c.restoreFlag).commit()
                             }
                             else -> {
@@ -251,9 +268,18 @@
             }
         }
         if (c.restoreFlag and WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI != 0) {
+            FileLog.d(
+                TAG,
+                "restore flag set AND WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI != 0, setting valid target to false: $targetPkg, itemType=${c.itemType}, restoreFlag=${c.restoreFlag}",
+            )
             validTarget = false
         }
-        if (validTarget) {
+        if (validTarget && !isPreArchivedShortcut) {
+            FileLog.d(
+                TAG,
+                "valid target true, marking restored: $targetPkg," +
+                    " itemType=${c.itemType}, restoreFlag=${c.restoreFlag}",
+            )
             // The shortcut points to a valid target (either no target
             // or something which is ready to be used)
             c.markRestored()
@@ -263,14 +289,15 @@
         when {
             c.restoreFlag != 0 -> {
                 // Already verified above that user is same as default user
-                info = c.getRestoredItemInfo(intent)
+                info = c.getRestoredItemInfo(intent, isPreArchivedShortcut)
             }
             c.itemType == Favorites.ITEM_TYPE_APPLICATION ->
                 info = c.getAppShortcutInfo(intent, allowMissingTarget, useLowResIcon, false)
             c.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT -> {
                 val key = ShortcutKey.fromIntent(intent, c.user)
                 if (unlockedUsers[c.serialNumber]) {
-                    val pinnedShortcut = shortcutKeyToPinnedShortcuts[key]
+                    val pinnedShortcut =
+                        shortcutKeyToPinnedShortcuts[key] ?: retryDeepShortcutById(key)
                     if (pinnedShortcut == null) {
                         // The shortcut is no longer valid.
                         c.markDeleted(
@@ -283,7 +310,7 @@
                     // If the pinned deep shortcut is no longer published,
                     // use the last saved icon instead of the default.
                     val csi = CacheableShortcutInfo(pinnedShortcut, appInfoWrapper)
-                    iconCache.getShortcutIcon(info, csi, c::loadIcon)
+                    iconCache.getShortcutIcon(info, csi, c::loadIconFromDb)
                     if (appInfoWrapper.isSuspended()) {
                         info.runtimeStatusFlags =
                             info.runtimeStatusFlags or ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED
@@ -376,30 +403,39 @@
     }
 
     /**
+     * It is possible that the data was cleared from ShortcutManager after it was restored. In that
+     * instance, the Launcher would have a valid Shortcut id, but ShortcutManager wouldn't recognize
+     * it as valid. Here we retry by querying ShortcutManager by package name and shortcut id.
+     */
+    private fun retryDeepShortcutById(key: ShortcutKey): ShortcutInfo? {
+        FileLog.d(TAG, "retryDeepShortcutById: package=${key.packageName}, shortcutId=${key.id}")
+        return launcherApps
+            .getShortcuts(
+                ShortcutQuery().apply {
+                    setPackage(key.packageName)
+                    setShortcutIds(listOf(key.id))
+                    setQueryFlags(ShortcutRequest.ALL)
+                },
+                key.user,
+            )
+            ?.firstOrNull()
+    }
+
+    /**
      * Loads CollectionInfo information from the database and formats it. This function runs while
      * LoaderTask is still active; some of the processing for folder content items is done after all
      * the items in the workspace have been loaded. The loaded and formatted CollectionInfo is then
      * stored in the BgDataModel.
      */
     private fun processFolderOrAppPair() {
-        var collection = bgDataModel.findOrMakeFolder(c.id)
+        var collection = c.findOrMakeFolder(c.id, bgDataModel)
         // If we generated a placeholder Folder before this point, it may need to be replaced with
         // an app pair.
         if (c.itemType == Favorites.ITEM_TYPE_APP_PAIR && collection is FolderInfo) {
-            if (!FeatureFlags.enableAppPairs()) {
-                // If app pairs are not enabled, stop loading.
-                Log.e(TAG, "app pairs flag is off, did not load app pair")
-                return
-            }
-
-            val folderInfo: FolderInfo = collection
             val newAppPair = AppPairInfo()
             // Move the placeholder's contents over to the new app pair.
-            folderInfo.getContents().forEach(newAppPair::add)
+            collection.getContents().forEach(newAppPair::add)
             collection = newAppPair
-            // Remove the placeholder and add the app pair into the data model.
-            bgDataModel.collections.remove(c.id)
-            bgDataModel.collections.put(c.id, collection)
         }
 
         c.applyCommonProperties(collection)
@@ -521,7 +557,7 @@
                         appWidgetInfo.providerName,
                         appWidgetInfo.user,
                     )
-                iconCache.getTitleAndIconForApp(appWidgetInfo.pendingItemInfo, false)
+                iconCache.getTitleAndIconForApp(appWidgetInfo.pendingItemInfo, DEFAULT_LOOKUP_FLAG)
             }
             WidgetInflater.TYPE_REAL ->
                 WidgetSizes.updateWidgetSizeRangesAsync(
@@ -553,7 +589,7 @@
                 logWidgetInfo(app.invariantDeviceProfile, lapi)
             }
         }
-        c.checkAndAddItem(appWidgetInfo, bgDataModel)
+        c.checkAndAddItem(appWidgetInfo, bgDataModel, memoryLogger)
     }
 
     companion object {
diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index 97b62b4..fe8fb5f 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -215,8 +215,7 @@
                 PackageManagerHelper.getLoadingProgress(lai),
                 PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
         info.setNonResizeable(apiWrapper.isNonResizeableActivity(lai));
-        info.setSupportsMultiInstance(
-                pmHelper.supportsMultiInstance(lai.getComponentName()));
+        info.setSupportsMultiInstance(apiWrapper.supportsMultiInstance(lai));
         return (oldProgressLevel != info.getProgressLevel())
                 || (oldRuntimeStatusFlags != info.runtimeStatusFlags);
     }
diff --git a/src/com/android/launcher3/model/data/AppPairInfo.kt b/src/com/android/launcher3/model/data/AppPairInfo.kt
index b6de04a..c0fe4fd 100644
--- a/src/com/android/launcher3/model/data/AppPairInfo.kt
+++ b/src/com/android/launcher3/model/data/AppPairInfo.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import com.android.launcher3.LauncherSettings
+import com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG
 import com.android.launcher3.R
 import com.android.launcher3.icons.IconCache
 import com.android.launcher3.logger.LauncherAtom
@@ -33,9 +34,8 @@
     }
 
     /** Convenience constructor, calls primary constructor and init block */
-    constructor(app1: WorkspaceItemInfo, app2: WorkspaceItemInfo) : this() {
-        add(app1)
-        add(app2)
+    constructor(apps: List<WorkspaceItemInfo>) : this() {
+        apps.forEach(this::add)
     }
 
     /** Creates a new AppPairInfo that is a copy of the provided one. */
@@ -74,16 +74,17 @@
         val isTablet =
             (ActivityContext.lookupContext(context) as ActivityContext).getDeviceProfile().isTablet
         return Pair(
-            isTablet || !getFirstApp().isNonResizeable(),
-            isTablet || !getSecondApp().isNonResizeable(),
+            isTablet || !getFirstApp().isNonResizeable,
+            isTablet || !getSecondApp().isNonResizeable,
         )
     }
 
     /** Fetches high-res icons for member apps if needed. */
     fun fetchHiResIconsIfNeeded(iconCache: IconCache) {
-        getAppContents().stream().filter(ItemInfoWithIcon::usingLowResIcon).forEach { member ->
-            iconCache.getTitleAndIcon(member, false)
-        }
+        getAppContents()
+            .stream()
+            .filter { it.matchingLookupFlag.isVisuallyLessThan(DESKTOP_ICON_FLAG) }
+            .forEach { member -> iconCache.getTitleAndIcon(member, DESKTOP_ICON_FLAG) }
     }
 
     /**
diff --git a/src/com/android/launcher3/model/data/FolderInfo.java b/src/com/android/launcher3/model/data/FolderInfo.java
index f0f2892..9656ac1 100644
--- a/src/com/android/launcher3/model/data/FolderInfo.java
+++ b/src/com/android/launcher3/model/data/FolderInfo.java
@@ -164,7 +164,7 @@
     }
 
     /**
-     * Returns the folder's contents as an ArrayList of {@link ItemInfo}. Includes
+     * Returns the folder's contents as an unsorted ArrayList of {@link ItemInfo}. Includes
      * {@link WorkspaceItemInfo} and {@link AppPairInfo}s.
      */
     @NonNull
diff --git a/src/com/android/launcher3/model/data/IconRequestInfo.java b/src/com/android/launcher3/model/data/IconRequestInfo.java
index e77e527..42af018 100644
--- a/src/com/android/launcher3/model/data/IconRequestInfo.java
+++ b/src/com/android/launcher3/model/data/IconRequestInfo.java
@@ -64,23 +64,25 @@
     }
 
     /**
-     * Loads this request's item info's title. This method should only be used on IconRequestInfos
-     * for WorkspaceItemInfos.
+     * Loads this request's item info's title and icon from given iconBlob from Launcher.db.
+     * This method should only be used on {@link IconRequestInfo} for {@link WorkspaceItemInfo}
+     *  or {@link AppInfo}.
      */
-    public boolean loadWorkspaceIcon(Context context) {
-        if (!(itemInfo instanceof WorkspaceItemInfo)) {
+    public boolean loadIconFromDbBlob(Context context) {
+        if (!(itemInfo instanceof WorkspaceItemInfo) && !(itemInfo instanceof AppInfo)) {
             throw new IllegalStateException(
-                    "loadWorkspaceIcon should only be use for a WorkspaceItemInfos: " + itemInfo);
+                    "loadIconFromDb should only be used for either WorkspaceItemInfo or AppInfo: "
+                            + itemInfo);
         }
 
         try (LauncherIcons li = LauncherIcons.obtain(context)) {
-            WorkspaceItemInfo info = (WorkspaceItemInfo) itemInfo;
-            // Failed to load from resource, try loading from DB.
+            ItemInfoWithIcon info = itemInfo;
             if (iconBlob == null) {
+                Log.d(TAG, "loadIconFromDb: icon blob null, returning. Component="
+                        + info.getTargetComponent());
                 return false;
             }
-            info.bitmap = li.createIconBitmap(decodeByteArray(
-                    iconBlob, 0, iconBlob.length));
+            info.bitmap = li.createIconBitmap(decodeByteArray(iconBlob, 0, iconBlob.length));
             return true;
         } catch (Exception e) {
             Log.e(TAG, "Failed to decode byte array for info " + itemInfo, e);
diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java
index c22a8a5..588e759 100644
--- a/src/com/android/launcher3/model/data/ItemInfo.java
+++ b/src/com/android/launcher3/model/data/ItemInfo.java
@@ -439,8 +439,7 @@
         LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
         itemBuilder.setIsKidsMode(
                 SettingsCache.INSTANCE.get(context).getValue(NAV_BAR_KIDS_MODE, 0));
-        UserCache.INSTANCE.executeIfCreated(cache ->
-                itemBuilder.setUserType(getUserType(cache.getUserInfo(user))));
+        itemBuilder.setUserType(getUserType(UserCache.INSTANCE.get(context).getUserInfo(user)));
         itemBuilder.setRank(rank);
         itemBuilder.addAllItemAttributes(mAttributeList);
         return itemBuilder;
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 6ac44ff..f5e5e16 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -16,17 +16,22 @@
 
 package com.android.launcher3.model.data;
 
+import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
+
 import android.content.Context;
 import android.content.Intent;
 import android.os.Process;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.Flags;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.ThemeManager;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.BitmapInfo.DrawableCreationFlags;
 import com.android.launcher3.icons.FastBitmapDrawable;
+import com.android.launcher3.icons.cache.CacheLookupFlag;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.util.ApiWrapper;
@@ -41,6 +46,7 @@
     /**
      * The bitmap for the application icon
      */
+    @NonNull
     public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO;
 
     /**
@@ -185,10 +191,10 @@
     }
 
     /**
-     * Indicates whether we're using a low res icon
+     * Returns the lookup flag to match this current state of this info
      */
-    public boolean usingLowResIcon() {
-        return bitmap.isLowRes();
+    public CacheLookupFlag getMatchingLookupFlag() {
+        return bitmap.getMatchingLookupFlag();
     }
 
     /**
@@ -320,7 +326,12 @@
      * Returns a FastBitmapDrawable with the icon and context theme applied
      */
     public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags) {
-        FastBitmapDrawable drawable = bitmap.newIcon(context, creationFlags);
+        ThemeManager themeManager = ThemeManager.INSTANCE.get(context);
+        if (!themeManager.isIconThemeEnabled()) {
+            creationFlags &= ~FLAG_THEMED;
+        }
+        FastBitmapDrawable drawable = bitmap.newIcon(
+                context, creationFlags, Utilities.getIconShapeOrNull(context));
         drawable.setIsDisabled(isDisabled());
         return drawable;
     }
diff --git a/src/com/android/launcher3/model/data/TaskItemInfo.kt b/src/com/android/launcher3/model/data/TaskItemInfo.kt
index fc1cd4d..8b72835 100644
--- a/src/com/android/launcher3/model/data/TaskItemInfo.kt
+++ b/src/com/android/launcher3/model/data/TaskItemInfo.kt
@@ -17,8 +17,7 @@
 package com.android.launcher3.model.data
 
 /**
- * Temporary class holding a Task ID to allow us to reference a Task when clicking a hotseat item.
- *
- * TODO(b/315344726): Remove this class when we have proper Taskbar support for multi-instance apps
+ * A Task info class holding a Task ID to allow us to reference a Task when clicking a hotseat item.
+ * This is also used to help identify the shortcuts shown in the long-press menu.
  */
 class TaskItemInfo(val taskId: Int, itemInfo: WorkspaceItemInfo) : WorkspaceItemInfo(itemInfo)
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index 9af61f0..9a7c347 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -36,6 +36,7 @@
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.util.ApiWrapper;
 import com.android.launcher3.util.ContentWriter;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
 
 import java.util.Arrays;
 
@@ -142,7 +143,7 @@
                 .put(Favorites.OPTIONS, options)
                 .put(Favorites.RESTORED, status);
 
-        if (!usingLowResIcon()) {
+        if (!getMatchingLookupFlag().useLowRes()) {
             writer.putIcon(bitmap, user);
         }
     }
@@ -178,7 +179,7 @@
 
     public void updateFromDeepShortcutInfo(@NonNull final ShortcutInfo shortcutInfo,
             @NonNull final Context context) {
-        if (com.android.wm.shell.Flags.enableBubbleAnything()) {
+        if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
             mShortcutInfo = shortcutInfo;
         }
         // {@link ShortcutInfo#getActivity} can change during an update. Recreate the intent
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index 836ea4a..864fed0 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -170,6 +170,9 @@
                     for (NotificationsChangedListener listener : sNotificationsChangedListeners) {
                         listener.onNotificationPosted(msg.first, msg.second);
                     }
+                    Log.i(TAG, "received notification posted event - " + msg.first);
+                } else {
+                    Log.i(TAG, "received notification posted event, but there are no listeners");
                 }
                 break;
             case MSG_NOTIFICATION_REMOVED:
@@ -178,6 +181,9 @@
                     for (NotificationsChangedListener listener : sNotificationsChangedListeners) {
                         listener.onNotificationRemoved(msg.first, msg.second);
                     }
+                    Log.i(TAG, "received notification removed event - " + msg.first);
+                } else {
+                    Log.i(TAG, "received notification removed event, but there are no listeners");
                 }
                 break;
             case MSG_NOTIFICATION_FULL_REFRESH:
@@ -186,6 +192,11 @@
                         listener.onNotificationFullRefresh(
                                 (List<StatusBarNotification>) message.obj);
                     }
+                    ((List<StatusBarNotification>) message.obj).forEach(sbn -> Log.i(TAG,
+                            "Handling notification state refresh for " + sbn.getPackageName() + "#"
+                                    + sbn.getUserId()));
+                } else {
+                    Log.i(TAG, "received notification refresh event, but there are no listeners");
                 }
                 break;
         }
@@ -205,6 +216,7 @@
     @Override
     public void onListenerConnected() {
         super.onListenerConnected();
+        Log.i(TAG, "onListenerConnected");
         sIsConnected = true;
 
         // Register an observer to rebind the notification listener when dots are re-enabled.
@@ -230,6 +242,7 @@
     @Override
     public void onListenerDisconnected() {
         super.onListenerDisconnected();
+        Log.i(TAG, "onListenerDisconnected");
         sIsConnected = false;
         mSettingsCache.unregister(NOTIFICATION_BADGING_URI, mNotificationSettingsChangedListener);
         onNotificationFullRefresh();
diff --git a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
index a691e45..37f5189 100644
--- a/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
+++ b/src/com/android/launcher3/pageindicators/PageIndicatorDots.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.pageindicators;
 
+import static com.android.launcher3.Flags.enableLauncherVisualRefresh;
 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
 
 import android.animation.Animator;
@@ -57,8 +58,8 @@
 public class PageIndicatorDots extends View implements Insettable, PageIndicator {
 
     private static final float SHIFT_PER_ANIMATION = 0.5f;
-    private static final float SHIFT_THRESHOLD = 0.1f;
-    private static final long ANIMATION_DURATION = 150;
+    private static final float SHIFT_THRESHOLD = (enableLauncherVisualRefresh() ? 0.5f : 0.2f);
+    private static final long ANIMATION_DURATION = (enableLauncherVisualRefresh() ? 200 : 150);
     private static final int PAGINATION_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay();
     private static final int PAGINATION_FADE_IN_DURATION = 83;
     private static final int PAGINATION_FADE_OUT_DURATION = 167;
@@ -78,6 +79,7 @@
     // This value approximately overshoots to 1.5 times the original size.
     private static final float ENTER_ANIMATION_OVERSHOOT_TENSION = 4.9f;
 
+    // This is used to optimize the onDraw method by not constructing a new RectF each draw.
     private static final RectF sTempRect = new RectF();
 
     private static final FloatProperty<PageIndicatorDots> CURRENT_POSITION =
@@ -93,7 +95,7 @@
                     obj.invalidate();
                     obj.invalidateOutline();
                 }
-    };
+            };
 
     private static final IntProperty<PageIndicatorDots> PAGINATION_ALPHA =
             new IntProperty<PageIndicatorDots>("pagination_alpha") {
@@ -111,6 +113,7 @@
 
     private final Handler mDelayedPaginationFadeHandler = new Handler(Looper.getMainLooper());
     private final float mDotRadius;
+    private final float mGapWidth;
     private final float mCircleGap;
     private final boolean mIsRtl;
 
@@ -130,6 +133,7 @@
      * 1.0  => Active dot is at position 1
      */
     private float mCurrentPosition;
+    private int mLastPosition;
     private float mFinalPosition;
     private boolean mIsScrollPaused;
     @VisibleForTesting
@@ -157,7 +161,10 @@
         mPaginationPaint.setStyle(Style.FILL);
         mPaginationPaint.setColor(Themes.getAttrColor(context, R.attr.pageIndicatorDotColor));
         mDotRadius = getResources().getDimension(R.dimen.page_indicator_dot_size) / 2;
-        mCircleGap = DOT_GAP_FACTOR * mDotRadius;
+        mGapWidth = getResources().getDimension(R.dimen.page_indicator_gap_width);
+        mCircleGap = (enableLauncherVisualRefresh())
+                ? mDotRadius * 2 + mGapWidth
+                : DOT_GAP_FACTOR * mDotRadius;
         setOutlineProvider(new MyOutlineProver());
         mIsRtl = Utilities.isRtl(getResources());
     }
@@ -188,29 +195,40 @@
 
         mTotalScroll = totalScroll;
 
-        int scrollPerPage = totalScroll / (mNumPages - 1);
-        int pageToLeft = scrollPerPage == 0 ? 0 : currentScroll / scrollPerPage;
-        int pageToLeftScroll = pageToLeft * scrollPerPage;
-        int pageToRightScroll = pageToLeftScroll + scrollPerPage;
+        if (enableLauncherVisualRefresh()) {
+            float scrollPerPage = (float) totalScroll / (mNumPages - 1);
+            float position = currentScroll / scrollPerPage;
+            animateToPosition(Math.round(position));
 
-        float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
-        if (currentScroll < pageToLeftScroll + scrollThreshold) {
-            // scroll is within the left page's threshold
-            animateToPosition(pageToLeft);
-            if (mShouldAutoHide) {
-                hideAfterDelay();
-            }
-        } else if (currentScroll > pageToRightScroll - scrollThreshold) {
-            // scroll is far enough from left page to go to the right page
-            animateToPosition(pageToLeft + 1);
-            if (mShouldAutoHide) {
+            float delta = Math.abs((int) position - position);
+            if (mShouldAutoHide && (delta < 0.1 || delta > 0.9)) {
                 hideAfterDelay();
             }
         } else {
-            // scroll is between left and right page
-            animateToPosition(pageToLeft + SHIFT_PER_ANIMATION);
-            if (mShouldAutoHide) {
-                mDelayedPaginationFadeHandler.removeCallbacksAndMessages(null);
+            int scrollPerPage = totalScroll / (mNumPages - 1);
+            int pageToLeft = scrollPerPage == 0 ? 0 : currentScroll / scrollPerPage;
+            int pageToLeftScroll = pageToLeft * scrollPerPage;
+            int pageToRightScroll = pageToLeftScroll + scrollPerPage;
+
+            float scrollThreshold = SHIFT_THRESHOLD * scrollPerPage;
+            if (currentScroll < pageToLeftScroll + scrollThreshold) {
+                // scroll is within the left page's threshold
+                animateToPosition(pageToLeft);
+                if (mShouldAutoHide) {
+                    hideAfterDelay();
+                }
+            } else if (currentScroll > pageToRightScroll - scrollThreshold) {
+                // scroll is far enough from left page to go to the right page
+                animateToPosition(pageToLeft + 1);
+                if (mShouldAutoHide) {
+                    hideAfterDelay();
+                }
+            } else {
+                // scroll is between left and right page
+                animateToPosition(pageToLeft + SHIFT_PER_ANIMATION);
+                if (mShouldAutoHide) {
+                    mDelayedPaginationFadeHandler.removeCallbacksAndMessages(null);
+                }
             }
         }
     }
@@ -283,15 +301,23 @@
 
     private void animateToPosition(float position) {
         mFinalPosition = position;
-        if (Math.abs(mCurrentPosition - mFinalPosition) < SHIFT_THRESHOLD) {
+        if (!enableLauncherVisualRefresh()
+                && Math.abs(mCurrentPosition - mFinalPosition) < SHIFT_THRESHOLD) {
             mCurrentPosition = mFinalPosition;
         }
-        if (mAnimator == null && Float.compare(mCurrentPosition, mFinalPosition) != 0) {
-            float positionForThisAnim = mCurrentPosition > mFinalPosition ?
-                    mCurrentPosition - SHIFT_PER_ANIMATION : mCurrentPosition + SHIFT_PER_ANIMATION;
+        if (mAnimator == null && Float.compare(mCurrentPosition, position) != 0) {
+            float positionForThisAnim = enableLauncherVisualRefresh()
+                    ? position
+                    : (mCurrentPosition > mFinalPosition
+                            ? mCurrentPosition - SHIFT_PER_ANIMATION
+                            : mCurrentPosition + SHIFT_PER_ANIMATION);
             mAnimator = ObjectAnimator.ofFloat(this, CURRENT_POSITION, positionForThisAnim);
             mAnimator.addListener(new AnimationCycleListener());
             mAnimator.setDuration(ANIMATION_DURATION);
+            if (enableLauncherVisualRefresh()) {
+                mLastPosition = (int) mCurrentPosition;
+                mAnimator.setInterpolator(new OvershootInterpolator());
+            }
             mAnimator.start();
         }
     }
@@ -314,6 +340,7 @@
         invalidate();
     }
 
+    // TODO(b/394355070): Verify Folder Entry Animation works correctly with visual updates
     public void playEntryAnimation() {
         int count = mEntryAnimationRadiusFactors.length;
         if (count == 0) {
@@ -391,6 +418,7 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        // TODO(b/394355070): Verify Folder Entry Animation works correctly with visual updates
         // Add extra spacing of mDotRadius on all sides so than entry animation could be run.
         int width = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY ?
                 MeasureSpec.getSize(widthMeasureSpec) : (int) ((mNumPages * 3 + 2) * mDotRadius);
@@ -410,17 +438,14 @@
             return;
         }
 
-        // Draw all page indicators;
         float circleGap = mCircleGap;
-        float startX = ((float) getWidth() / 2)
-                - (mCircleGap * (((float) mNumPages - 1) / 2))
-                - mDotRadius;
-
-        float x = startX + mDotRadius;
+        float x = ((float) getWidth() / 2) - (mCircleGap * ((float) mNumPages - 1) / 2);
         float y = getHeight() / 2;
 
         if (mEntryAnimationRadiusFactors != null) {
             // During entry animation, only draw the circles
+            // TODO(b/394355070): Verify Folder Entry Animation works correctly - visual updates
+
             if (mIsRtl) {
                 x = getWidth() - x;
                 circleGap = -circleGap;
@@ -432,18 +457,84 @@
                 x += circleGap;
             }
         } else {
+            // Save the current alpha value, so we can reset to it again after drawing the dots
             int alpha = mPaginationPaint.getAlpha();
 
-            // Here we draw the dots
-            mPaginationPaint.setAlpha((int) (alpha * DOT_ALPHA_FRACTION));
-            for (int i = 0; i < mNumPages; i++) {
-                canvas.drawCircle(x, y, mDotRadius, mPaginationPaint);
-                x += circleGap;
+            if (enableLauncherVisualRefresh()) {
+                int nonActiveAlpha = (int) (alpha * DOT_ALPHA_FRACTION);
+
+                float diameter = 2 * mDotRadius;
+                sTempRect.top = y - mDotRadius;
+                sTempRect.bottom = y + mDotRadius;
+                sTempRect.left = x - diameter;
+
+                float posDif = Math.abs(mLastPosition - mCurrentPosition);
+                float boundedPosition = (posDif > 1)
+                        ? Math.round(mCurrentPosition)
+                        : mCurrentPosition;
+                float bounceProgress = (posDif > 1) ? posDif - 1 : 0;
+                float bounceAdjustment = Math.abs(mCurrentPosition - boundedPosition) * diameter;
+
+                // Here we draw the dots, one at a time from the left-most dot to the right-most dot
+                // 1.0 => 000000 000000111111 000000
+                // 1.3 => 000000 0000001111 11000000
+                // 1.6 => 000000 00000011 1111000000
+                // 2.0 => 000000 000000 111111000000
+                for (int i = 0; i < mNumPages; i++) {
+                    mPaginationPaint.setAlpha(nonActiveAlpha);
+                    float delta = Math.abs(boundedPosition - i);
+                    if (delta <= SHIFT_THRESHOLD) {
+                        mPaginationPaint.setAlpha(alpha);
+                    }
+
+                    // If boundedPosition is 3.3, both 3 and 4 should enter this condition.
+                    // If boundedPosition is 3, only 3 should enter this condition.
+                    if (delta < 1) {
+                        sTempRect.right = sTempRect.left + diameter + ((1 - delta) * diameter);
+
+                        // While the animation is shifting the active pagination dots size from
+                        // the previously active one, to the newly active dot, there is no bounce
+                        // adjustment. The bounce happens in the "Overshoot" phase of the animation.
+                        // mLastPosition is used to determine when the currentPosition is just
+                        // leaving the page, or if it is in the overshoot phase.
+                        if (boundedPosition == i && bounceProgress != 0) {
+                            if (mLastPosition < mCurrentPosition) {
+                                sTempRect.left -= bounceAdjustment;
+                            } else {
+                                sTempRect.right += bounceAdjustment;
+                            }
+                        }
+                    } else {
+                        sTempRect.right = sTempRect.left + diameter;
+
+                        if (mLastPosition == i && bounceProgress != 0) {
+                            if (mLastPosition > mCurrentPosition) {
+                                sTempRect.left += bounceAdjustment;
+                            } else {
+                                sTempRect.right -= bounceAdjustment;
+                            }
+                        }
+                    }
+                    canvas.drawRoundRect(sTempRect, mDotRadius, mDotRadius, mPaginationPaint);
+
+                    // TODO(b/394355070) Verify RTL experience works correctly with visual updates
+                    sTempRect.left = sTempRect.right + mGapWidth;
+                }
+            } else {
+                // Here we draw the dots
+                mPaginationPaint.setAlpha((int) (alpha * DOT_ALPHA_FRACTION));
+                for (int i = 0; i < mNumPages; i++) {
+                    canvas.drawCircle(x, y, mDotRadius, mPaginationPaint);
+                    x += circleGap;
+                }
+
+                // Here we draw the current page indicator
+                mPaginationPaint.setAlpha(alpha);
+                canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint);
             }
 
-            // Here we draw the current page indicator
+            // Reset the alpha so it doesn't become progressively more transparent each onDraw call
             mPaginationPaint.setAlpha(alpha);
-            canvas.drawRoundRect(getActiveRect(), mDotRadius, mDotRadius, mPaginationPaint);
         }
     }
 
@@ -499,6 +590,7 @@
         @Override
         public void getOutline(View view, Outline outline) {
             if (mEntryAnimationRadiusFactors == null) {
+                // TODO(b/394355070): Verify Outline works correctly with visual updates
                 RectF activeRect = getActiveRect();
                 outline.setRoundRect(
                         (int) activeRect.left,
diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
index b9c928c..7451ce2 100644
--- a/src/com/android/launcher3/pm/InstallSessionTracker.java
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -34,13 +34,15 @@
 
 import com.android.launcher3.Flags;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.SafeCloseable;
 
 import java.lang.ref.WeakReference;
 import java.util.Objects;
 
 @SuppressWarnings("NewApi")
 @WorkerThread
-public class InstallSessionTracker extends PackageInstaller.SessionCallback {
+public class InstallSessionTracker extends PackageInstaller.SessionCallback implements
+        SafeCloseable {
 
     public static final String TAG = "InstallSessionTracker";
 
@@ -196,7 +198,8 @@
         }
     }
 
-    public void unregister() {
+    @Override
+    public void close() {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
             mInstaller.unregisterSessionCallback(this);
         } else {
diff --git a/src/com/android/launcher3/pm/UserCache.java b/src/com/android/launcher3/pm/UserCache.java
index e861961..98a3882 100644
--- a/src/com/android/launcher3/pm/UserCache.java
+++ b/src/com/android/launcher3/pm/UserCache.java
@@ -32,11 +32,15 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.annotation.WorkerThread;
 
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.UserBadgeDrawable;
 import com.android.launcher3.util.ApiWrapper;
+import com.android.launcher3.util.DaggerSingletonObject;
+import com.android.launcher3.util.DaggerSingletonTracker;
 import com.android.launcher3.util.FlagOp;
-import com.android.launcher3.util.MainThreadInitializedObject;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.launcher3.util.UserIconInfo;
@@ -47,10 +51,16 @@
 import java.util.Map;
 import java.util.function.BiConsumer;
 
+import javax.inject.Inject;
+
 /**
  * Class which manages a local cache of user handles to avoid system rpc
  */
-public class UserCache implements SafeCloseable {
+@LauncherAppSingleton
+public class UserCache {
+
+    public static DaggerSingletonObject<UserCache> INSTANCE =
+            new DaggerSingletonObject<>(LauncherBaseAppComponent::getUserCache);
 
     public static final String ACTION_PROFILE_ADDED = ATLEAST_U
             ? Intent.ACTION_PROFILE_ADDED : Intent.ACTION_MANAGED_PROFILE_ADDED;
@@ -65,19 +75,14 @@
     public static final String ACTION_PROFILE_UNAVAILABLE =
             "android.intent.action.PROFILE_UNAVAILABLE";
 
-    public static final MainThreadInitializedObject<UserCache> INSTANCE =
-            new MainThreadInitializedObject<>(UserCache::new);
-
     /** Returns an instance of UserCache bound to the context provided. */
     public static UserCache getInstance(Context context) {
         return INSTANCE.get(context);
     }
 
     private final List<BiConsumer<UserHandle, String>> mUserEventListeners = new ArrayList<>();
-    private final SimpleBroadcastReceiver mUserChangeReceiver =
-            new SimpleBroadcastReceiver(MODEL_EXECUTOR, this::onUsersChanged);
-
-    private final Context mContext;
+    private final SimpleBroadcastReceiver mUserChangeReceiver;
+    private final ApiWrapper mApiWrapper;
 
     @NonNull
     private Map<UserHandle, UserIconInfo> mUserToSerialMap;
@@ -85,20 +90,23 @@
     @NonNull
     private Map<UserHandle, List<String>> mUserToPreInstallAppMap;
 
-    private UserCache(Context context) {
-        mContext = context;
+    @Inject
+    public UserCache(
+            @ApplicationContext Context context,
+            DaggerSingletonTracker tracker,
+            ApiWrapper apiWrapper
+    ) {
+        mApiWrapper = apiWrapper;
+        mUserChangeReceiver = new SimpleBroadcastReceiver(context,
+                MODEL_EXECUTOR, this::onUsersChanged);
         mUserToSerialMap = Collections.emptyMap();
         MODEL_EXECUTOR.execute(this::initAsync);
-    }
-
-    @Override
-    public void close() {
-        MODEL_EXECUTOR.execute(() -> mUserChangeReceiver.unregisterReceiverSafely(mContext));
+        tracker.addCloseable(() -> mUserChangeReceiver.unregisterReceiverSafely());
     }
 
     @WorkerThread
     private void initAsync() {
-        mUserChangeReceiver.register(mContext,
+        mUserChangeReceiver.register(
                 Intent.ACTION_MANAGED_PROFILE_AVAILABLE,
                 Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE,
                 Intent.ACTION_MANAGED_PROFILE_REMOVED,
@@ -124,7 +132,7 @@
 
     @WorkerThread
     private void updateCache() {
-        mUserToSerialMap = ApiWrapper.INSTANCE.get(mContext).queryAllUsers();
+        mUserToSerialMap = mApiWrapper.queryAllUsers();
         mUserToPreInstallAppMap = fetchPreInstallApps();
     }
 
@@ -134,7 +142,7 @@
         mUserToSerialMap.forEach((userHandle, userIconInfo) -> {
             // Fetch only for private profile, as other profiles have no usages yet.
             List<String> preInstallApp = userIconInfo.isPrivate()
-                    ? ApiWrapper.INSTANCE.get(mContext).getPreInstalledSystemPackages(userHandle)
+                    ? mApiWrapper.getPreInstalledSystemPackages(userHandle)
                     : new ArrayList<>();
             userToPreInstallApp.put(userHandle, preInstallApp);
         });
@@ -211,6 +219,6 @@
     public static UserBadgeDrawable getBadgeDrawable(Context context, UserHandle userHandle) {
         return (UserBadgeDrawable) BitmapInfo.LOW_RES_INFO.withFlags(UserCache.getInstance(context)
                         .getUserInfo(userHandle).applyBitmapInfoFlags(FlagOp.NO_OP))
-                .getBadgeDrawable(context, false /* isThemed */);
+                .getBadgeDrawable(context, false /* isThemed */, null);
     }
 }
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index 1c9db17..aad1400 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -40,7 +40,6 @@
 import androidx.annotation.LayoutRes;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
@@ -235,6 +234,20 @@
      */
     public void populateAndShowRows(final BubbleTextView originalIcon,
             int deepShortcutCount, List<SystemShortcut> systemShortcuts) {
+        populateAndShowRows(originalIcon, (ItemInfo) originalIcon.getTag(), deepShortcutCount,
+                systemShortcuts);
+    }
+
+    /**
+     * Populate and show shortcuts for the Launcher U app shortcut design.
+     * Will inflate the container and shortcut View instances for the popup container.
+     * @param originalIcon App icon that the popup is shown for
+     * @param itemInfo The info that is used to load app shortcuts
+     * @param deepShortcutCount Number of DeepShortcutView instances to add to container
+     * @param systemShortcuts List of SystemShortcuts to add to container
+     */
+    public void populateAndShowRows(final BubbleTextView originalIcon, ItemInfo itemInfo,
+            int deepShortcutCount, List<SystemShortcut> systemShortcuts) {
 
         mOriginalIcon = originalIcon;
         mContainerWidth = getResources().getDimensionPixelSize(R.dimen.bg_popup_item_width);
@@ -247,7 +260,7 @@
                     R.layout.system_shortcut);
         }
         show();
-        loadAppShortcuts((ItemInfo) originalIcon.getTag());
+        loadAppShortcuts(itemInfo);
     }
 
     /**
@@ -579,7 +592,7 @@
     /**
      * Dismisses the popup if it is no longer valid
      */
-    public static void dismissInvalidPopup(BaseDraggingActivity activity) {
+    public static <T extends Context & ActivityContext> void dismissInvalidPopup(T activity) {
         PopupContainerWithArrow popup = getOpen(activity);
         if (popup != null && (!popup.mOriginalIcon.isAttachedToWindow()
                 || !ShortcutUtil.supportsShortcuts((ItemInfo) popup.mOriginalIcon.getTag()))) {
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index 8a5e388..5c1a755 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -23,20 +23,27 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.allapps.ActivityAllAppsContainerView;
 import com.android.launcher3.dot.DotInfo;
+import com.android.launcher3.dot.FolderDotInfo;
+import com.android.launcher3.folder.Folder;
+import com.android.launcher3.folder.FolderIcon;
+import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.notification.NotificationKeyData;
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.ShortcutUtil;
+import com.android.launcher3.views.ActivityContext;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
@@ -47,19 +54,49 @@
     private static final boolean LOGD = false;
     private static final String TAG = "PopupDataProvider";
 
-    private final Consumer<Predicate<PackageUserKey>> mNotificationDotsChangeListener;
+    private final ActivityContext mContext;
+
+    /** Maps packages to their DotInfo's . */
+    private final Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>();
 
     /** Maps launcher activity components to a count of how many shortcuts they have. */
     private HashMap<ComponentKey, Integer> mDeepShortcutMap = new HashMap<>();
-    /** Maps packages to their DotInfo's . */
-    private Map<PackageUserKey, DotInfo> mPackageUserToDotInfos = new HashMap<>();
 
-    public PopupDataProvider(Consumer<Predicate<PackageUserKey>> notificationDotsChangeListener) {
-        mNotificationDotsChangeListener = notificationDotsChangeListener;
+    public PopupDataProvider(ActivityContext context) {
+        mContext = context;
     }
 
     private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
-        mNotificationDotsChangeListener.accept(updatedDots);
+        final PackageUserKey packageUserKey = new PackageUserKey(null, null);
+        Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
+                || updatedDots.test(packageUserKey);
+
+        ItemOperator op = (info, v) -> {
+            if (v instanceof BubbleTextView && info != null && matcher.test(info)) {
+                ((BubbleTextView) v).applyDotState(info, true /* animate */);
+            } else if (v instanceof FolderIcon icon
+                    && info instanceof FolderInfo fi && fi.anyMatch(matcher)) {
+                FolderDotInfo folderDotInfo = new FolderDotInfo();
+                for (ItemInfo si : fi.getContents()) {
+                    folderDotInfo.addDotInfo(getDotInfoForItem(si));
+                }
+                icon.setDotInfo(folderDotInfo);
+            }
+
+            // process all the shortcuts
+            return false;
+        };
+
+        mContext.getContent().mapOverItems(op);
+        Folder folder = Folder.getOpen(mContext);
+        if (folder != null) {
+            folder.mapOverItems(op);
+        }
+
+        ActivityAllAppsContainerView<?> appsView = mContext.getAppsView();
+        if (appsView != null) {
+            appsView.getAppsStore().updateNotificationDots(updatedDots);
+        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index b748011..bc5064d 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -110,13 +110,16 @@
     public static <T extends Context & ActivityContext> Runnable createUpdateRunnable(
             final T context,
             final ItemInfo originalInfo,
-            final Handler uiHandler, final PopupContainerWithArrow container,
-            final List<DeepShortcutView> shortcutViews) {
+            final Handler uiHandler,
+            final PopupContainerWithArrow container,
+            final List<DeepShortcutView> shortcutViews
+    ) {
         final ComponentName activity = originalInfo.getTargetComponent();
         final UserHandle user = originalInfo.user;
+        final String targetPackage = originalInfo.getTargetPackage();
         return () -> {
             ApplicationInfoWrapper infoWrapper =
-                    new ApplicationInfoWrapper(context, originalInfo.getTargetPackage(), user);
+                    new ApplicationInfoWrapper(context, targetPackage, user);
             List<ShortcutInfo> shortcuts = new ShortcutRequest(context, user)
                     .withContainer(activity)
                     .query(ShortcutRequest.PUBLISHED);
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 63c9d94..b7efdec 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -407,7 +407,7 @@
                         && !(itemInfo instanceof WorkspaceItemInfo)) {
                     return null;
                 }
-                return new BubbleShortcut(activity, itemInfo, originalView);
+                return new BubbleShortcut<>(activity, itemInfo, originalView);
             };
 
     public interface BubbleActivityStarter {
@@ -415,7 +415,7 @@
         void showShortcutBubble(ShortcutInfo info);
 
         /** Tell SysUI to show the provided intent in a bubble. */
-        void showAppBubble(Intent intent);
+        void showAppBubble(Intent intent, UserHandle user);
     }
 
     public static class BubbleShortcut<T extends ActivityContext> extends SystemShortcut<T> {
@@ -452,7 +452,7 @@
                 if (intent.getPackage() == null) {
                     intent.setPackage(mItemInfo.getTargetPackage());
                 }
-                mStarter.showAppBubble(intent);
+                mStarter.showAppBubble(intent, mItemInfo.user);
             } else {
                 Log.w(TAG, "unable to bubble, no intent: " + mItemInfo);
             }
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.kt b/src/com/android/launcher3/provider/LauncherDbUtils.kt
index 6f1d0dd..c92328d 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.kt
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.kt
@@ -28,6 +28,7 @@
 import android.text.TextUtils
 import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherSettings
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
 import com.android.launcher3.Utilities
 import com.android.launcher3.icons.IconCache
 import com.android.launcher3.model.LoaderCursor
@@ -132,8 +133,10 @@
     }
 
     @JvmStatic
-    fun shiftTableByXCells(db: SQLiteDatabase, x: Int, toTable: String) {
-        db.run { execSQL("UPDATE $toTable SET cellY = cellY + $x") }
+    fun shiftWorkspaceByXCells(db: SQLiteDatabase, x: Int, toTable: String) {
+        db.run {
+            execSQL("UPDATE $toTable SET cellY = cellY + $x WHERE container = $CONTAINER_DESKTOP")
+        }
     }
 
     /**
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index dc42920..23941bb 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -51,6 +51,7 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.annotation.WorkerThread;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherFiles;
@@ -130,9 +131,11 @@
         removeOldDBs(context, oldPhoneFileName);
         // The idp before this contains data about the old phone, after this it becomes the idp
         // of the current phone.
-        FileLog.d(TAG, "Resetting IDP to default for restore dest device");
-        idp.reset(context);
-        trySettingPreviousGridAsCurrent(context, idp, oldPhoneFileName, previousDbs);
+        if (!Flags.oneGridSpecs()) {
+            FileLog.d(TAG, "Resetting IDP to default for restore dest device");
+            idp.reset(context);
+            trySettingPreviousGridAsCurrent(context, idp, oldPhoneFileName, previousDbs);
+        }
     }
 
 
@@ -415,7 +418,11 @@
     }
 
     public static boolean isPending(Context context) {
-        return LauncherPrefs.get(context).has(RESTORE_DEVICE);
+        return isPending(LauncherPrefs.get(context));
+    }
+
+    public static boolean isPending(LauncherPrefs prefs) {
+        return prefs.has(RESTORE_DEVICE);
     }
 
     /**
@@ -531,7 +538,7 @@
         }
 
         logFavoritesTable(controller.getDb(), "launcher db after remap widget ids", null, null);
-        LauncherAppState.INSTANCE.executeIfCreated(app -> app.getModel().forceReload());
+        LauncherAppState.INSTANCE.get(context).getModel().reloadIfActive();
     }
 
     private static void logDatabaseWidgetInfo(ModelDbController controller) {
diff --git a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
index e4c50f0..2d1a5f5 100644
--- a/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
+++ b/src/com/android/launcher3/recyclerview/AllAppsRecyclerViewPool.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.util.Log
+import android.view.ContextThemeWrapper
 import android.view.InflateException
 import androidx.annotation.VisibleForTesting
 import androidx.annotation.VisibleForTesting.Companion.PROTECTED
@@ -33,8 +34,6 @@
 import com.android.launcher3.util.Executors.VIEW_PREINFLATION_EXECUTOR
 import com.android.launcher3.util.Themes
 import com.android.launcher3.views.ActivityContext
-import com.android.launcher3.views.ActivityContext.ActivityContextDelegate
-import java.lang.IllegalStateException
 
 const val PREINFLATE_ICONS_ROW_COUNT = 4
 const val EXTRA_ICONS_COUNT = 2
@@ -80,11 +79,9 @@
         // create a separate AssetManager obj internally to avoid lock contention with
         // AssetManager obj that is associated with the launcher context on the main thread.
         val allAppsPreInflationContext =
-            ActivityContextDelegate(
-                context.createConfigurationContext(context.resources.configuration),
-                Themes.getActivityThemeRes(context),
-                context,
-            )
+            ContextThemeWrapper(context, Themes.getActivityThemeRes(context)).apply {
+                applyOverrideConfiguration(context.resources.configuration)
+            }
 
         // Because we perform onCreateViewHolder() on worker thread, we need a separate
         // adapter/inflator object as they are not thread-safe. Note that the adapter
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 9b3292d..c4fed71 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.secondarydisplay;
 
+import static com.android.launcher3.util.WallpaperThemeManager.setWallpaperDependentTheme;
+import static com.android.window.flags.Flags.enableTaskbarConnectedDisplays;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.content.Intent;
@@ -26,10 +29,11 @@
 import android.view.ViewAnimationUtils;
 import android.view.inputmethod.InputMethodManager;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.BaseActivity;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DragSource;
 import com.android.launcher3.DropTarget;
@@ -54,12 +58,10 @@
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.touch.ItemClickHandler.ItemClickProxy;
 import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.PackageUserKey;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.BaseDragLayer;
-import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -67,7 +69,7 @@
 /**
  * Launcher activity for secondary displays
  */
-public class SecondaryDisplayLauncher extends BaseDraggingActivity
+public class SecondaryDisplayLauncher extends BaseActivity
         implements BgDataModel.Callbacks, DragController.DragListener {
 
     private LauncherModel mModel;
@@ -77,12 +79,10 @@
     private View mAppsButton;
 
     private PopupDataProvider mPopupDataProvider;
-    private WidgetPickerDataProvider mWidgetPickerDataProvider;
 
     private boolean mAppDrawerShown = false;
 
     private StringCache mStringCache;
-    private boolean mBindingItems = false;
     private SecondaryDisplayPredictions mSecondaryDisplayPredictions;
 
     private final int[] mTempXY = new int[2];
@@ -90,49 +90,26 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        setWallpaperDependentTheme(this);
         mModel = LauncherAppState.getInstance(this).getModel();
         mDragController = new SecondaryDragController(this);
         mSecondaryDisplayPredictions = SecondaryDisplayPredictions.newInstance(this);
-        if (getWindow().getDecorView().isAttachedToWindow()) {
-            initUi();
-        }
-    }
 
-    @Override
-    public void onAttachedToWindow() {
-        super.onAttachedToWindow();
-        initUi();
-    }
-
-    @Override
-    public void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-        this.getDragController().removeDragListener(this);
-    }
-
-    private void initUi() {
-        if (mDragLayer != null) {
-            return;
-        }
-        InvariantDeviceProfile currentDisplayIdp = new InvariantDeviceProfile(
-                this, getWindow().getDecorView().getDisplay());
-
-        // Disable transpose layout and use multi-window mode so that the icons are scaled properly
-        mDeviceProfile = currentDisplayIdp.getDeviceProfile(this)
-                .toBuilder(this)
-                .setMultiWindowMode(true)
-                .setTransposeLayoutWithOrientation(false)
-                .build();
+        mDeviceProfile = InvariantDeviceProfile.INSTANCE.get(this)
+                .createDeviceProfileForSecondaryDisplay(this);
         mDeviceProfile.autoResizeAllAppsCells();
 
         setContentView(R.layout.secondary_launcher);
         mDragLayer = findViewById(R.id.drag_layer);
         mAppsView = findViewById(R.id.apps_view);
         mAppsButton = findViewById(R.id.all_apps_button);
+        // TODO (b/391965805): Replace this flag with DesktopExperiences flag.
+        if (enableTaskbarConnectedDisplays()) {
+            mAppsButton.setVisibility(View.INVISIBLE);
+        }
 
         mDragController.addDragListener(this);
-        mPopupDataProvider = new PopupDataProvider(
-                mAppsView.getAppsStore()::updateNotificationDots);
+        mPopupDataProvider = new PopupDataProvider(this);
 
         mModel.addCallbacksAndLoad(this);
     }
@@ -202,14 +179,6 @@
     }
 
     @Override
-    public View getRootView() {
-        return mDragLayer;
-    }
-
-    @Override
-    protected void reapplyUi() { }
-
-    @Override
     public BaseDragLayer getDragLayer() {
         return mDragLayer;
     }
@@ -256,7 +225,9 @@
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     mAppsView.setVisibility(View.INVISIBLE);
-                    mAppsButton.setVisibility(View.VISIBLE);
+                    // TODO (b/391965805): Replace this flag with DesktopExperiences flag.
+                    mAppsButton.setVisibility(
+                            enableTaskbarConnectedDisplays() ? View.INVISIBLE : View.VISIBLE);
                     mAppsView.getSearchUiManager().resetSearch();
                 }
             });
@@ -266,21 +237,10 @@
 
     @Override
     public void startBinding() {
-        mBindingItems = true;
         mDragController.cancelDrag();
     }
 
     @Override
-    public boolean isBindingItems() {
-        return mBindingItems;
-    }
-
-    @Override
-    public void finishBindingItems(IntSet pagesBoundFirst) {
-        mBindingItems = false;
-    }
-
-    @Override
     public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) {
         mPopupDataProvider.setDeepShortcutMap(deepShortcutMap);
     }
@@ -312,16 +272,13 @@
         mStringCache = cache;
     }
 
+    @Override
+    @NonNull
     public PopupDataProvider getPopupDataProvider() {
         return mPopupDataProvider;
     }
 
     @Override
-    public WidgetPickerDataProvider getWidgetPickerDataProvider() {
-        return mWidgetPickerDataProvider;
-    }
-
-    @Override
     public OnClickListener getItemOnClickListener() {
         return this::onIconClicked;
     }
diff --git a/src/com/android/launcher3/shapes/AppShapesProvider.kt b/src/com/android/launcher3/shapes/AppShapesProvider.kt
deleted file mode 100644
index 8c2f181..0000000
--- a/src/com/android/launcher3/shapes/AppShapesProvider.kt
+++ /dev/null
@@ -1,58 +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.shapes
-
-import com.android.systemui.shared.Flags
-
-object AppShapesProvider {
-
-    val shapes =
-        if (Flags.newCustomizationPickerUi())
-            listOf(
-                AppShape(
-                    "arch",
-                    "arch",
-                    "M100 83.46C100 85.471 100 86.476 99.9 87.321 99.116 93.916 93.916 99.116 87.321 99.9 86.476 100 85.471 100 83.46 100H16.54C14.529 100 13.524 100 12.679 99.9 6.084 99.116.884 93.916.1 87.321 0 86.476 0 85.471 0 83.46L0 50C0 22.386 22.386 0 50 0 77.614 0 100 22.386 100 50V83.46Z",
-                ),
-                AppShape(
-                    "4_sided_cookie",
-                    "4 sided cookie",
-                    "M63.605 3C84.733-6.176 106.176 15.268 97 36.395L95.483 39.888C92.681 46.338 92.681 53.662 95.483 60.112L97 63.605C106.176 84.732 84.733 106.176 63.605 97L60.112 95.483C53.662 92.681 46.338 92.681 39.888 95.483L36.395 97C15.267 106.176-6.176 84.732 3 63.605L4.517 60.112C7.319 53.662 7.319 46.338 4.517 39.888L3 36.395C-6.176 15.268 15.267-6.176 36.395 3L39.888 4.517C46.338 7.319 53.662 7.319 60.112 4.517L63.605 3Z",
-                ),
-                AppShape(
-                    "seven_sided_cookie",
-                    "7 sided cookie",
-                    "M35.209 4.878C36.326 3.895 36.884 3.404 37.397 3.006 44.82-2.742 55.18-2.742 62.603 3.006 63.116 3.404 63.674 3.895 64.791 4.878 65.164 5.207 65.351 5.371 65.539 5.529 68.167 7.734 71.303 9.248 74.663 9.932 74.902 9.981 75.147 10.025 75.637 10.113 77.1 10.375 77.831 10.506 78.461 10.66 87.573 12.893 94.032 21.011 94.176 30.412 94.186 31.062 94.151 31.805 94.08 33.293 94.057 33.791 94.045 34.04 94.039 34.285 93.958 37.72 94.732 41.121 96.293 44.18 96.404 44.399 96.522 44.618 96.759 45.056 97.467 46.366 97.821 47.021 98.093 47.611 102.032 56.143 99.727 66.266 92.484 72.24 91.983 72.653 91.381 73.089 90.177 73.961 89.774 74.254 89.572 74.4 89.377 74.548 86.647 76.626 84.477 79.353 83.063 82.483 82.962 82.707 82.865 82.936 82.671 83.395 82.091 84.766 81.8 85.451 81.51 86.033 77.31 94.44 67.977 98.945 58.801 96.994 58.166 96.859 57.451 96.659 56.019 96.259 55.54 96.125 55.3 96.058 55.063 95.998 51.74 95.154 48.26 95.154 44.937 95.998 44.699 96.058 44.46 96.125 43.981 96.259 42.549 96.659 41.834 96.859 41.199 96.994 32.023 98.945 22.69 94.44 18.49 86.033 18.2 85.451 17.909 84.766 17.329 83.395 17.135 82.936 17.038 82.707 16.937 82.483 15.523 79.353 13.353 76.626 10.623 74.548 10.428 74.4 10.226 74.254 9.823 73.961 8.619 73.089 8.017 72.653 7.516 72.24.273 66.266-2.032 56.143 1.907 47.611 2.179 47.021 2.533 46.366 3.241 45.056 3.478 44.618 3.596 44.399 3.707 44.18 5.268 41.121 6.042 37.72 5.961 34.285 5.955 34.04 5.943 33.791 5.92 33.293 5.849 31.805 5.814 31.062 5.824 30.412 5.968 21.011 12.427 12.893 21.539 10.66 22.169 10.506 22.9 10.375 24.363 10.113 24.853 10.025 25.098 9.981 25.337 9.932 28.697 9.248 31.833 7.734 34.461 5.529 34.649 5.371 34.836 5.207 35.209 4.878Z",
-                ),
-                AppShape(
-                    "sunny",
-                    "sunny",
-                    "M42.846 4.873C46.084-.531 53.916-.531 57.154 4.873L60.796 10.951C62.685 14.103 66.414 15.647 69.978 14.754L76.851 13.032C82.962 11.5 88.5 17.038 86.968 23.149L85.246 30.022C84.353 33.586 85.897 37.315 89.049 39.204L95.127 42.846C100.531 46.084 100.531 53.916 95.127 57.154L89.049 60.796C85.897 62.685 84.353 66.414 85.246 69.978L86.968 76.851C88.5 82.962 82.962 88.5 76.851 86.968L69.978 85.246C66.414 84.353 62.685 85.898 60.796 89.049L57.154 95.127C53.916 100.531 46.084 100.531 42.846 95.127L39.204 89.049C37.315 85.898 33.586 84.353 30.022 85.246L23.149 86.968C17.038 88.5 11.5 82.962 13.032 76.851L14.754 69.978C15.647 66.414 14.103 62.685 10.951 60.796L4.873 57.154C-.531 53.916-.531 46.084 4.873 42.846L10.951 39.204C14.103 37.315 15.647 33.586 14.754 30.022L13.032 23.149C11.5 17.038 17.038 11.5 23.149 13.032L30.022 14.754C33.586 15.647 37.315 14.103 39.204 10.951L42.846 4.873Z",
-                ),
-                AppShape(
-                    "circle",
-                    "circle",
-                    "M99.18 50C99.18 77.162 77.162 99.18 50 99.18 22.838 99.18.82 77.162.82 50 .82 22.839 22.838.82 50 .82 77.162.82 99.18 22.839 99.18 50Z",
-                ),
-                AppShape(
-                    "square",
-                    "square",
-                    "M99.18 53.689C99.18 67.434 99.18 74.306 97.022 79.758 93.897 87.649 87.649 93.897 79.758 97.022 74.306 99.18 67.434 99.18 53.689 99.18H46.311C32.566 99.18 25.694 99.18 20.242 97.022 12.351 93.897 6.103 87.649 2.978 79.758.82 74.306.82 67.434.82 53.689L.82 46.311C.82 32.566.82 25.694 2.978 20.242 6.103 12.351 12.351 6.103 20.242 2.978 25.694.82 32.566.82 46.311.82L53.689.82C67.434.82 74.306.82 79.758 2.978 87.649 6.103 93.897 12.351 97.022 20.242 99.18 25.694 99.18 32.566 99.18 46.311V53.689Z\n",
-                ),
-            )
-        else emptyList()
-}
diff --git a/src/com/android/launcher3/shapes/AppShape.kt b/src/com/android/launcher3/shapes/IconShapeModel.kt
similarity index 78%
rename from src/com/android/launcher3/shapes/AppShape.kt
rename to src/com/android/launcher3/shapes/IconShapeModel.kt
index 68200a0..fc49adc 100644
--- a/src/com/android/launcher3/shapes/AppShape.kt
+++ b/src/com/android/launcher3/shapes/IconShapeModel.kt
@@ -16,4 +16,10 @@
 
 package com.android.launcher3.shapes
 
-class AppShape(val key: String, val title: String, val path: String)
+data class IconShapeModel(
+    val key: String,
+    val title: String,
+    val pathString: String,
+    val folderPathString: String = pathString,
+    val iconScale: Float = 1f,
+)
diff --git a/src/com/android/launcher3/shapes/ShapesProvider.kt b/src/com/android/launcher3/shapes/ShapesProvider.kt
new file mode 100644
index 0000000..03e30d8
--- /dev/null
+++ b/src/com/android/launcher3/shapes/ShapesProvider.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shapes
+
+import androidx.annotation.VisibleForTesting
+import com.android.launcher3.Flags as LauncherFlags
+import com.android.systemui.shared.Flags
+
+object ShapesProvider {
+    private const val FOLDER_CLOVER_PATH =
+        "M 39.616 4 C 46.224 6.87 53.727 6.87 60.335 4 L 63.884 2.459 C 85.178 -6.789 106.789 14.822 97.541 36.116 L 96 39.665 C 93.13 46.273 93.13 53.776 96 60.384 L 97.541 63.934 C 106.789 85.227 85.178 106.839 63.884 97.591 L 60.335 96.049 C 53.727 93.179 46.224 93.179 39.616 96.049 L 36.066 97.591 C 14.773 106.839 -6.839 85.227 2.409 63.934 L 3.951 60.384 C 6.821 53.776 6.821 46.273 3.951 39.665 L 2.409 36.116 C -6.839 14.822 14.773 -6.789 36.066 2.459 Z"
+    private const val FOLDER_COMPLEX_CLOVER_PATH =
+        "M 49.85 6.764 L 50.013 6.971 L 50.175 6.764 C 53.422 2.635 58.309 0.207 63.538 0.207 C 65.872 0.207 68.175 0.692 70.381 1.648 L 71.79 2.264 L 71.792 2.265 A 3.46 3.46 0 0 0 74.515 2.265 L 74.517 2.264 L 75.926 1.652 A 17.1 17.1 0 0 1 82.769 0.207 C 88.495 0.207 93.824 3.117 97.022 7.989 C 100.21 12.848 100.697 18.712 98.36 24.087 L 97.749 25.496 V 25.497 A 3.45 3.45 0 0 0 97.749 28.222 V 28.223 L 98.36 29.632 C 100.697 35.007 100.207 40.871 97.022 45.73 A 17.5 17.5 0 0 1 93.264 49.838 L 93.06 50 L 93.264 50.162 A 17.5 17.5 0 0 1 97.022 54.27 C 100.21 59.129 100.697 64.993 98.36 70.368 V 71.778 A 3.45 3.45 0 0 0 97.749 74.503 V 74.504 L 98.36 75.913 C 100.697 81.288 100.207 87.152 97.022 92.011 C 93.824 96.883 88.495 99.793 82.769 99.793 C 80.435 99.793 78.132 99.308 75.926 98.348 L 74.517 97.736 H 74.515 A 3.5 3.5 0 0 0 73.153 97.455 C 72.682 97.455 72.225 97.552 71.792 97.736 H 71.79 L 70.381 98.348 A 17.1 17.1 0 0 1 63.538 99.793 C 58.309 99.793 53.422 97.365 50.175 93.236 L 50.013 93.029 L 49.85 93.236 C 46.603 97.365 41.717 99.793 36.488 99.793 C 34.154 99.793 31.851 99.308 29.645 98.348 L 28.236 97.736 H 28.234 A 3.5 3.5 0 0 0 26.872 97.455 C 26.401 97.455 25.944 97.552 25.511 97.736 H 25.509 L 24.1 98.348 A 17.1 17.1 0 0 1 17.257 99.793 C 11.53 99.793 6.202 96.883 3.004 92.011 C -0.181 87.152 -0.671 81.288 1.661 75.913 L 2.277 74.504 V 74.503 A 3.45 3.45 0 0 0 2.277 71.778 V 71.777 L 1.665 70.368 C -0.671 64.993 -0.181 59.129 3.004 54.274 A 17.5 17.5 0 0 1 6.761 50.162 L 6.965 50 L 6.761 49.838 A 17.5 17.5 0 0 1 3.004 45.73 C -0.181 40.871 -0.671 35.007 1.665 29.632 L 2.277 28.223 V 28.222 A 3.45 3.45 0 0 0 2.277 25.497 V 25.496 L 1.665 24.087 C -0.671 18.712 -0.181 12.848 3.004 7.994 V 7.993 C 6.202 3.117 11.53 0.207 17.257 0.207 C 19.591 0.207 21.894 0.692 24.1 1.652 L 25.509 2.264 L 25.511 2.265 A 3.46 3.46 0 0 0 28.234 2.265 L 28.236 2.264 L 29.645 1.652 A 17.1 17.1 0 0 1 36.488 0.207 C 41.717 0.207 46.603 2.635 49.85 6.764 Z"
+    private const val FOLDER_ARCH_PATH =
+        "M 50 0 L 72.5 0 A 27.5 27.5 0 0 1 100 27.5 L 100 86.67 A 13.33 13.33 0 0 1 86.67 100 L 13.33 100 A 13.33 13.33 0 0 1 0 86.67 L 0 27.5 A 27.5 27.5 0 0 1 27.5 0 Z"
+    private const val FOLDER_SQUARE_PATH =
+        "M 50 0 L 83.4 0 A 16.6 16.6 0 0 1 100 16.6 L 100 83.4 A 16.6 16.6 0 0 1 83.4 100 L 16.6 100 A 16.6 16.6 0 0 1 0 83.4 L 0 16.6 A 16.6 16.6 0 0 1 16.6 0 Z"
+    private const val CIRCLE_PATH = "M50 0A50 50,0,1,1,50 100A50 50,0,1,1,50 0"
+    private const val SQUARE_PATH =
+        "M53.689 0.82 L53.689 .82 C67.434 .82 74.306 .82 79.758 2.978 87.649 6.103 93.897 12.351 97.022 20.242 99.18 25.694 99.18 32.566 99.18 46.311 V53.689 C99.18 67.434 99.18 74.306 97.022 79.758 93.897 87.649 87.649 93.897 79.758 97.022 74.306 99.18 67.434 99.18 53.689 99.18 H46.311 C32.566 99.18 25.694 99.18 20.242 97.022 12.351 93.897 6.103 87.649 2.978 79.758 .82 74.306 .82 67.434 .82 53.689 L.82 46.311 C.82 32.566 .82 25.694 2.978 20.242 6.103 12.351 12.351 6.103 20.242 2.978 25.694 .82 32.566 .82 46.311 .82Z"
+    private const val FOUR_SIDED_COOKIE_PATH =
+        "M39.888,4.517C46.338 7.319 53.662 7.319 60.112 4.517L63.605 3C84.733 -6.176 106.176 15.268 97 36.395L95.483 39.888C92.681 46.338 92.681 53.662 95.483 60.112L97 63.605C106.176 84.732 84.733 106.176 63.605 97L60.112 95.483C53.662 92.681 46.338 92.681 39.888 95.483L36.395 97C15.267 106.176 -6.176 84.732 3 63.605L4.517 60.112C7.319 53.662 7.319 46.338 4.517 39.888L3 36.395C -6.176 15.268 15.267 -6.176 36.395 3Z"
+    private const val SEVEN_SIDED_COOKIE_PATH =
+        "M35.209 4.878C36.326 3.895 36.884 3.404 37.397 3.006 44.82 -2.742 55.18 -2.742 62.603 3.006 63.116 3.404 63.674 3.895 64.791 4.878 65.164 5.207 65.351 5.371 65.539 5.529 68.167 7.734 71.303 9.248 74.663 9.932 74.902 9.981 75.147 10.025 75.637 10.113 77.1 10.375 77.831 10.506 78.461 10.66 87.573 12.893 94.032 21.011 94.176 30.412 94.186 31.062 94.151 31.805 94.08 33.293 94.057 33.791 94.045 34.04 94.039 34.285 93.958 37.72 94.732 41.121 96.293 44.18 96.404 44.399 96.522 44.618 96.759 45.056 97.467 46.366 97.821 47.021 98.093 47.611 102.032 56.143 99.727 66.266 92.484 72.24 91.983 72.653 91.381 73.089 90.177 73.961 89.774 74.254 89.572 74.4 89.377 74.548 86.647 76.626 84.477 79.353 83.063 82.483 82.962 82.707 82.865 82.936 82.671 83.395 82.091 84.766 81.8 85.451 81.51 86.033 77.31 94.44 67.977 98.945 58.801 96.994 58.166 96.859 57.451 96.659 56.019 96.259 55.54 96.125 55.3 96.058 55.063 95.998 51.74 95.154 48.26 95.154 44.937 95.998 44.699 96.058 44.46 96.125 43.981 96.259 42.549 96.659 41.834 96.859 41.199 96.994 32.023 98.945 22.69 94.44 18.49 86.033 18.2 85.451 17.909 84.766 17.329 83.395 17.135 82.936 17.038 82.707 16.937 82.483 15.523 79.353 13.353 76.626 10.623 74.548 10.428 74.4 10.226 74.254 9.823 73.961 8.619 73.089 8.017 72.653 7.516 72.24 .273 66.266 -2.032 56.143 1.907 47.611 2.179 47.021 2.533 46.366 3.241 45.056 3.478 44.618 3.596 44.399 3.707 44.18 5.268 41.121 6.042 37.72 5.961 34.285 5.955 34.04 5.943 33.791 5.92 33.293 5.849 31.805 5.814 31.062 5.824 30.412 5.968 21.011 12.427 12.893 21.539 10.66 22.169 10.506 22.9 10.375 24.363 10.113 24.853 10.025 25.098 9.981 25.337 9.932 28.697 9.248 31.833 7.734 34.461 5.529 34.649 5.371 34.836 5.207 35.209 4.878Z"
+    private const val ARCH_PATH =
+        "M50 0C77.614 0 100 22.386 100 50C100 85.471 100 86.476 99.9 87.321 99.116 93.916 93.916 99.116 87.321 99.9 86.476 100 85.471 100 83.46 100H16.54C14.529 100 13.524 100 12.679 99.9 6.084 99.116 .884 93.916 .1 87.321 0 86.476 0 85.471 0 83.46L0 50C0 22.386 22.386 0 50 0Z"
+    @VisibleForTesting const val CIRCLE_KEY = "circle"
+    @VisibleForTesting const val SQUARE_KEY = "square"
+    @VisibleForTesting const val FOUR_SIDED_COOKIE_KEY = "four_sided_cookie"
+    @VisibleForTesting const val SEVEN_SIDED_COOKIE_KEY = "seven_sided_cookie"
+    @VisibleForTesting const val ARCH_KEY = "arch"
+
+    val iconShapes =
+        if (Flags.newCustomizationPickerUi() && LauncherFlags.enableLauncherIconShapes()) {
+            arrayOf(
+                IconShapeModel(
+                    key = CIRCLE_KEY,
+                    title = "circle",
+                    pathString = CIRCLE_PATH,
+                    folderPathString = FOLDER_CLOVER_PATH,
+                ),
+                IconShapeModel(
+                    key = SQUARE_KEY,
+                    title = "square",
+                    pathString = SQUARE_PATH,
+                    folderPathString = FOLDER_SQUARE_PATH,
+                ),
+                IconShapeModel(
+                    key = FOUR_SIDED_COOKIE_KEY,
+                    title = "4 sided cookie",
+                    pathString = FOUR_SIDED_COOKIE_PATH,
+                    folderPathString = FOLDER_COMPLEX_CLOVER_PATH,
+                    iconScale = 72f / 83.4f,
+                ),
+                IconShapeModel(
+                    key = SEVEN_SIDED_COOKIE_KEY,
+                    title = "7 sided cookie",
+                    pathString = SEVEN_SIDED_COOKIE_PATH,
+                    folderPathString = FOLDER_CLOVER_PATH,
+                    iconScale = 72f / 80f,
+                ),
+                IconShapeModel(
+                    key = ARCH_KEY,
+                    title = "arch",
+                    pathString = ARCH_PATH,
+                    folderPathString = FOLDER_ARCH_PATH,
+                ),
+            )
+        } else {
+            arrayOf(IconShapeModel(key = CIRCLE_KEY, title = "circle", pathString = CIRCLE_PATH))
+        }
+}
diff --git a/src/com/android/launcher3/statemanager/BaseState.java b/src/com/android/launcher3/statemanager/BaseState.java
index f6b610c..ea54159 100644
--- a/src/com/android/launcher3/statemanager/BaseState.java
+++ b/src/com/android/launcher3/statemanager/BaseState.java
@@ -15,15 +15,13 @@
  */
 package com.android.launcher3.statemanager;
 
-import android.content.Context;
-
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.views.ActivityContext;
 
 /**
  * Interface representing a state of a StatefulContainer
  */
-public interface BaseState<T extends BaseState> {
+public interface BaseState<T> {
 
     // Flag to indicate that Launcher is non-interactive in this state
     int FLAG_NON_INTERACTIVE = 1 << 0;
@@ -37,8 +35,7 @@
     /**
      * @return How long the animation to this state should take (or from this state to NORMAL).
      */
-    <DEVICE_PROFILE_CONTEXT extends Context & ActivityContext>
-    int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState);
+    int getTransitionDuration(ActivityContext context, boolean isToState);
 
     /**
      * Returns the state to go back to from this state
@@ -72,6 +69,13 @@
     }
 
     /**
+     * For this state, whether we should show desktop exploded view in Overview.
+     */
+    default boolean showExplodedDesktopView() {
+        return false;
+    }
+
+    /**
      * For this state, whether fullscreen and desktop quickswitch carousel are detached.
      */
     default boolean detachDesktopCarousel() {
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 763f3ba..a125331 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -27,7 +27,6 @@
 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;
@@ -50,50 +49,49 @@
 /**
  * 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
+ * @param <S> Basestate used by the state manager
+ * @param <T> container object used to manage state
  */
-public class StateManager<STATE_TYPE extends BaseState<STATE_TYPE>,
-        STATEFUL_CONTAINER extends Context & StatefulContainer<STATE_TYPE>> {
+public class StateManager<S extends BaseState<S>, T extends StatefulContainer<S>> {
 
     public static final String TAG = "StateManager";
     // b/279059025, b/325463989
     private static final boolean DEBUG = true;
 
-    private final AnimationState mConfig = new AnimationState();
+    private final AnimationState<S> mConfig = new AnimationState<>();
     private final Handler mUiHandler;
-    private final STATEFUL_CONTAINER mStatefulContainer;
-    private final ArrayList<StateListener<STATE_TYPE>> mListeners = new ArrayList<>();
-    private final STATE_TYPE mBaseState;
+    private final T mContainer;
+    private final ArrayList<StateListener<S>> mListeners = new ArrayList<>();
+    private final S mBaseState;
 
     // Animators which are run on properties also controlled by state animations.
-    private final AtomicAnimationFactory mAtomicAnimationFactory;
+    private final AtomicAnimationFactory<S> mAtomicAnimationFactory;
 
-    private StateHandler<STATE_TYPE>[] mStateHandlers;
-    private STATE_TYPE mState;
+    private StateHandler<S>[] mStateHandlers;
+    private S mState;
 
-    private STATE_TYPE mLastStableState;
-    private STATE_TYPE mCurrentStableState;
+    private S mLastStableState;
+    private S mCurrentStableState;
 
-    private STATE_TYPE mRestState;
+    private S mRestState;
 
-    public StateManager(STATEFUL_CONTAINER container, STATE_TYPE baseState) {
+    public StateManager(T container, S baseState) {
         mUiHandler = new Handler(Looper.getMainLooper());
-        mStatefulContainer = container;
+        mContainer = container;
         mBaseState = baseState;
         mState = mLastStableState = mCurrentStableState = baseState;
         mAtomicAnimationFactory = container.createAtomicAnimationFactory();
     }
 
-    public STATE_TYPE getState() {
+    public S getState() {
         return mState;
     }
 
-    public STATE_TYPE getTargetState() {
-        return (STATE_TYPE) mConfig.targetState;
+    public S getTargetState() {
+        return mConfig.targetState;
     }
 
-    public STATE_TYPE getCurrentStableState() {
+    public S getCurrentStableState() {
         return mCurrentStableState;
     }
 
@@ -115,20 +113,20 @@
         writer.println(prefix + "\tisInTransition:" + isInTransition());
     }
 
-    public StateHandler<STATE_TYPE>[] getStateHandlers() {
+    public StateHandler<S>[] getStateHandlers() {
         if (mStateHandlers == null) {
-            ArrayList<StateHandler<STATE_TYPE>> handlers = new ArrayList<>();
-            mStatefulContainer.collectStateHandlers(handlers);
+            ArrayList<StateHandler<S>> handlers = new ArrayList<>();
+            mContainer.collectStateHandlers(handlers);
             mStateHandlers = handlers.toArray(new StateHandler[handlers.size()]);
         }
         return mStateHandlers;
     }
 
-    public void addStateListener(StateListener listener) {
+    public void addStateListener(StateListener<S> listener) {
         mListeners.add(listener);
     }
 
-    public void removeStateListener(StateListener listener) {
+    public void removeStateListener(StateListener<S> listener) {
         mListeners.remove(listener);
     }
 
@@ -136,14 +134,14 @@
      * Returns true if the state changes should be animated.
      */
     public boolean shouldAnimateStateChange() {
-        return mStatefulContainer.shouldAnimateStateChange();
+        return mContainer.shouldAnimateStateChange();
     }
 
     /**
      * @return {@code true} if the state matches the current state and there is no active
      *         transition to different state.
      */
-    public boolean isInStableState(STATE_TYPE state) {
+    public boolean isInStableState(S state) {
         return mState == state && mCurrentStableState == state
                 && (mConfig.targetState == null || mConfig.targetState == state);
     }
@@ -156,23 +154,23 @@
     }
 
     /**
-     * @see #goToState(STATE_TYPE, boolean, AnimatorListener)
+     * @see #goToState(S, boolean, AnimatorListener)
      */
-    public void goToState(STATE_TYPE state) {
+    public void goToState(S state) {
         goToState(state, shouldAnimateStateChange());
     }
 
     /**
-     * @see #goToState(STATE_TYPE, boolean, AnimatorListener)
+     * @see #goToState(S, boolean, AnimatorListener)
      */
-    public void goToState(STATE_TYPE state, AnimatorListener listener) {
+    public void goToState(S state, AnimatorListener listener) {
         goToState(state, shouldAnimateStateChange(), listener);
     }
 
     /**
-     * @see #goToState(STATE_TYPE, boolean, AnimatorListener)
+     * @see #goToState(S, boolean, AnimatorListener)
      */
-    public void goToState(STATE_TYPE state, boolean animated) {
+    public void goToState(S state, boolean animated) {
         goToState(state, animated, 0, null);
     }
 
@@ -183,21 +181,21 @@
      *                true otherwise
      * @param listener any action to perform at the end of the transition, or null.
      */
-    public void goToState(STATE_TYPE state, boolean animated, AnimatorListener listener) {
+    public void goToState(S state, boolean animated, AnimatorListener listener) {
         goToState(state, animated, 0, listener);
     }
 
     /**
      * Changes the Launcher state to the provided state after the given delay.
      */
-    public void goToState(STATE_TYPE state, long delay, AnimatorListener listener) {
+    public void goToState(S state, long delay, AnimatorListener listener) {
         goToState(state, true, delay, listener);
     }
 
     /**
      * Changes the Launcher state to the provided state after the given delay.
      */
-    public void goToState(STATE_TYPE state, long delay) {
+    public void goToState(S state, long delay) {
         goToState(state, true, delay, null);
     }
 
@@ -219,7 +217,7 @@
             cancelAnimation();
         }
         if (mConfig.currentAnimation == null) {
-            for (StateHandler handler : getStateHandlers()) {
+            for (StateHandler<S> handler : getStateHandlers()) {
                 handler.setState(mState);
             }
             if (wasInAnimation) {
@@ -230,21 +228,21 @@
 
     /** Handles backProgress in predictive back gesture by passing it to state handlers. */
     public void onBackProgressed(
-            STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {
-        for (StateHandler handler : getStateHandlers()) {
+            S toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {
+        for (StateHandler<S> handler : getStateHandlers()) {
             handler.onBackProgressed(toState, backProgress);
         }
     }
 
     /** Handles back cancelled event in predictive back gesture by passing it to state handlers. */
-    public void onBackCancelled(STATE_TYPE toState) {
-        for (StateHandler handler : getStateHandlers()) {
+    public void onBackCancelled(S toState) {
+        for (StateHandler<S> handler : getStateHandlers()) {
             handler.onBackCancelled(toState);
         }
     }
 
     private void goToState(
-            STATE_TYPE state, boolean animated, long delay, AnimatorListener listener) {
+            S state, boolean animated, long delay, AnimatorListener listener) {
         if (enableStateManagerProtoLog()) {
             StateManagerProtoLogProxy.logGoToState(
                     mState, state, getTrimmedStackTrace("StateManager.goToState"));
@@ -254,7 +252,7 @@
         }
 
         animated &= areAnimatorsEnabled();
-        if (mStatefulContainer.isInState(state)) {
+        if (getState() == state) {
             if (mConfig.currentAnimation == null) {
                 // Run any queued runnable
                 if (listener != null) {
@@ -273,13 +271,13 @@
         }
 
         // Cancel the current animation. This will reset mState to mCurrentStableState, so store it.
-        STATE_TYPE fromState = mState;
+        S fromState = mState;
         cancelAnimation();
 
         if (!animated) {
             mAtomicAnimationFactory.cancelAllStateElementAnimation();
             onStateTransitionStart(state);
-            for (StateHandler handler : getStateHandlers()) {
+            for (StateHandler<S> handler : getStateHandlers()) {
                 handler.setState(state);
             }
 
@@ -306,13 +304,13 @@
         }
     }
 
-    private void goToStateAnimated(STATE_TYPE state, STATE_TYPE fromState,
+    private void goToStateAnimated(S state, S fromState,
             AnimatorListener listener) {
         // 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(mStatefulContainer, false /* isToState */)
-                : state.getTransitionDuration(mStatefulContainer, true /* isToState */);
+                ? fromState.getTransitionDuration(mContainer, false /* isToState */)
+                : state.getTransitionDuration(mContainer, true /* isToState */);
         prepareForAtomicAnimation(fromState, state, mConfig);
         AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).buildAnim();
         if (listener != null) {
@@ -326,7 +324,7 @@
      * - Setting interpolators for various animations included in the state transition.
      * - Setting some start values (e.g. scale) for views that are hidden but about to be shown.
      */
-    public void prepareForAtomicAnimation(STATE_TYPE fromState, STATE_TYPE toState,
+    public void prepareForAtomicAnimation(S fromState, S toState,
             StateAnimationConfig config) {
         mAtomicAnimationFactory.prepareForAtomicAnimation(fromState, toState, config);
     }
@@ -335,7 +333,7 @@
      * Creates an animation representing atomic transitions between the provided states
      */
     public AnimatorSet createAtomicAnimation(
-            STATE_TYPE fromState, STATE_TYPE toState, StateAnimationConfig config) {
+            S fromState, S toState, StateAnimationConfig config) {
         if (enableStateManagerProtoLog()) {
             StateManagerProtoLogProxy.logCreateAtomicAnimation(
                     mState, toState, getTrimmedStackTrace("StateManager.createAtomicAnimation"));
@@ -348,7 +346,7 @@
         PendingAnimation builder = new PendingAnimation(config.duration);
         prepareForAtomicAnimation(fromState, toState, config);
 
-        for (StateHandler handler : mStatefulContainer.getStateManager().getStateHandlers()) {
+        for (StateHandler<S> handler : getStateHandlers()) {
             handler.setStateWithAnimation(toState, config, builder);
         }
         return builder.buildAnim();
@@ -362,19 +360,19 @@
      *                accuracy.
      */
     public AnimatorPlaybackController createAnimationToNewWorkspace(
-            STATE_TYPE state, long duration) {
+            S state, long duration) {
         return createAnimationToNewWorkspace(state, duration, 0 /* animFlags */);
     }
 
     public AnimatorPlaybackController createAnimationToNewWorkspace(
-            STATE_TYPE state, long duration, @AnimationFlags int animFlags) {
+            S state, long duration, @AnimationFlags int animFlags) {
         StateAnimationConfig config = new StateAnimationConfig();
         config.duration = duration;
         config.animFlags = animFlags;
         return createAnimationToNewWorkspace(state, config);
     }
 
-    public AnimatorPlaybackController createAnimationToNewWorkspace(STATE_TYPE state,
+    public AnimatorPlaybackController createAnimationToNewWorkspace(S state,
             StateAnimationConfig config) {
         config.animProps |= StateAnimationConfig.USER_CONTROLLED;
         cancelAnimation();
@@ -384,10 +382,10 @@
         return mConfig.playbackController;
     }
 
-    private PendingAnimation createAnimationToNewWorkspaceInternal(final STATE_TYPE state) {
+    private PendingAnimation createAnimationToNewWorkspaceInternal(final S state) {
         PendingAnimation builder = new PendingAnimation(mConfig.duration);
         if (!mConfig.hasAnimationFlag(SKIP_ALL_ANIMATIONS)) {
-            for (StateHandler handler : getStateHandlers()) {
+            for (StateHandler<S> handler : getStateHandlers()) {
                 handler.setStateWithAnimation(state, mConfig, builder);
             }
         }
@@ -396,7 +394,7 @@
         return builder;
     }
 
-    private AnimatorListener createStateAnimationListener(STATE_TYPE state) {
+    private AnimatorListener createStateAnimationListener(S state) {
         return new AnimationSuccessListener() {
 
             @Override
@@ -412,9 +410,9 @@
         };
     }
 
-    private void onStateTransitionStart(STATE_TYPE state) {
+    private void onStateTransitionStart(S state) {
         mState = state;
-        mStatefulContainer.onStateSetStart(mState);
+        mContainer.onStateSetStart(mState);
 
         if (enableStateManagerProtoLog()) {
             StateManagerProtoLogProxy.logOnStateTransitionStart(state);
@@ -426,14 +424,14 @@
         }
     }
 
-    private void onStateTransitionEnd(STATE_TYPE state) {
+    private void onStateTransitionEnd(S state) {
         // Only change the stable states after the transitions have finished
         if (state != mCurrentStableState) {
             mLastStableState = state.getHistoryForState(mCurrentStableState);
             mCurrentStableState = state;
         }
 
-        mStatefulContainer.onStateSetEnd(state);
+        mContainer.onStateSetEnd(state);
         if (state == mBaseState) {
             setRestState(null);
         }
@@ -448,7 +446,7 @@
         }
     }
 
-    public STATE_TYPE getLastState() {
+    public S getLastState() {
         return mLastStableState;
     }
 
@@ -468,11 +466,11 @@
         }
     }
 
-    public STATE_TYPE getRestState() {
+    public S getRestState() {
         return mRestState == null ? mBaseState : mRestState;
     }
 
-    public void setRestState(STATE_TYPE restState) {
+    public void setRestState(S restState) {
         mRestState = restState;
     }
 
@@ -518,7 +516,7 @@
      * @param anim The custom animation to the given state.
      * @param toState The state we are animating towards.
      */
-    public void setCurrentAnimation(AnimatorSet anim, STATE_TYPE toState) {
+    public void setCurrentAnimation(AnimatorSet anim, S toState) {
         cancelAnimation();
         setCurrentAnimation(anim);
         anim.addListener(createStateAnimationListener(toState));
@@ -691,10 +689,10 @@
 
         /** Handles backProgress in predictive back gesture for target state. */
         default void onBackProgressed(
-                STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {};
+                STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {}
 
         /** Handles back cancelled event in predictive back gesture for target state.  */
-        default void onBackCancelled(STATE_TYPE toState) {};
+        default void onBackCancelled(STATE_TYPE toState) {}
     }
 
     public interface StateListener<STATE_TYPE> {
diff --git a/src/com/android/launcher3/statemanager/StatefulActivity.java b/src/com/android/launcher3/statemanager/StatefulActivity.java
index f21e5da..445701d 100644
--- a/src/com/android/launcher3/statemanager/StatefulActivity.java
+++ b/src/com/android/launcher3/statemanager/StatefulActivity.java
@@ -18,10 +18,8 @@
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 
-import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_RECREATE_TO_UPDATE_THEME;
 import static com.android.launcher3.LauncherState.FLAG_NON_INTERACTIVE;
 
-import android.content.Context;
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.Handler;
@@ -30,9 +28,8 @@
 import android.view.View;
 
 import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
 
-import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.BaseActivity;
 import com.android.launcher3.LauncherRootView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.statemanager.StateManager.StateHandler;
@@ -46,7 +43,7 @@
  * @param <STATE_TYPE> Type of state object
  */
 public abstract class StatefulActivity<STATE_TYPE extends BaseState<STATE_TYPE>>
-        extends BaseDraggingActivity implements StatefulContainer<STATE_TYPE> {
+        extends BaseActivity implements StatefulContainer<STATE_TYPE> {
 
     public final Handler mHandler = new Handler();
     private final Runnable mHandleDeferredResume = this::handleDeferredResume;
@@ -56,7 +53,6 @@
 
     protected Configuration mOldConfig;
     private int mOldRotation;
-    private boolean mRecreateToUpdateTheme = false;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -66,18 +62,6 @@
         mOldRotation = WindowManagerProxy.INSTANCE.get(this).getRotation(this);
     }
 
-    @Override
-    protected void onSaveInstanceState(@NonNull Bundle outState) {
-        outState.putBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME, mRecreateToUpdateTheme);
-        super.onSaveInstanceState(outState);
-    }
-
-    @Override
-    protected void recreateToUpdateTheme() {
-        mRecreateToUpdateTheme = true;
-        super.recreateToUpdateTheme();
-    }
-
     /**
      * Create handlers to control the property changes for this activity
      */
@@ -214,11 +198,6 @@
         mOldRotation = rotation;
     }
 
-    @Override
-    public Context getContext() {
-        return this;
-    }
-
     /**
      * Logic for when device configuration changes (rotation, screen size change, multi-window,
      * etc.)
diff --git a/src/com/android/launcher3/statemanager/StatefulContainer.java b/src/com/android/launcher3/statemanager/StatefulContainer.java
index b10af0a..83a2fdc 100644
--- a/src/com/android/launcher3/statemanager/StatefulContainer.java
+++ b/src/com/android/launcher3/statemanager/StatefulContainer.java
@@ -20,8 +20,6 @@
 import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
 import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
 
-import android.content.Context;
-import android.content.ContextWrapper;
 import android.content.res.Configuration;
 
 import androidx.annotation.CallSuper;
@@ -40,23 +38,6 @@
         ActivityContext {
 
     /**
-     * Returns an instance of an implementation of StatefulContainer
-     *
-     * @param context will find instance of StatefulContainer from given context.
-     */
-    static <T extends StatefulContainer> T fromContext(Context context) {
-        if (context instanceof StatefulContainer) {
-            return (T) context;
-        } else if (context instanceof ContextWrapper) {
-            return fromContext(((ContextWrapper) context).getBaseContext());
-        } else {
-            throw new IllegalArgumentException("Cannot find StatefulContainer in parent tree");
-        }
-    }
-
-    Context getContext();
-
-    /**
      * Creates a factory for atomic state animations
      */
     default StateManager.AtomicAnimationFactory<STATE_TYPE> createAtomicAnimationFactory() {
diff --git a/src/com/android/launcher3/states/EditModeState.kt b/src/com/android/launcher3/states/EditModeState.kt
index 6ff47ae..268a373 100644
--- a/src/com/android/launcher3/states/EditModeState.kt
+++ b/src/com/android/launcher3/states/EditModeState.kt
@@ -36,11 +36,7 @@
                 FLAG_WORKSPACE_HAS_BACKGROUNDS)
     }
 
-    override fun <T> getTransitionDuration(context: T, isToState: Boolean): Int where
-    T : Context?,
-    T : ActivityContext? {
-        return 150
-    }
+    override fun getTransitionDuration(context: ActivityContext, isToState: Boolean) = 150
 
     override fun <T> getDepthUnchecked(context: T): Float where T : Context?, T : ActivityContext? {
         if (enableScalingRevealHomeAnimation()) {
diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java
index bf2fb30..ed22d39 100644
--- a/src/com/android/launcher3/states/HintState.java
+++ b/src/com/android/launcher3/states/HintState.java
@@ -26,6 +26,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
 
 /**
  * Scale down workspace/hotseat to hint at going to either overview (on pause) or first home screen.
@@ -46,7 +47,7 @@
     }
 
     @Override
-    public int getTransitionDuration(Context context, boolean isToState) {
+    public int getTransitionDuration(ActivityContext context, boolean isToState) {
         return 80;
     }
 
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 7d7ccd3..9376518 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -29,6 +29,7 @@
 import android.content.Context;
 import android.os.Handler;
 import android.os.Message;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.WorkerThread;
@@ -210,7 +211,9 @@
         }
 
         final int activityFlags;
-        if (mStateHandlerRequest != REQUEST_NONE) {
+        if (mIsFixedLandscape) {
+            activityFlags = SCREEN_ORIENTATION_USER_LANDSCAPE;
+        } else if (mStateHandlerRequest != REQUEST_NONE) {
             activityFlags = mStateHandlerRequest == REQUEST_LOCK ?
                     SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
         } else if (mCurrentTransitionRequest != REQUEST_NONE) {
@@ -218,8 +221,6 @@
                     SCREEN_ORIENTATION_LOCKED : SCREEN_ORIENTATION_UNSPECIFIED;
         } else if (mCurrentStateRequest == REQUEST_LOCK) {
             activityFlags = SCREEN_ORIENTATION_LOCKED;
-        } else if (mIsFixedLandscape) {
-            activityFlags = SCREEN_ORIENTATION_USER_LANDSCAPE;
         } else if (mIgnoreAutoRotateSettings || mCurrentStateRequest == REQUEST_ROTATE
                 || mHomeRotationEnabled || mForceAllowRotationForTesting) {
             activityFlags = SCREEN_ORIENTATION_UNSPECIFIED;
@@ -230,6 +231,7 @@
         }
         if (activityFlags != mLastActivityFlags) {
             mLastActivityFlags = activityFlags;
+            Log.d("b/380940677", toString());
             mRequestOrientationHandler.sendEmptyMessage(activityFlags);
         }
     }
@@ -257,9 +259,9 @@
         return String.format("[mStateHandlerRequest=%d, mCurrentStateRequest=%d, "
                         + "mLastActivityFlags=%d, mIgnoreAutoRotateSettings=%b, "
                         + "mHomeRotationEnabled=%b, mForceAllowRotationForTesting=%b,"
-                        + " mDestroyed=%b]",
+                        + " mDestroyed=%b, mIsFixedLandscape=%b]",
                 mStateHandlerRequest, mCurrentStateRequest, mLastActivityFlags,
                 mIgnoreAutoRotateSettings, mHomeRotationEnabled, mForceAllowRotationForTesting,
-                mDestroyed);
+                mDestroyed, mIsFixedLandscape);
     }
 }
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 2e57ed8..15e6c61 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -24,6 +24,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Workspace;
+import com.android.launcher3.views.ActivityContext;
 
 /**
  * Definition for spring loaded state used during drag and drop.
@@ -41,7 +42,7 @@
     }
 
     @Override
-    public int getTransitionDuration(Context context, boolean isToState) {
+    public int getTransitionDuration(ActivityContext context, boolean isToState) {
         return 150;
     }
 
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index aa3f2f2..e5105cd 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -15,12 +15,12 @@
  */
 package com.android.launcher3.testing;
 
+import static com.android.launcher3.Flags.enableFallbackOverviewInWindow;
 import static com.android.launcher3.Flags.enableGridOnlyOverview;
+import static com.android.launcher3.Flags.enableLauncherOverviewInWindow;
 import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
 import static com.android.launcher3.config.FeatureFlags.FOLDABLE_SINGLE_PAGE;
-import static com.android.launcher3.config.FeatureFlags.enableAppPairs;
-import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
 import static com.android.launcher3.testing.shared.TestProtocol.TEST_INFO_RESPONSE_FIELD;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -55,9 +55,7 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.icons.ClockDrawableWrapper;
-import com.android.launcher3.testing.shared.HotseatCellCenterRequest;
 import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.testing.shared.WorkspaceCellCenterRequest;
 import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.ResourceBasedOverride;
@@ -216,6 +214,10 @@
                         ENABLE_TASKBAR_NAVBAR_UNIFICATION);
                 return response;
 
+            case TestProtocol.REQUEST_TASKBAR_SHOWN_ON_HOME:
+                response.putBoolean(TEST_INFO_RESPONSE_FIELD,
+                        DisplayController.showLockedTaskbarOnHome(mContext));
+                return response;
             case TestProtocol.REQUEST_NUM_ALL_APPS_COLUMNS:
                 response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
                         mDeviceProfile.numShownAllAppsColumns);
@@ -245,8 +247,8 @@
             }
 
             case TestProtocol.REQUEST_GET_SPLIT_SELECTION_ACTIVE:
-                response.putBoolean(TEST_INFO_RESPONSE_FIELD, enableSplitContextually()
-                        && Launcher.ACTIVITY_TRACKER.getCreatedContext().isSplitSelectionActive());
+                response.putBoolean(TEST_INFO_RESPONSE_FIELD,
+                        Launcher.ACTIVITY_TRACKER.getCreatedContext().isSplitSelectionActive());
                 return response;
 
             case TestProtocol.REQUEST_ENABLE_ROTATION:
@@ -265,15 +267,15 @@
                 });
 
             case TestProtocol.REQUEST_WORKSPACE_CELL_CENTER: {
-                final WorkspaceCellCenterRequest request = extra.getParcelable(
-                        TestProtocol.TEST_INFO_REQUEST_FIELD);
+                Rect cellPos = extra.getParcelable(TestProtocol.TEST_INFO_PARAM_CELL_SPAN);
                 return getLauncherUIProperty(Bundle::putParcelable, launcher -> {
                     final Workspace<?> workspace = launcher.getWorkspace();
                     // TODO(b/216387249): allow caller selecting different pages.
                     CellLayout cellLayout = (CellLayout) workspace.getPageAt(
                             workspace.getCurrentPage());
                     final Rect cellRect = getDescendantRectRelativeToDragLayerForCell(launcher,
-                            cellLayout, request.cellX, request.cellY, request.spanX, request.spanY);
+                            cellLayout, cellPos.left, cellPos.top, cellPos.width(),
+                            cellPos.height());
                     return new Point(cellRect.centerX(), cellRect.centerY());
                 });
             }
@@ -292,12 +294,11 @@
             }
 
             case TestProtocol.REQUEST_HOTSEAT_CELL_CENTER: {
-                final HotseatCellCenterRequest request = extra.getParcelable(
-                        TestProtocol.TEST_INFO_REQUEST_FIELD);
+                int cellIndex = extra.getInt(TestProtocol.TEST_INFO_PARAM_INDEX);
                 return getLauncherUIProperty(Bundle::putParcelable, launcher -> {
                     final Hotseat hotseat = launcher.getHotseat();
                     final Rect cellRect = getDescendantRectRelativeToDragLayerForCell(launcher,
-                            hotseat, request.cellInd, /* cellY= */ 0,
+                            hotseat, cellIndex, /* cellY= */ 0,
                             /* spanX= */ 1, /* spanY= */ 1);
                     // TODO(b/234322284): return the real center point.
                     return new Point(cellRect.left + (cellRect.right - cellRect.left) / 3,
@@ -328,8 +329,9 @@
                 return response;
             }
 
-            case TestProtocol.REQUEST_FLAG_ENABLE_APP_PAIRS: {
-                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, enableAppPairs());
+            case TestProtocol.REQUEST_IS_RECENTS_WINDOW_ENABLED: {
+                response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD,
+                        enableLauncherOverviewInWindow() || enableFallbackOverviewInWindow());
                 return response;
             }
 
diff --git a/src/com/android/launcher3/testing/TestInformationProvider.java b/src/com/android/launcher3/testing/TestInformationProvider.java
index 17b472a..4b592e7 100644
--- a/src/com/android/launcher3/testing/TestInformationProvider.java
+++ b/src/com/android/launcher3/testing/TestInformationProvider.java
@@ -16,61 +16,40 @@
 
 package com.android.launcher3.testing;
 
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
+import android.content.Context;
 import android.os.Bundle;
 import android.util.Log;
 
-import com.android.launcher3.Utilities;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
-public class TestInformationProvider extends ContentProvider {
+import com.android.launcher3.Utilities;
+import com.android.launcher3.util.ContentProviderProxy;
+
+public class TestInformationProvider extends ContentProviderProxy {
 
     private static final String TAG = "TestInformationProvider";
 
+    @Nullable
     @Override
-    public boolean onCreate() {
-        return true;
-    }
-
-    @Override
-    public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
-        return 0;
-    }
-
-    @Override
-    public int delete(Uri uri, String s, String[] strings) {
-        return 0;
-    }
-
-    @Override
-    public Uri insert(Uri uri, ContentValues contentValues) {
-        return null;
-    }
-
-    @Override
-    public String getType(Uri uri) {
-        return null;
-    }
-
-    @Override
-    public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
-        return null;
-    }
-
-    @Override
-    public Bundle call(String method, String arg, Bundle extras) {
+    public ProxyProvider getProxy(@NonNull Context context) {
         if (Utilities.isRunningInTestHarness()) {
-            TestInformationHandler handler = TestInformationHandler.newInstance(getContext());
-            handler.init(getContext());
+            return new ProxyProvider() {
+                @Nullable
+                @Override
+                public Bundle call(@NonNull String method, @Nullable String arg,
+                        @Nullable Bundle extras) {
+                    TestInformationHandler handler = TestInformationHandler.newInstance(context);
+                    handler.init(context);
 
-            Bundle response =  handler.call(method, arg, extras);
-            if (response == null) {
-                Log.e(TAG, "Couldn't handle method: " + method + "; current handler="
-                        + handler.getClass().getSimpleName());
-            }
-            return response;
+                    Bundle response = handler.call(method, arg, extras);
+                    if (response == null) {
+                        Log.e(TAG, "Couldn't handle method: " + method + "; current handler="
+                                + handler.getClass().getSimpleName());
+                    }
+                    return response;
+                }
+            };
         }
         return null;
     }
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 74a0966..4509bae 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.touch;
 
 import static com.android.app.animation.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.Flags.enableMouseInteractionChanges;
 import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.LauncherAnimUtils.TABLET_BOTTOM_SHEET_SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
@@ -33,6 +34,7 @@
 
 import android.animation.Animator.AnimatorListener;
 import android.animation.ValueAnimator;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 
 import com.android.launcher3.Launcher;
@@ -40,13 +42,11 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.contextualeducation.ContextualEduStatsManager;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.FlingBlockCheck;
 import com.android.launcher3.util.TouchController;
-import com.android.systemui.contextualeducation.GestureType;
 
 /**
  * TouchController for handling state changes
@@ -109,7 +109,9 @@
                 ignoreSlopWhenSettling = true;
             } else {
                 directionsToDetectScroll = getSwipeDirection();
-                if (directionsToDetectScroll == 0) {
+                boolean ignoreMouseScroll = ev.getSource() == InputDevice.SOURCE_MOUSE
+                        && enableMouseInteractionChanges();
+                if (directionsToDetectScroll == 0 || ignoreMouseScroll) {
                     mNoIntercept = true;
                     return false;
                 }
@@ -390,7 +392,6 @@
         } else {
             logReachedState(mToState);
         }
-        updateContextualEduStats(targetState);
     }
 
     protected void goToTargetState(LauncherState targetState) {
@@ -406,18 +407,6 @@
                 .setDuration(0).start();
     }
 
-    private void updateContextualEduStats(LauncherState targetState) {
-        if (targetState == OVERVIEW) {
-            ContextualEduStatsManager.INSTANCE.get(
-                    mLauncher).updateEduStats(mDetector.isTrackpadGesture(), GestureType.OVERVIEW);
-        } else if (targetState == ALL_APPS && !mDetector.isTrackpadGesture()) {
-            // Only update if it is touch gesture as trackpad gesture is not relevant for all apps
-            // which only provides keyboard education.
-            ContextualEduStatsManager.INSTANCE.get(
-                    mLauncher).updateEduStats(/* isTrackpadGesture= */ false, GestureType.ALL_APPS);
-        }
-    }
-
     private void logReachedState(LauncherState targetState) {
         if (mStartState == targetState) {
             return;
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index 107bcc1..2cc4909 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -207,6 +207,11 @@
             }
             config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE);
             config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE);
+            if (launcher.getDeviceProfile().isPhone) {
+                config.setInterpolator(ANIM_WORKSPACE_FADE, INSTANT);
+                config.setInterpolator(ANIM_HOTSEAT_FADE, INSTANT);
+                config.animFlags |= StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
+            }
         } else {
             if (config.isUserControlled()) {
                 config.setInterpolator(ANIM_DEPTH, Interpolators.reverse(BLUR_MANUAL));
@@ -248,6 +253,11 @@
             }
             config.setInterpolator(ANIM_WORKSPACE_SCALE, DECELERATED_EASE);
             config.setInterpolator(ANIM_DEPTH, DECELERATED_EASE);
+            if (launcher.getDeviceProfile().isPhone) {
+                config.setInterpolator(ANIM_WORKSPACE_FADE, FINAL_FRAME);
+                config.setInterpolator(ANIM_HOTSEAT_FADE, FINAL_FRAME);
+                config.animFlags |= StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
+            }
         } else {
             config.setInterpolator(ANIM_DEPTH,
                     config.isUserControlled() ? BLUR_MANUAL : BLUR_ATOMIC);
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 78709b8..381d17a 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -228,10 +228,9 @@
     private static void onClickPendingAppItem(View v, Launcher launcher, String packageName,
             boolean downloadStarted) {
         ItemInfo item = (ItemInfo) v.getTag();
-        CompletableFuture<SessionInfo> siFuture;
-        siFuture = CompletableFuture.supplyAsync(() ->
-                        InstallSessionHelper.INSTANCE.get(launcher)
-                                .getActiveSessionInfo(item.user, packageName),
+        CompletableFuture<SessionInfo> siFuture = CompletableFuture.supplyAsync(() ->
+                InstallSessionHelper.INSTANCE.get(launcher)
+                        .getActiveSessionInfo(item.user, packageName),
                 UI_HELPER_EXECUTOR);
         Consumer<SessionInfo> marketLaunchAction = sessionInfo -> {
             if (sessionInfo != null) {
@@ -245,8 +244,8 @@
                 }
             }
             // Fallback to using custom market intent.
-            Intent intent = ApiWrapper.INSTANCE.get(launcher).getAppMarketActivityIntent(
-                    packageName, Process.myUserHandle());
+            Intent intent = ApiWrapper.INSTANCE.get(launcher).getMarketSearchIntent(
+                    packageName, item.user);
             launcher.startActivitySafely(v, intent, item);
         };
 
@@ -358,9 +357,7 @@
         // Check for abandoned promise
         if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()
                 && (!Flags.enableSupportForArchiving() || !shortcut.isArchived())) {
-            String packageName = shortcut.getIntent().getComponent() != null
-                    ? shortcut.getIntent().getComponent().getPackageName()
-                    : shortcut.getIntent().getPackage();
+            String packageName = shortcut.getTargetPackage();
             if (!TextUtils.isEmpty(packageName)) {
                 onClickPendingAppItem(
                         v,
diff --git a/src/com/android/launcher3/touch/ItemLongClickListener.java b/src/com/android/launcher3/touch/ItemLongClickListener.java
index 89057a2..98ba8c7 100644
--- a/src/com/android/launcher3/touch/ItemLongClickListener.java
+++ b/src/com/android/launcher3/touch/ItemLongClickListener.java
@@ -188,7 +188,7 @@
         // Return early if an item is already being dragged (e.g. when long-pressing two shortcuts)
         if (launcher.getDragController().isDragging()) return false;
         // Return early if user is in the middle of selecting split-screen apps
-        if (FeatureFlags.enableSplitContextually() && launcher.isSplitSelectionActive()) {
+        if (launcher.isSplitSelectionActive()) {
             return false;
         }
 
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index 0ff10c2..d72e6f9 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -21,6 +21,7 @@
 import static android.view.MotionEvent.ACTION_POINTER_UP;
 import static android.view.MotionEvent.ACTION_UP;
 
+import static com.android.launcher3.Flags.enableMouseInteractionChanges;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_CLOSE_TAP_OUTSIDE;
@@ -31,6 +32,7 @@
 import android.graphics.Rect;
 import android.view.GestureDetector;
 import android.view.HapticFeedbackConstants;
+import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnTouchListener;
@@ -41,7 +43,6 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.testing.TestLogging;
@@ -193,6 +194,10 @@
 
     @Override
     public void onLongPress(MotionEvent event) {
+        if (event.getSource() == InputDevice.SOURCE_MOUSE && enableMouseInteractionChanges()) {
+            // Stop mouse long press events from showing the menu.
+            return;
+        }
         maybeShowMenu();
     }
 
@@ -207,7 +212,7 @@
                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                 mLauncher.getStatsLogManager().logger().log(LAUNCHER_WORKSPACE_LONGPRESS);
                 mLauncher.showDefaultOptions(mTouchDownPoint.x, mTouchDownPoint.y);
-                if (FeatureFlags.enableSplitContextually() && mLauncher.isSplitSelectionActive()) {
+                if (mLauncher.isSplitSelectionActive()) {
                     mLauncher.dismissSplitSelection(LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
                 }
             } else {
diff --git a/src/com/android/launcher3/util/ApiWrapper.java b/src/com/android/launcher3/util/ApiWrapper.java
index 467a7ec..0510d59 100644
--- a/src/com/android/launcher3/util/ApiWrapper.java
+++ b/src/com/android/launcher3/util/ApiWrapper.java
@@ -28,6 +28,7 @@
 import android.content.pm.ShortcutInfo;
 import android.graphics.drawable.ColorDrawable;
 import android.net.Uri;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArrayMap;
@@ -58,10 +59,13 @@
             LauncherAppComponent::getApiWrapper);
 
     protected final Context mContext;
+    private final String[] mLegacyMultiInstanceSupportedApps;
 
     @Inject
     public ApiWrapper(@ApplicationContext Context context) {
         mContext = context;
+        mLegacyMultiInstanceSupportedApps = context.getResources().getStringArray(
+                com.android.launcher3.R.array.config_appsSupportMultiInstancesSplit);
     }
 
     /**
@@ -120,6 +124,21 @@
      * Activity).
      */
     public Intent getAppMarketActivityIntent(String packageName, UserHandle user) {
+        return createMarketIntent(packageName);
+    }
+
+    /**
+     * Returns an intent which can be used to start a search for a package on app market
+     */
+    public Intent getMarketSearchIntent(String packageName, UserHandle user) {
+        // If we are search for the current user, just launch the market directly as the
+        // system won't have the installer details either
+        return  (Process.myUserHandle().equals(user))
+                ? createMarketIntent(packageName)
+                : getAppMarketActivityIntent(packageName, user);
+    }
+
+    private static Intent createMarketIntent(String packageName) {
         return new Intent(Intent.ACTION_VIEW)
                 .setData(new Uri.Builder()
                         .scheme("market")
@@ -141,12 +160,31 @@
     /**
      * Checks if an activity is flagged as non-resizeable.
      */
-    public boolean isNonResizeableActivity(LauncherActivityInfo lai) {
-        // Overridden in quickstep
+    public boolean isNonResizeableActivity(@NonNull LauncherActivityInfo lai) {
+        // Overridden in Quickstep
         return false;
     }
 
     /**
+     * Checks if an activity supports multi-instance.
+     */
+    public boolean supportsMultiInstance(@NonNull LauncherActivityInfo lai) {
+        // Check app multi-instance properties after V
+        if (!Utilities.ATLEAST_V) {
+            return false;
+        }
+
+        // Check the legacy hardcoded allowlist first
+        for (String pkg : mLegacyMultiInstanceSupportedApps) {
+            if (pkg.equals(lai.getComponentName().getPackageName())) {
+                return true;
+            }
+        }
+
+        // Overridden in Quickstep
+        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.
@@ -171,6 +209,20 @@
         return appInfo.sourceDir;
     }
 
+    /**
+     * Returns the round icon resource Id if defined by the app
+     */
+    public int getRoundIconRes(@NonNull ApplicationInfo appInfo) {
+        return 0;
+    }
+
+    /**
+     * Checks if the shortcut is using an icon with file or URI source
+     */
+    public boolean isFileDrawable(@NonNull ShortcutInfo shortcutInfo) {
+        return false;
+    }
+
     private static class NoopDrawable extends ColorDrawable {
         @Override
         public int getIntrinsicHeight() {
diff --git a/src/com/android/launcher3/util/BaseContext.kt b/src/com/android/launcher3/util/BaseContext.kt
new file mode 100644
index 0000000..8aa10f3
--- /dev/null
+++ b/src/com/android/launcher3/util/BaseContext.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.Context
+import android.view.ContextThemeWrapper
+import android.view.View
+import android.view.View.OnAttachStateChangeListener
+import android.view.ViewTreeObserver.OnWindowFocusChangeListener
+import android.view.ViewTreeObserver.OnWindowVisibilityChangeListener
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.Lifecycle.State.CREATED
+import androidx.lifecycle.Lifecycle.State.DESTROYED
+import androidx.lifecycle.Lifecycle.State.RESUMED
+import androidx.lifecycle.Lifecycle.State.STARTED
+import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.setViewTreeLifecycleOwner
+import androidx.savedstate.SavedStateRegistry
+import androidx.savedstate.SavedStateRegistryController
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
+import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener
+import com.android.launcher3.Utilities
+import com.android.launcher3.views.ActivityContext
+
+/**
+ * A context wrapper with lifecycle tracking based on the window events on the rootView of the
+ * [ActivityContext]
+ */
+abstract class BaseContext
+@JvmOverloads
+constructor(base: Context, themeResId: Int, private val destroyOnDetach: Boolean = true) :
+    ContextThemeWrapper(base, themeResId), ActivityContext {
+
+    private val listeners = mutableListOf<OnDeviceProfileChangeListener>()
+
+    private val savedStateRegistryController = SavedStateRegistryController.create(this)
+    private val lifecycleRegistry = LifecycleRegistry(this)
+    private val cleanupSet = WeakCleanupSet(this)
+
+    override val savedStateRegistry: SavedStateRegistry
+        get() = savedStateRegistryController.savedStateRegistry
+
+    override val lifecycle: Lifecycle
+        get() = lifecycleRegistry
+
+    private val viewCache = ViewCache()
+
+    init {
+        Executors.MAIN_EXECUTOR.execute {
+            savedStateRegistryController.performAttach()
+            savedStateRegistryController.performRestore(null)
+        }
+    }
+
+    override fun getOnDeviceProfileChangeListeners() = listeners
+
+    private val finishActions = RunnableList()
+
+    /** Called when the root view is created for this context */
+    fun onViewCreated() {
+        val view = rootView
+        val attachListener =
+            object : OnAttachStateChangeListener {
+
+                override fun onViewAttachedToWindow(view: View) {
+                    view.rootView.setViewTreeLifecycleOwner(this@BaseContext)
+                    view.rootView.setViewTreeSavedStateRegistryOwner(this@BaseContext)
+                    lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
+
+                    val treeObserver = view.viewTreeObserver
+
+                    val focusListener = OnWindowFocusChangeListener { updateState() }
+                    treeObserver.addOnWindowFocusChangeListener(focusListener)
+                    finishActions.add {
+                        treeObserver.removeOnWindowFocusChangeListener(focusListener)
+                    }
+
+                    if (Utilities.ATLEAST_V) {
+                        val visibilityListener = OnWindowVisibilityChangeListener { updateState() }
+                        treeObserver.addOnWindowVisibilityChangeListener(visibilityListener)
+                        finishActions.add {
+                            treeObserver.removeOnWindowVisibilityChangeListener(visibilityListener)
+                        }
+                    }
+                }
+
+                override fun onViewDetachedFromWindow(view: View) {
+                    if (destroyOnDetach) onViewDestroyed()
+                }
+            }
+        view.addOnAttachStateChangeListener(attachListener)
+        finishActions.add { view.removeOnAttachStateChangeListener(attachListener) }
+
+        if (view.isAttachedToWindow) attachListener.onViewAttachedToWindow(view)
+        updateState()
+    }
+
+    override fun getViewCache() = viewCache
+
+    override fun getOwnerCleanupSet() = cleanupSet
+
+    private fun updateState() {
+        if (lifecycleRegistry.currentState.isAtLeast(CREATED)) {
+            lifecycleRegistry.currentState =
+                if (rootView.windowVisibility != View.VISIBLE) CREATED
+                else (if (!rootView.hasWindowFocus()) STARTED else RESUMED)
+        }
+    }
+
+    fun onViewDestroyed() {
+        if (
+            !lifecycleRegistry.currentState.isAtLeast(CREATED) &&
+                lifecycleRegistry.currentState != DESTROYED
+        ) {
+            lifecycleRegistry.currentState = CREATED
+        }
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+        finishActions.executeAllAndDestroy()
+    }
+}
diff --git a/src/com/android/launcher3/util/ContentProviderProxy.kt b/src/com/android/launcher3/util/ContentProviderProxy.kt
new file mode 100644
index 0000000..db693db
--- /dev/null
+++ b/src/com/android/launcher3/util/ContentProviderProxy.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.Context
+import android.database.Cursor
+import android.net.Uri
+import android.os.Bundle
+
+/** Wrapper around [ContentProvider] which allows delegating all calls to an interface */
+abstract class ContentProviderProxy : ContentProvider() {
+
+    override fun onCreate() = true
+
+    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int =
+        checkGetProxy()?.delete(uri, selection, selectionArgs) ?: 0
+
+    /** Do not route this call through proxy as it doesn't generally require initializing objects */
+    override fun getType(uri: Uri): String? = null
+
+    override fun insert(uri: Uri, values: ContentValues?): Uri? =
+        checkGetProxy()?.insert(uri, values)
+
+    override fun query(
+        uri: Uri,
+        projection: Array<out String>?,
+        selection: String?,
+        selectionArgs: Array<out String>?,
+        sortOrder: String?,
+    ): Cursor? = checkGetProxy()?.query(uri, projection, selection, selectionArgs, sortOrder)
+
+    override fun update(
+        uri: Uri,
+        values: ContentValues?,
+        selection: String?,
+        selectionArgs: Array<out String>?,
+    ): Int = checkGetProxy()?.update(uri, values, selection, selectionArgs) ?: 0
+
+    override fun call(method: String, arg: String?, extras: Bundle?): Bundle? =
+        checkGetProxy()?.call(method, arg, extras)
+
+    private fun checkGetProxy(): ProxyProvider? = context?.let { getProxy(it) }
+
+    abstract fun getProxy(ctx: Context): ProxyProvider?
+
+    /** Interface for handling the actual content provider calls */
+    interface ProxyProvider {
+
+        fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0
+
+        fun insert(uri: Uri, values: ContentValues?): Uri? = null
+
+        fun query(
+            uri: Uri,
+            projection: Array<out String>?,
+            selection: String?,
+            selectionArgs: Array<out String>?,
+            sortOrder: String?,
+        ): Cursor? = null
+
+        fun update(
+            uri: Uri,
+            values: ContentValues?,
+            selection: String?,
+            selectionArgs: Array<out String>?,
+        ): Int = 0
+
+        fun call(method: String, arg: String?, extras: Bundle?): Bundle? = null
+    }
+}
diff --git a/src/com/android/launcher3/util/DaggerSingletonObject.java b/src/com/android/launcher3/util/DaggerSingletonObject.java
index a245761..a4bd30a 100644
--- a/src/com/android/launcher3/util/DaggerSingletonObject.java
+++ b/src/com/android/launcher3/util/DaggerSingletonObject.java
@@ -24,8 +24,7 @@
 import java.util.function.Function;
 
 /**
- * A class to provide DaggerSingleton objects in a traditional way for
- * {@link MainThreadInitializedObject}.
+ * A class to provide DaggerSingleton objects in a traditional way.
  * We should delete this class at the end and use @Inject to get dagger provided singletons.
  */
 
diff --git a/src/com/android/launcher3/util/DaggerSingletonTracker.java b/src/com/android/launcher3/util/DaggerSingletonTracker.java
index b7a88db..34b3760 100644
--- a/src/com/android/launcher3/util/DaggerSingletonTracker.java
+++ b/src/com/android/launcher3/util/DaggerSingletonTracker.java
@@ -45,7 +45,7 @@
      * Adds the SafeCloseable Singletons to the mLauncherAppSingletons list.
      * This helps to track the singletons and close them appropriately.
      * See {@link DaggerSingletonTracker#close()} and
-     * {@link MainThreadInitializedObject.SandboxContext#onDestroy()}
+     * {@link SandboxContext#onDestroy()}
      */
     public void addCloseable(SafeCloseable closeable) {
         MAIN_EXECUTOR.execute(() -> {
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 26912eb..52f8887 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -18,6 +18,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
+import static com.android.launcher3.Flags.enableOverviewOnConnectedDisplays;
 import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
 import static com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE;
 import static com.android.launcher3.InvariantDeviceProfile.TYPE_TABLET;
@@ -42,9 +43,11 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.Display;
 
 import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
 import androidx.annotation.VisibleForTesting;
@@ -53,24 +56,34 @@
 import com.android.launcher3.LauncherPrefChangeListener;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.dagger.ApplicationContext;
+import com.android.launcher3.dagger.LauncherAppComponent;
+import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.util.window.CachedDisplayInfo;
 import com.android.launcher3.util.window.WindowManagerProxy;
+import com.android.launcher3.util.window.WindowManagerProxy.DesktopVisibilityListener;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.StringJoiner;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import javax.inject.Inject;
 
 /**
  * Utility class to cache properties of default display to avoid a system RPC on every call.
  */
 @SuppressLint("NewApi")
-public class DisplayController implements ComponentCallbacks, SafeCloseable {
+@LauncherAppSingleton
+public class DisplayController implements DesktopVisibilityListener {
 
     private static final String TAG = "DisplayController";
     private static final boolean DEBUG = false;
@@ -80,8 +93,8 @@
     // TODO(b/254119092) remove all logs with this tag
     public static final String TASKBAR_NOT_DESTROYED_TAG = "b/254119092";
 
-    public static final MainThreadInitializedObject<DisplayController> INSTANCE =
-            new MainThreadInitializedObject<>(DisplayController::new);
+    public static final DaggerSingletonObject<DisplayController> INSTANCE =
+            new DaggerSingletonObject<>(LauncherAppComponent::getDisplayController);
 
     public static final int CHANGE_ACTIVE_SCREEN = 1 << 0;
     public static final int CHANGE_ROTATION = 1 << 1;
@@ -90,74 +103,105 @@
     public static final int CHANGE_NAVIGATION_MODE = 1 << 4;
     public static final int CHANGE_TASKBAR_PINNING = 1 << 5;
     public static final int CHANGE_DESKTOP_MODE = 1 << 6;
+    public static final int CHANGE_SHOW_LOCKED_TASKBAR = 1 << 7;
 
     public static final int CHANGE_ALL = CHANGE_ACTIVE_SCREEN | CHANGE_ROTATION
             | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS | CHANGE_NAVIGATION_MODE
-            | CHANGE_TASKBAR_PINNING | CHANGE_DESKTOP_MODE;
+            | CHANGE_TASKBAR_PINNING | CHANGE_DESKTOP_MODE | CHANGE_SHOW_LOCKED_TASKBAR;
 
     private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
     private static final String TARGET_OVERLAY_PACKAGE = "android";
 
-    private final Context mContext;
-    private final DisplayManager mDM;
+    private final WindowManagerProxy mWMProxy;
 
-    // Null for SDK < S
-    private final Context mWindowContext;
+    private final @ApplicationContext Context mAppContext;
 
     // The callback in this listener updates DeviceProfile, which other listeners might depend on
     private DisplayInfoChangeListener mPriorityListener;
-    private final ArrayList<DisplayInfoChangeListener> mListeners = new ArrayList<>();
+
+    private final SparseArray<PerDisplayInfo> mPerDisplayInfo =
+            new SparseArray<>();
 
     // We will register broadcast receiver on main thread to ensure not missing changes on
     // TARGET_OVERLAY_PACKAGE and ACTION_OVERLAY_CHANGED.
-    private final SimpleBroadcastReceiver mReceiver =
-            new SimpleBroadcastReceiver(MAIN_EXECUTOR, this::onIntent);
+    private final SimpleBroadcastReceiver mReceiver;
 
-    private Info mInfo;
     private boolean mDestroyed = false;
 
-    private LauncherPrefChangeListener mTaskbarPinningPreferenceChangeListener;
-
-    @VisibleForTesting
-    protected DisplayController(Context context) {
-        mContext = context;
-        mDM = context.getSystemService(DisplayManager.class);
+    @Inject
+    protected DisplayController(@ApplicationContext Context context,
+            WindowManagerProxy wmProxy,
+            LauncherPrefs prefs,
+            DaggerSingletonTracker lifecycle) {
+        mAppContext = context;
+        mWMProxy = wmProxy;
 
         if (enableTaskbarPinning()) {
-            attachTaskbarPinningSharedPreferenceChangeListener(mContext);
+            LauncherPrefChangeListener prefListener = key -> {
+                Info info = getInfo();
+                boolean isTaskbarPinningChanged = TASKBAR_PINNING_KEY.equals(key)
+                        && info.mIsTaskbarPinned != prefs.get(TASKBAR_PINNING);
+                boolean isTaskbarPinningDesktopModeChanged =
+                        TASKBAR_PINNING_DESKTOP_MODE_KEY.equals(key)
+                                && info.mIsTaskbarPinnedInDesktopMode != prefs.get(
+                                TASKBAR_PINNING_IN_DESKTOP_MODE);
+                if (isTaskbarPinningChanged || isTaskbarPinningDesktopModeChanged) {
+                    notifyConfigChange(DEFAULT_DISPLAY);
+                }
+            };
+
+            prefs.addListener(prefListener, TASKBAR_PINNING);
+            prefs.addListener(prefListener, TASKBAR_PINNING_IN_DESKTOP_MODE);
+            lifecycle.addCloseable(() -> prefs.removeListener(
+                        prefListener, TASKBAR_PINNING, TASKBAR_PINNING_IN_DESKTOP_MODE));
         }
 
-        Display display = mDM.getDisplay(DEFAULT_DISPLAY);
-        mWindowContext = mContext.createWindowContext(display, TYPE_APPLICATION, null);
-        mWindowContext.registerComponentCallbacks(this);
+        DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+        Display defaultDisplay = displayManager.getDisplay(DEFAULT_DISPLAY);
+        PerDisplayInfo defaultPerDisplayInfo = getOrCreatePerDisplayInfo(defaultDisplay);
 
         // Initialize navigation mode change listener
-        mReceiver.registerPkgActions(mContext, TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED);
+        mReceiver = new SimpleBroadcastReceiver(context, MAIN_EXECUTOR, this::onIntent);
+        mReceiver.registerPkgActions(TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED);
 
-        WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(context);
-        mInfo = new Info(mWindowContext, wmProxy,
-                wmProxy.estimateInternalDisplayBounds(mWindowContext));
-        FileLog.i(TAG, "(CTOR) perDisplayBounds: " + mInfo.mPerDisplayBounds);
-    }
+        wmProxy.registerDesktopVisibilityListener(this);
+        FileLog.i(TAG, "(CTOR) perDisplayBounds: "
+                + defaultPerDisplayInfo.mInfo.mPerDisplayBounds);
 
-    private void attachTaskbarPinningSharedPreferenceChangeListener(Context context) {
-        mTaskbarPinningPreferenceChangeListener = key -> {
-            LauncherPrefs prefs = LauncherPrefs.get(mContext);
-            boolean isTaskbarPinningChanged = TASKBAR_PINNING_KEY.equals(key)
-                    && mInfo.mIsTaskbarPinned != prefs.get(TASKBAR_PINNING);
-            boolean isTaskbarPinningDesktopModeChanged =
-                    TASKBAR_PINNING_DESKTOP_MODE_KEY.equals(key)
-                            && mInfo.mIsTaskbarPinnedInDesktopMode != prefs.get(
-                            TASKBAR_PINNING_IN_DESKTOP_MODE);
-            if (isTaskbarPinningChanged || isTaskbarPinningDesktopModeChanged) {
-                notifyConfigChange();
-            }
-        };
+        if (enableOverviewOnConnectedDisplays()) {
+            final DisplayManager.DisplayListener displayListener =
+                    new DisplayManager.DisplayListener() {
+                        @Override
+                        public void onDisplayAdded(int displayId) {
+                            getOrCreatePerDisplayInfo(displayManager.getDisplay(displayId));
+                        }
 
-        LauncherPrefs.get(context).addListener(
-                mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING);
-        LauncherPrefs.get(context).addListener(
-                mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING_IN_DESKTOP_MODE);
+                        @Override
+                        public void onDisplayChanged(int displayId) {
+                        }
+
+                        @Override
+                        public void onDisplayRemoved(int displayId) {
+                            removePerDisplayInfo(displayId);
+                        }
+                    };
+            displayManager.registerDisplayListener(displayListener, MAIN_EXECUTOR.getHandler());
+            lifecycle.addCloseable(() -> {
+                displayManager.unregisterDisplayListener(displayListener);
+            });
+            // Add any PerDisplayInfos for already-connected displays.
+            Arrays.stream(displayManager.getDisplays())
+                    .forEach((it) ->
+                            getOrCreatePerDisplayInfo(
+                                    displayManager.getDisplay(it.getDisplayId())));
+        }
+
+        lifecycle.addCloseable(() -> {
+            mDestroyed = true;
+            defaultPerDisplayInfo.cleanup();
+            mReceiver.unregisterReceiverSafely();
+            wmProxy.unregisterDesktopVisibilityListener(this);
+        });
     }
 
     /**
@@ -200,27 +244,30 @@
     }
 
     /**
+     * Returns whether the taskbar is pinned in gesture navigation mode.
+     */
+    public static boolean isInDesktopMode(Context context) {
+        return INSTANCE.get(context).getInfo().isInDesktopMode();
+    }
+
+    /**
      * Returns whether the taskbar is forced to be pinned when home is visible.
      */
     public static boolean showLockedTaskbarOnHome(Context context) {
         return INSTANCE.get(context).getInfo().showLockedTaskbarOnHome();
     }
 
+    /**
+     * Returns whether desktop taskbar (pinned taskbar that shows desktop tasks) is to be used
+     * on the display because the display is a freeform display.
+     */
+    public static boolean showDesktopTaskbarForFreeformDisplay(Context context) {
+        return INSTANCE.get(context).getInfo().showDesktopTaskbarForFreeformDisplay();
+    }
+
     @Override
-    public void close() {
-        mDestroyed = true;
-        if (enableTaskbarPinning()) {
-            LauncherPrefs.get(mContext).removeListener(
-                    mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING);
-            LauncherPrefs.get(mContext).removeListener(
-                    mTaskbarPinningPreferenceChangeListener, TASKBAR_PINNING_IN_DESKTOP_MODE);
-        }
-        if (mWindowContext != null) {
-            mWindowContext.unregisterComponentCallbacks(this);
-        } else {
-            // TODO: unregister broadcast receiver
-        }
-        mReceiver.unregisterReceiverSafely(mContext);
+    public void onIsInDesktopModeChanged(int displayId, boolean isInDesktopModeAndNotInOverview) {
+        notifyConfigChange(displayId);
     }
 
     /**
@@ -243,59 +290,88 @@
         }
         if (ACTION_OVERLAY_CHANGED.equals(intent.getAction())) {
             Log.d(TAG, "Overlay changed, notifying listeners");
-            notifyConfigChange();
+            notifyConfigChange(DEFAULT_DISPLAY);
         }
     }
 
+    @VisibleForTesting
+    public void onConfigurationChanged(Configuration config) {
+        onConfigurationChanged(config, DEFAULT_DISPLAY);
+    }
+
     @UiThread
-    @Override
-    public final void onConfigurationChanged(Configuration config) {
+    private void onConfigurationChanged(Configuration config, int displayId) {
         Log.d(TASKBAR_NOT_DESTROYED_TAG, "DisplayController#onConfigurationChanged: " + config);
-        if (config.densityDpi != mInfo.densityDpi
-                || config.fontScale != mInfo.fontScale
-                || !mInfo.mScreenSizeDp.equals(
-                        new PortraitSize(config.screenHeightDp, config.screenWidthDp))
-                || mWindowContext.getDisplay().getRotation() != mInfo.rotation
-                || WindowManagerProxy.INSTANCE.get(mContext).showLockedTaskbarOnHome(mWindowContext)
-                        != mInfo.showLockedTaskbarOnHome()) {
-            notifyConfigChange();
+        PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId);
+        Context windowContext = perDisplayInfo.mWindowContext;
+        Info info = perDisplayInfo.mInfo;
+        if (config.densityDpi != info.densityDpi
+                || config.fontScale != info.fontScale
+                || !info.mScreenSizeDp.equals(
+                    new PortraitSize(config.screenHeightDp, config.screenWidthDp))
+                || windowContext.getDisplay().getRotation() != info.rotation
+                || mWMProxy.showLockedTaskbarOnHome(windowContext)
+                != info.showLockedTaskbarOnHome()
+                || mWMProxy.showDesktopTaskbarForFreeformDisplay(windowContext)
+                != info.showDesktopTaskbarForFreeformDisplay()) {
+            notifyConfigChange(displayId);
         }
     }
 
-    @Override
-    public final void onLowMemory() { }
-
     public void setPriorityListener(DisplayInfoChangeListener listener) {
         mPriorityListener = listener;
     }
 
     public void addChangeListener(DisplayInfoChangeListener listener) {
-        mListeners.add(listener);
+        addChangeListenerForDisplay(listener, DEFAULT_DISPLAY);
     }
 
     public void removeChangeListener(DisplayInfoChangeListener listener) {
-        mListeners.remove(listener);
+        removeChangeListenerForDisplay(listener, DEFAULT_DISPLAY);
+    }
+
+    public void addChangeListenerForDisplay(DisplayInfoChangeListener listener, int displayId) {
+        PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId);
+        if (perDisplayInfo != null) {
+            perDisplayInfo.addListener(listener);
+        }
+    }
+
+    public void removeChangeListenerForDisplay(DisplayInfoChangeListener listener, int displayId) {
+        PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId);
+        if (perDisplayInfo != null) {
+            perDisplayInfo.removeListener(listener);
+        }
     }
 
     public Info getInfo() {
-        return mInfo;
+        return mPerDisplayInfo.get(DEFAULT_DISPLAY).mInfo;
+    }
+
+    public @Nullable Info getInfoForDisplay(int displayId) {
+        if (enableOverviewOnConnectedDisplays()) {
+            PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId);
+            if (perDisplayInfo != null) {
+                return perDisplayInfo.mInfo;
+            } else {
+                return null;
+            }
+        } else {
+            return getInfo();
+        }
     }
 
     @AnyThread
     public void notifyConfigChange() {
-        WindowManagerProxy wmProxy = WindowManagerProxy.INSTANCE.get(mContext);
-        Info oldInfo = mInfo;
+        notifyConfigChange(DEFAULT_DISPLAY);
+    }
 
-        Context displayInfoContext = mWindowContext;
-        Info newInfo = new Info(displayInfoContext, wmProxy, oldInfo.mPerDisplayBounds);
+    @AnyThread
+    public void notifyConfigChange(int displayId) {
+        notifyConfigChangeForDisplay(displayId);
+    }
 
-        if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale
-                || newInfo.getNavigationMode() != oldInfo.getNavigationMode()) {
-            // Cache may not be valid anymore, recreate without cache
-            newInfo = new Info(displayInfoContext, wmProxy,
-                    wmProxy.estimateInternalDisplayBounds(displayInfoContext));
-        }
-
+    private int calculateChange(Info oldInfo, Info newInfo) {
         int change = 0;
         if (!newInfo.normalizedDisplayInfo.equals(oldInfo.normalizedDisplayInfo)) {
             change |= CHANGE_ACTIVE_SCREEN;
@@ -317,34 +393,82 @@
         }
         if ((newInfo.mIsTaskbarPinned != oldInfo.mIsTaskbarPinned)
                 || (newInfo.mIsTaskbarPinnedInDesktopMode
-                    != oldInfo.mIsTaskbarPinnedInDesktopMode)
+                != oldInfo.mIsTaskbarPinnedInDesktopMode)
                 || newInfo.isPinnedTaskbar() != oldInfo.isPinnedTaskbar()) {
             change |= CHANGE_TASKBAR_PINNING;
         }
         if (newInfo.mIsInDesktopMode != oldInfo.mIsInDesktopMode) {
             change |= CHANGE_DESKTOP_MODE;
         }
+        if (newInfo.mShowLockedTaskbarOnHome != oldInfo.mShowLockedTaskbarOnHome) {
+            change |= CHANGE_SHOW_LOCKED_TASKBAR;
+        }
 
         if (DEBUG) {
             Log.d(TAG, "handleInfoChange - change: " + getChangeFlagsString(change));
         }
+        return change;
+    }
 
-        if (change != 0) {
-            mInfo = newInfo;
-            final int flags = change;
-            MAIN_EXECUTOR.execute(() -> notifyChange(displayInfoContext, flags));
+    private Info getNewInfo(Info oldInfo, Context displayInfoContext) {
+        Info newInfo = new Info(displayInfoContext, mWMProxy, oldInfo.mPerDisplayBounds);
+
+        if (newInfo.densityDpi != oldInfo.densityDpi || newInfo.fontScale != oldInfo.fontScale
+                || newInfo.getNavigationMode() != oldInfo.getNavigationMode()) {
+            // Cache may not be valid anymore, recreate without cache
+            newInfo = new Info(displayInfoContext, mWMProxy,
+                    mWMProxy.estimateInternalDisplayBounds(displayInfoContext));
+        }
+        return newInfo;
+    }
+
+    @AnyThread
+    public void notifyConfigChangeForDisplay(int displayId) {
+        PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId);
+        if (perDisplayInfo == null) return;
+        Info oldInfo = perDisplayInfo.mInfo;
+        final Info newInfo = getNewInfo(oldInfo, perDisplayInfo.mWindowContext);
+        final int flags = calculateChange(oldInfo, newInfo);
+        if (flags != 0) {
+            MAIN_EXECUTOR.execute(() -> {
+                perDisplayInfo.mInfo = newInfo;
+                if (displayId == DEFAULT_DISPLAY && mPriorityListener != null) {
+                    mPriorityListener.onDisplayInfoChanged(perDisplayInfo.mWindowContext, newInfo,
+                            flags);
+                }
+                perDisplayInfo.notifyListeners(newInfo, flags);
+            });
         }
     }
 
-    private void notifyChange(Context context, int flags) {
-        if (mPriorityListener != null) {
-            mPriorityListener.onDisplayInfoChanged(context, mInfo, flags);
+    private PerDisplayInfo getOrCreatePerDisplayInfo(Display display) {
+        int displayId = display.getDisplayId();
+        PerDisplayInfo perDisplayInfo = mPerDisplayInfo.get(displayId);
+        if (perDisplayInfo != null) {
+            return perDisplayInfo;
         }
+        if (DEBUG) {
+            Log.d(TAG,
+                    String.format("getOrCreatePerDisplayInfo - no cached value found for %d",
+                            displayId));
+        }
+        Context windowContext = mAppContext.createWindowContext(display, TYPE_APPLICATION, null);
+        Info info = new Info(windowContext, mWMProxy,
+                mWMProxy.estimateInternalDisplayBounds(windowContext));
+        perDisplayInfo = new PerDisplayInfo(displayId, windowContext, info);
+        mPerDisplayInfo.put(displayId, perDisplayInfo);
+        return perDisplayInfo;
+    }
 
-        int count = mListeners.size();
-        for (int i = 0; i < count; i++) {
-            mListeners.get(i).onDisplayInfoChanged(context, mInfo, flags);
-        }
+    /**
+     * Clean up resources for the given display id.
+     * @param displayId The display id
+     */
+    void removePerDisplayInfo(int displayId) {
+        PerDisplayInfo info = mPerDisplayInfo.get(displayId);
+        if (info == null) return;
+        info.cleanup();
+        mPerDisplayInfo.remove(displayId);
     }
 
     public static class Info {
@@ -375,6 +499,8 @@
         private final boolean mShowLockedTaskbarOnHome;
         private final boolean mIsHomeVisible;
 
+        private final boolean mShowDesktopTaskbarForFreeformDisplay;
+
         public Info(Context displayInfoContext) {
             /* don't need system overrides for external displays */
             this(displayInfoContext, new WindowManagerProxy(), new ArrayMap<>());
@@ -435,8 +561,10 @@
             mIsTaskbarPinned = LauncherPrefs.get(displayInfoContext).get(TASKBAR_PINNING);
             mIsTaskbarPinnedInDesktopMode = LauncherPrefs.get(displayInfoContext).get(
                     TASKBAR_PINNING_IN_DESKTOP_MODE);
-            mIsInDesktopMode = wmProxy.isInDesktopMode();
+            mIsInDesktopMode = wmProxy.isInDesktopMode(DEFAULT_DISPLAY);
             mShowLockedTaskbarOnHome = wmProxy.showLockedTaskbarOnHome(displayInfoContext);
+            mShowDesktopTaskbarForFreeformDisplay = wmProxy.showDesktopTaskbarForFreeformDisplay(
+                    displayInfoContext);
             mIsHomeVisible = wmProxy.isHomeVisible(displayInfoContext);
         }
 
@@ -454,6 +582,11 @@
                 return sTransientTaskbarStatusForTests;
             }
             if (enableTaskbarPinning()) {
+                // If "freeform" display taskbar is enabled, ensure the taskbar is pinned.
+                if (mShowDesktopTaskbarForFreeformDisplay) {
+                    return false;
+                }
+
                 // If Launcher is visible on the freeform display, ensure the taskbar is pinned.
                 if (mShowLockedTaskbarOnHome && mIsHomeVisible) {
                     return false;
@@ -474,6 +607,13 @@
         }
 
         /**
+         * Returns whether the taskbar is in desktop mode.
+         */
+        public boolean isInDesktopMode() {
+            return mIsInDesktopMode;
+        }
+
+        /**
          * Returns {@code true} if the bounds represent a tablet.
          */
         public boolean isTablet(WindowBounds bounds) {
@@ -532,6 +672,14 @@
         public boolean showLockedTaskbarOnHome() {
             return mShowLockedTaskbarOnHome;
         }
+
+        /**
+         * Returns whether the taskbar should be pinned, and showing desktop tasks, because the
+         * display is a "freeform" display.
+         */
+        public boolean showDesktopTaskbarForFreeformDisplay() {
+            return mShowDesktopTaskbarForFreeformDisplay;
+        }
     }
 
     /**
@@ -547,6 +695,7 @@
         appendFlag(result, change, CHANGE_NAVIGATION_MODE, "CHANGE_NAVIGATION_MODE");
         appendFlag(result, change, CHANGE_TASKBAR_PINNING, "CHANGE_TASKBAR_VARIANT");
         appendFlag(result, change, CHANGE_DESKTOP_MODE, "CHANGE_DESKTOP_MODE");
+        appendFlag(result, change, CHANGE_SHOW_LOCKED_TASKBAR, "CHANGE_SHOW_LOCKED_TASKBAR");
         return result.toString();
     }
 
@@ -554,20 +703,29 @@
      * Dumps the current state information
      */
     public void dump(PrintWriter pw) {
-        Info info = mInfo;
-        pw.println("DisplayController.Info:");
-        pw.println("  normalizedDisplayInfo=" + info.normalizedDisplayInfo);
-        pw.println("  rotation=" + info.rotation);
-        pw.println("  fontScale=" + info.fontScale);
-        pw.println("  densityDpi=" + info.densityDpi);
-        pw.println("  navigationMode=" + info.getNavigationMode().name());
-        pw.println("  isTaskbarPinned=" + info.mIsTaskbarPinned);
-        pw.println("  isTaskbarPinnedInDesktopMode=" + info.mIsTaskbarPinnedInDesktopMode);
-        pw.println("  isInDesktopMode=" + info.mIsInDesktopMode);
-        pw.println("  currentSize=" + info.currentSize);
-        info.mPerDisplayBounds.forEach((key, value) -> pw.println(
-                "  perDisplayBounds - " + key + ": " + value));
-        pw.println("  isTransientTaskbar=" + info.isTransientTaskbar());
+        int count = mPerDisplayInfo.size();
+        for (int i = 0; i < count; ++i) {
+            int displayId = mPerDisplayInfo.keyAt(i);
+            Info info = getInfoForDisplay(displayId);
+            if (info == null) {
+                continue;
+            }
+            pw.println(String.format(Locale.ENGLISH, "DisplayController.Info (displayId=%d):",
+                    displayId));
+            pw.println("  normalizedDisplayInfo=" + info.normalizedDisplayInfo);
+            pw.println("  rotation=" + info.rotation);
+            pw.println("  fontScale=" + info.fontScale);
+            pw.println("  densityDpi=" + info.densityDpi);
+            pw.println("  navigationMode=" + info.getNavigationMode().name());
+            pw.println("  isTaskbarPinned=" + info.mIsTaskbarPinned);
+            pw.println("  isTaskbarPinnedInDesktopMode=" + info.mIsTaskbarPinnedInDesktopMode);
+            pw.println("  isInDesktopMode=" + info.mIsInDesktopMode);
+            pw.println("  showLockedTaskbarOnHome=" + info.showLockedTaskbarOnHome());
+            pw.println("  currentSize=" + info.currentSize);
+            info.mPerDisplayBounds.forEach((key, value) -> pw.println(
+                    "  perDisplayBounds - " + key + ": " + value));
+            pw.println("  isTransientTaskbar=" + info.isTransientTaskbar());
+        }
     }
 
     /**
@@ -595,4 +753,47 @@
         }
     }
 
+    private class PerDisplayInfo implements ComponentCallbacks {
+        final int mDisplayId;
+        final CopyOnWriteArrayList<DisplayInfoChangeListener> mListeners =
+                new CopyOnWriteArrayList<>();
+        final Context mWindowContext;
+        Info mInfo;
+
+        PerDisplayInfo(int displayId, Context windowContext, Info info) {
+            this.mDisplayId = displayId;
+            this.mWindowContext = windowContext;
+            this.mInfo = info;
+            windowContext.registerComponentCallbacks(this);
+        }
+
+        void addListener(DisplayInfoChangeListener listener) {
+            mListeners.add(listener);
+        }
+
+        void removeListener(DisplayInfoChangeListener listener) {
+            mListeners.remove(listener);
+        }
+
+        void notifyListeners(Info info, int flags) {
+            int count = mListeners.size();
+            for (int i = 0; i < count; ++i) {
+                mListeners.get(i).onDisplayInfoChanged(mWindowContext, info, flags);
+            }
+        }
+
+        @Override
+        public void onConfigurationChanged(@NonNull Configuration newConfig) {
+            DisplayController.this.onConfigurationChanged(newConfig, mDisplayId);
+        }
+
+        @Override
+        public void onLowMemory() {}
+
+        void cleanup() {
+            mWindowContext.unregisterComponentCallbacks(this);
+            mListeners.clear();
+        }
+    }
+
 }
diff --git a/src/com/android/launcher3/util/Executors.java b/src/com/android/launcher3/util/Executors.java
index c622b71..296fc8a 100644
--- a/src/com/android/launcher3/util/Executors.java
+++ b/src/com/android/launcher3/util/Executors.java
@@ -16,8 +16,8 @@
 package com.android.launcher3.util;
 
 import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
 
-import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Process;
 
@@ -51,21 +51,20 @@
     /**
      * An {@link LooperExecutor} to be used with async task where order is important.
      */
-    public static final LooperExecutor ORDERED_BG_EXECUTOR = new LooperExecutor(
-            createAndStartNewLooper("BackgroundExecutor", THREAD_PRIORITY_BACKGROUND));
+    public static final LooperExecutor ORDERED_BG_EXECUTOR =
+            new LooperExecutor("BackgroundExecutor", THREAD_PRIORITY_BACKGROUND);
 
     /**
      * Returns the executor for running tasks on the main thread.
      */
     public static final LooperExecutor MAIN_EXECUTOR =
-            new LooperExecutor(Looper.getMainLooper());
+            new LooperExecutor(Looper.getMainLooper(), THREAD_PRIORITY_FOREGROUND);
 
     /**
      * A background executor for using time sensitive actions where user is waiting for response.
      */
     public static final LooperExecutor UI_HELPER_EXECUTOR =
-            new LooperExecutor(
-                    createAndStartNewLooper("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND));
+            new LooperExecutor("UiThreadHelper", Process.THREAD_PRIORITY_FOREGROUND);
 
 
     /** A background executor to preinflate views. */
@@ -75,26 +74,9 @@
                             "preinflate-allapps-icons", THREAD_PRIORITY_BACKGROUND));
 
     /**
-     * Utility method to get a started handler thread statically
-     */
-    public static Looper createAndStartNewLooper(String name) {
-        return createAndStartNewLooper(name, Process.THREAD_PRIORITY_DEFAULT);
-    }
-
-    /**
-     * Utility method to get a started handler thread statically with the provided priority
-     */
-    public static Looper createAndStartNewLooper(String name, int priority) {
-        HandlerThread thread = new HandlerThread(name, priority);
-        thread.start();
-        return thread.getLooper();
-    }
-
-    /**
      * Executor used for running Launcher model related tasks (eg loading icons or updated db)
      */
-    public static final LooperExecutor MODEL_EXECUTOR =
-            new LooperExecutor(createAndStartNewLooper("launcher-loader"));
+    public static final LooperExecutor MODEL_EXECUTOR = new LooperExecutor("launcher-loader");
 
     /**
      * Returns and caches a single thread executor for a given package.
@@ -102,9 +84,7 @@
      * @param packageName Package associated with the executor.
      */
     public static LooperExecutor getPackageExecutor(String packageName) {
-        return PACKAGE_EXECUTORS.computeIfAbsent(
-                packageName, p -> new LooperExecutor(
-                        createAndStartNewLooper(p, Process.THREAD_PRIORITY_DEFAULT)));
+        return PACKAGE_EXECUTORS.computeIfAbsent(packageName, LooperExecutor::new);
     }
 
     /**
diff --git a/src/com/android/launcher3/util/IntSparseArrayMap.java b/src/com/android/launcher3/util/IntSparseArrayMap.java
index 9d5391b..70f74e3 100644
--- a/src/com/android/launcher3/util/IntSparseArrayMap.java
+++ b/src/com/android/launcher3/util/IntSparseArrayMap.java
@@ -19,6 +19,8 @@
 import android.util.SparseArray;
 
 import java.util.Iterator;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 
 /**
  * Extension of {@link SparseArray} with some utility methods.
@@ -43,6 +45,10 @@
         return new ValueIterator();
     }
 
+    public Stream<E> stream() {
+        return StreamSupport.stream(spliterator(), false);
+    }
+
     @Thunk class ValueIterator implements Iterator<E> {
 
         private int mNextIndex = 0;
diff --git a/src/com/android/launcher3/util/KFloatProperty.kt b/src/com/android/launcher3/util/KFloatProperty.kt
new file mode 100644
index 0000000..5579241
--- /dev/null
+++ b/src/com/android/launcher3/util/KFloatProperty.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.util.FloatProperty
+import kotlin.reflect.KMutableProperty1
+
+/** Maps any Kotlin mutable property (var) to [FloatProperty]. */
+class KFloatProperty<T>(private val kProperty: KMutableProperty1<T, Float>) :
+    FloatProperty<T>(kProperty.name) {
+    override fun get(target: T) = kProperty.get(target)
+
+    override fun setValue(target: T, value: Float) {
+        kProperty.set(target, value)
+    }
+}
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
deleted file mode 100644
index 02779ce..0000000
--- a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2021 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.graphics.drawable.Drawable;
-import android.view.View;
-
-import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.apppairs.AppPairIcon;
-import com.android.launcher3.folder.Folder;
-import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.graphics.PreloadIconDrawable;
-import com.android.launcher3.model.data.AppPairInfo;
-import com.android.launcher3.model.data.FolderInfo;
-import com.android.launcher3.model.data.ItemInfo;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.model.data.WorkspaceItemInfo;
-import com.android.launcher3.views.ActivityContext;
-import com.android.launcher3.widget.PendingAppWidgetHostView;
-
-import java.util.HashSet;
-import java.util.List;
-
-/**
- * Interface representing a container which can bind Launcher items with some utility methods
- */
-public interface LauncherBindableItemsContainer {
-
-    /**
-     * Called to update workspace items as a result of
-     * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindWorkspaceItemsChanged(List)}
-     */
-    default void updateWorkspaceItems(List<WorkspaceItemInfo> shortcuts, ActivityContext context) {
-        final HashSet<WorkspaceItemInfo> updates = new HashSet<>(shortcuts);
-        ItemOperator op = (info, v) -> {
-            if (v instanceof BubbleTextView && updates.contains(info)) {
-                WorkspaceItemInfo si = (WorkspaceItemInfo) info;
-                BubbleTextView shortcut = (BubbleTextView) v;
-                Drawable oldIcon = shortcut.getIcon();
-                boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
-                        && ((PreloadIconDrawable) oldIcon).hasNotCompleted();
-                shortcut.applyFromWorkspaceItem(
-                        si,
-                        si.isPromise() != oldPromiseState
-                                && oldIcon instanceof PreloadIconDrawable
-                                ? (PreloadIconDrawable) oldIcon
-                                : null);
-            } else if (info instanceof FolderInfo && v instanceof FolderIcon) {
-                ((FolderIcon) v).updatePreviewItems(updates::contains);
-            } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
-                appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
-            }
-
-            // Iterate all items
-            return false;
-        };
-
-        mapOverItems(op);
-        Folder openFolder = Folder.getOpen(context);
-        if (openFolder != null) {
-            openFolder.iterateOverItems(op);
-        }
-    }
-
-    /**
-     * Called to update restored items as a result of
-     * {@link com.android.launcher3.model.BgDataModel.Callbacks#bindRestoreItemsChange(HashSet)}}
-     */
-    default void updateRestoreItems(final HashSet<ItemInfo> updates, ActivityContext context) {
-        ItemOperator op = (info, v) -> {
-            if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView
-                    && updates.contains(info)) {
-                ((BubbleTextView) v).applyLoadingState(null);
-            } else if (v instanceof PendingAppWidgetHostView
-                    && info instanceof LauncherAppWidgetInfo
-                    && updates.contains(info)) {
-                ((PendingAppWidgetHostView) v).applyState();
-            } else if (v instanceof FolderIcon && info instanceof FolderInfo) {
-                ((FolderIcon) v).updatePreviewItems(updates::contains);
-            } else if (info instanceof AppPairInfo && v instanceof AppPairIcon appPairIcon) {
-                appPairIcon.maybeRedrawForWorkspaceUpdate(updates::contains);
-            }
-            // process all the shortcuts
-            return false;
-        };
-
-        mapOverItems(op);
-        Folder folder = Folder.getOpen(context);
-        if (folder != null) {
-            folder.iterateOverItems(op);
-        }
-    }
-
-    /**
-     * Map the operator over the shortcuts and widgets.
-     *
-     * @param op the operator to map over the shortcuts
-     */
-    void mapOverItems(ItemOperator op);
-
-    interface ItemOperator {
-        /**
-         * Process the next itemInfo, possibly with side-effect on the next item.
-         *
-         * @param info info for the shortcut
-         * @param view view for the shortcut
-         * @return true if done, false to continue the map
-         */
-        boolean evaluate(ItemInfo info, View view);
-    }
-}
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.kt b/src/com/android/launcher3/util/LauncherBindableItemsContainer.kt
new file mode 100644
index 0000000..8fdedef
--- /dev/null
+++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 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.view.View
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.apppairs.AppPairIcon
+import com.android.launcher3.folder.Folder
+import com.android.launcher3.folder.FolderIcon
+import com.android.launcher3.model.data.AppPairInfo
+import com.android.launcher3.model.data.FolderInfo
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator
+import com.android.launcher3.views.ActivityContext
+import com.android.launcher3.widget.PendingAppWidgetHostView
+import java.util.function.Predicate
+
+/** Interface representing a container which can bind Launcher items with some utility methods */
+interface LauncherBindableItemsContainer {
+
+    /**
+     * Called to update workspace items as a result of {@link
+     * com.android.launcher3.model.BgDataModel.Callbacks#bindItemsUpdated(Set)}
+     */
+    fun updateContainerItems(updates: Set<ItemInfo>, context: ActivityContext) {
+        val op = ItemOperator { info, v ->
+            when {
+                v is BubbleTextView && info is WorkspaceItemInfo && updates.contains(info) ->
+                    v.applyFromWorkspaceItem(info)
+                v is FolderIcon && info is FolderInfo -> v.updatePreviewItems(updates::contains)
+                v is AppPairIcon && info is AppPairInfo ->
+                    v.maybeRedrawForWorkspaceUpdate(updates::contains)
+                v is PendingAppWidgetHostView && updates.contains(info) -> {
+                    v.applyState()
+                    v.postProviderAvailabilityCheck()
+                }
+            }
+
+            // Iterate all items
+            false
+        }
+
+        mapOverItems(op)
+        Folder.getOpen(context)?.mapOverItems(op)
+    }
+
+    /** Returns the first view, matching the [op] */
+    @Deprecated("Use mapOverItems instead", ReplaceWith("mapOverItems(op)"))
+    fun getFirstMatch(op: ItemOperator): View? = mapOverItems(op)
+
+    /** Finds the first icon to match one of the given matchers, from highest to lowest priority. */
+    fun getFirstMatch(vararg matchers: Predicate<ItemInfo>): View? =
+        matchers.firstNotNullOfOrNull { mapOverItems { info, _ -> info != null && it.test(info) } }
+
+    fun getViewByItemId(id: Int): View? = mapOverItems { info, _ -> info != null && info.id == id }
+
+    /**
+     * Map the [op] over the shortcuts and widgets. Once we found the first view which matches, we
+     * will stop the iteration and return that view.
+     */
+    fun mapOverItems(op: ItemOperator): View?
+
+    fun interface ItemOperator {
+
+        /**
+         * Process the next itemInfo, possibly with side-effect on the next item.
+         *
+         * @param info info for the shortcut
+         * @param view view for the shortcut
+         * @return true if done, false to continue the map
+         */
+        fun evaluate(info: ItemInfo?, view: View): Boolean
+    }
+}
diff --git a/src/com/android/launcher3/util/LayoutImportExportHelper.kt b/src/com/android/launcher3/util/LayoutImportExportHelper.kt
new file mode 100644
index 0000000..8559f3b
--- /dev/null
+++ b/src/com/android/launcher3/util/LayoutImportExportHelper.kt
@@ -0,0 +1,136 @@
+/*
+ * 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.util
+
+import android.app.blob.BlobHandle.createWithSha256
+import android.app.blob.BlobStoreManager
+import android.content.Context
+import android.os.ParcelFileDescriptor.AutoCloseOutputStream
+import android.provider.Settings.Secure
+import com.android.launcher3.AutoInstallsLayout
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+import com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL
+import com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_TAG
+import com.android.launcher3.LauncherSettings.Settings.LAYOUT_PROVIDER_KEY
+import com.android.launcher3.LauncherSettings.Settings.createBlobProviderKey
+import com.android.launcher3.model.data.FolderInfo
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.LauncherAppWidgetInfo
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.MODEL_EXECUTOR
+import com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR
+import java.nio.charset.StandardCharsets
+import java.security.MessageDigest
+import java.util.concurrent.CompletableFuture
+
+object LayoutImportExportHelper {
+    fun exportModelDbAsXmlFuture(context: Context): CompletableFuture<String> {
+        val future = CompletableFuture<String>()
+        exportModelDbAsXml(context) { xmlString -> future.complete(xmlString) }
+        return future
+    }
+
+    fun exportModelDbAsXml(context: Context, callback: (String) -> Unit) {
+        val model = LauncherAppState.getInstance(context).model
+
+        model.enqueueModelUpdateTask { _, dataModel, _ ->
+            val builder = LauncherLayoutBuilder()
+            dataModel.itemsIdMap.forEach { info ->
+                val loc =
+                    when (info.container) {
+                        CONTAINER_DESKTOP ->
+                            builder.atWorkspace(info.cellX, info.cellY, info.screenId)
+
+                        CONTAINER_HOTSEAT -> builder.atHotseat(info.screenId)
+                        else -> return@forEach
+                    }
+                loc.addItem(context, info)
+            }
+
+            val layoutXml = builder.build()
+            callback(layoutXml)
+        }
+    }
+
+    fun importModelFromXml(context: Context, xmlString: String) {
+        importModelFromXml(context, xmlString.toByteArray(StandardCharsets.UTF_8))
+    }
+
+    fun importModelFromXml(context: Context, data: ByteArray) {
+        val model = LauncherAppState.getInstance(context).model
+
+        val digest = MessageDigest.getInstance("SHA-256").digest(data)
+        val handle = createWithSha256(digest, LAYOUT_DIGEST_LABEL, 0, LAYOUT_DIGEST_TAG)
+        val blobManager = context.getSystemService(BlobStoreManager::class.java)!!
+
+        val resolver = context.contentResolver
+
+        blobManager.openSession(blobManager.createSession(handle)).use { session ->
+            AutoCloseOutputStream(session.openWrite(0, -1)).use { it.write(data) }
+            session.allowPublicAccess()
+
+            session.commit(ORDERED_BG_EXECUTOR) {
+                Secure.putString(resolver, LAYOUT_PROVIDER_KEY, createBlobProviderKey(digest))
+
+                MODEL_EXECUTOR.submit { model.modelDbController.createEmptyDB() }.get()
+                MAIN_EXECUTOR.submit { model.forceReload() }.get()
+                MODEL_EXECUTOR.submit {}.get()
+                Secure.putString(resolver, LAYOUT_PROVIDER_KEY, null)
+            }
+        }
+    }
+
+    private fun LauncherLayoutBuilder.ItemTarget.addItem(context: Context, info: ItemInfo) {
+        val userType: String? =
+            when (UserCache.INSTANCE.get(context).getUserInfo(info.user).type) {
+                UserIconInfo.TYPE_WORK -> AutoInstallsLayout.USER_TYPE_WORK
+                UserIconInfo.TYPE_CLONED -> AutoInstallsLayout.USER_TYPE_CLONED
+                else -> null
+            }
+        when (info.itemType) {
+            ITEM_TYPE_APPLICATION ->
+                info.targetComponent?.let { c -> putApp(c.packageName, c.className, userType) }
+            ITEM_TYPE_DEEP_SHORTCUT ->
+                ShortcutKey.fromItemInfo(info).let { key ->
+                    putShortcut(key.packageName, key.id, userType)
+                }
+            ITEM_TYPE_FOLDER ->
+                (info as FolderInfo).let { folderInfo ->
+                    putFolder(folderInfo.title?.toString() ?: "").also { folderBuilder ->
+                        folderInfo.getContents().forEach { folderContent ->
+                            folderBuilder.addItem(context, folderContent)
+                        }
+                    }
+                }
+            ITEM_TYPE_APPWIDGET ->
+                putWidget(
+                    (info as LauncherAppWidgetInfo).providerName.packageName,
+                    info.providerName.className,
+                    info.spanX,
+                    info.spanY,
+                    userType,
+                )
+        }
+    }
+}
diff --git a/src/com/android/launcher3/util/LifecycleHelper.kt b/src/com/android/launcher3/util/LifecycleHelper.kt
new file mode 100644
index 0000000..803adae
--- /dev/null
+++ b/src/com/android/launcher3/util/LifecycleHelper.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.app.Activity
+import android.os.Bundle
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.setViewTreeLifecycleOwner
+import androidx.savedstate.SavedStateRegistryController
+import androidx.savedstate.SavedStateRegistryOwner
+import androidx.savedstate.setViewTreeSavedStateRegistryOwner
+
+/** Utility class for triggering various lifecycle events based on activity callbacks */
+class LifecycleHelper(
+    private val owner: SavedStateRegistryOwner,
+    private val savedStateRegistryController: SavedStateRegistryController,
+    private val lifecycleRegistry: LifecycleRegistry,
+) : ActivityLifecycleCallbacksAdapter {
+
+    override fun onActivityPreCreated(activity: Activity, savedInstanceState: Bundle?) {
+        savedStateRegistryController.performRestore(savedInstanceState)
+    }
+
+    override fun onActivityPostCreated(activity: Activity, savedInstanceState: Bundle?) {
+        activity.window.decorView.setViewTreeLifecycleOwner(owner)
+        activity.window.decorView.setViewTreeSavedStateRegistryOwner(owner)
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
+    }
+
+    override fun onActivityPostStarted(activity: Activity) {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
+    }
+
+    override fun onActivityPostResumed(activity: Activity) {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
+    }
+
+    override fun onActivityPrePaused(activity: Activity) {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
+    }
+
+    override fun onActivityPreStopped(activity: Activity) {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
+    }
+
+    override fun onActivityPreDestroyed(activity: Activity) {
+        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+    }
+
+    override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) {
+        lifecycleRegistry.currentState = Lifecycle.State.CREATED
+        savedStateRegistryController.performSave(bundle)
+    }
+}
diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt
index c8d86d4..742a327 100644
--- a/src/com/android/launcher3/util/LockedUserState.kt
+++ b/src/com/android/launcher3/util/LockedUserState.kt
@@ -20,10 +20,17 @@
 import android.os.Process
 import android.os.UserManager
 import androidx.annotation.VisibleForTesting
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.util.Executors.MAIN_EXECUTOR
 import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import javax.inject.Inject
 
-class LockedUserState(private val mContext: Context) : SafeCloseable {
+@LauncherAppSingleton
+class LockedUserState
+@Inject
+constructor(@ApplicationContext private val context: Context, lifeCycle: DaggerSingletonTracker) {
     val isUserUnlockedAtLauncherStartup: Boolean
     var isUserUnlocked = false
         private set(value) {
@@ -36,8 +43,8 @@
     private val mUserUnlockedActions: RunnableList = RunnableList()
 
     @VisibleForTesting
-    val mUserUnlockedReceiver =
-        SimpleBroadcastReceiver(UI_HELPER_EXECUTOR) {
+    val userUnlockedReceiver =
+        SimpleBroadcastReceiver(context, UI_HELPER_EXECUTOR) {
             if (Intent.ACTION_USER_UNLOCKED == it.action) {
                 isUserUnlocked = true
             }
@@ -53,8 +60,7 @@
         isUserUnlocked = checkIsUserUnlocked()
         isUserUnlockedAtLauncherStartup = isUserUnlocked
         if (!isUserUnlocked) {
-            mUserUnlockedReceiver.register(
-                mContext,
+            userUnlockedReceiver.register(
                 {
                     // If user is unlocked while registering broadcast receiver, we should update
                     // [isUserUnlocked], which will call [notifyUserUnlocked] in setter
@@ -62,22 +68,18 @@
                         MAIN_EXECUTOR.execute { isUserUnlocked = true }
                     }
                 },
-                Intent.ACTION_USER_UNLOCKED
+                Intent.ACTION_USER_UNLOCKED,
             )
         }
+        lifeCycle.addCloseable { userUnlockedReceiver.unregisterReceiverSafely() }
     }
 
     private fun checkIsUserUnlocked() =
-        mContext.getSystemService(UserManager::class.java)!!.isUserUnlocked(Process.myUserHandle())
+        context.getSystemService(UserManager::class.java)!!.isUserUnlocked(Process.myUserHandle())
 
     private fun notifyUserUnlocked() {
         mUserUnlockedActions.executeAllAndDestroy()
-        mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
-    }
-
-    /** Stops the receiver from listening for ACTION_USER_UNLOCK broadcasts. */
-    override fun close() {
-        mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
+        userUnlockedReceiver.unregisterReceiverSafely()
     }
 
     /**
@@ -88,9 +90,7 @@
         mUserUnlockedActions.add(action)
     }
 
-    /**
-     * Removes a previously queued `Runnable` to be run when the user is unlocked.
-     */
+    /** Removes a previously queued `Runnable` to be run when the user is unlocked. */
     fun removeOnUserUnlockedRunnable(action: Runnable) {
         mUserUnlockedActions.remove(action)
     }
@@ -98,7 +98,7 @@
     companion object {
         @VisibleForTesting
         @JvmField
-        val INSTANCE = MainThreadInitializedObject { LockedUserState(it) }
+        val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getLockedUserState)
 
         @JvmStatic fun get(context: Context): LockedUserState = INSTANCE.get(context)
     }
diff --git a/src/com/android/launcher3/util/LooperExecutor.java b/src/com/android/launcher3/util/LooperExecutor.java
deleted file mode 100644
index 3a8a13c..0000000
--- a/src/com/android/launcher3/util/LooperExecutor.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Copyright (C) 2017 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.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Process;
-
-import java.util.List;
-import java.util.concurrent.AbstractExecutorService;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Extension of {@link AbstractExecutorService} which executed on a provided looper.
- */
-public class LooperExecutor extends AbstractExecutorService {
-
-    private final Handler mHandler;
-
-    public LooperExecutor(Looper looper) {
-        mHandler = new Handler(looper);
-    }
-
-    public Handler getHandler() {
-        return mHandler;
-    }
-
-    @Override
-    public void execute(Runnable runnable) {
-        if (getHandler().getLooper() == Looper.myLooper()) {
-            runnable.run();
-        } else {
-            getHandler().post(runnable);
-        }
-    }
-
-    /**
-     * Same as execute, but never runs the action inline.
-     */
-    public void post(Runnable runnable) {
-        getHandler().post(runnable);
-    }
-
-    /**
-     * Not supported and throws an exception when used.
-     */
-    @Override
-    @Deprecated
-    public void shutdown() {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Not supported and throws an exception when used.
-     */
-    @Override
-    @Deprecated
-    public List<Runnable> shutdownNow() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean isShutdown() {
-        return false;
-    }
-
-    @Override
-    public boolean isTerminated() {
-        return false;
-    }
-
-    /**
-     * Not supported and throws an exception when used.
-     */
-    @Override
-    @Deprecated
-    public boolean awaitTermination(long l, TimeUnit timeUnit) {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Returns the thread for this executor
-     */
-    public Thread getThread() {
-        return getHandler().getLooper().getThread();
-    }
-
-    /**
-     * Returns the looper for this executor
-     */
-    public Looper getLooper() {
-        return getHandler().getLooper();
-    }
-
-    /**
-     * Set the priority of a thread, based on Linux priorities.
-     * @param priority Linux priority level, from -20 for highest scheduling priority
-     *                to 19 for lowest scheduling priority.
-     * @see Process#setThreadPriority(int, int)
-     */
-    public void setThreadPriority(int priority) {
-        Process.setThreadPriority(((HandlerThread) getThread()).getThreadId(), priority);
-    }
-}
diff --git a/src/com/android/launcher3/util/LooperExecutor.kt b/src/com/android/launcher3/util/LooperExecutor.kt
new file mode 100644
index 0000000..6ff528d
--- /dev/null
+++ b/src/com/android/launcher3/util/LooperExecutor.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2017 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.os.Handler
+import android.os.HandlerThread
+import android.os.Looper
+import android.os.Process
+import android.os.Process.THREAD_PRIORITY_FOREGROUND
+import androidx.annotation.IntDef
+import java.util.concurrent.AbstractExecutorService
+import java.util.concurrent.TimeUnit
+import kotlin.annotation.AnnotationRetention.SOURCE
+
+/** Extension of [AbstractExecutorService] which executed on a provided looper. */
+class LooperExecutor(looper: Looper, private val defaultPriority: Int) : AbstractExecutorService() {
+    val handler: Handler = Handler(looper)
+
+    @JvmOverloads
+    constructor(
+        name: String,
+        defaultPriority: Int = Process.THREAD_PRIORITY_DEFAULT,
+    ) : this(createAndStartNewLooper(name, defaultPriority), defaultPriority)
+
+    /** Returns the thread for this executor */
+    val thread: Thread
+        get() = handler.looper.thread
+
+    /** Returns the looper for this executor */
+    val looper: Looper
+        get() = handler.looper
+
+    @ElevationCaller private var elevationFlags: Int = 0
+
+    override fun execute(runnable: Runnable) {
+        if (handler.looper == Looper.myLooper()) {
+            runnable.run()
+        } else {
+            handler.post(runnable)
+        }
+    }
+
+    /** Same as execute, but never runs the action inline. */
+    fun post(runnable: Runnable) {
+        handler.post(runnable)
+    }
+
+    @Deprecated("Not supported and throws an exception when used")
+    override fun shutdown() {
+        throw UnsupportedOperationException()
+    }
+
+    @Deprecated("Not supported and throws an exception when used.")
+    override fun shutdownNow(): List<Runnable> {
+        throw UnsupportedOperationException()
+    }
+
+    override fun isShutdown() = false
+
+    override fun isTerminated() = false
+
+    @Deprecated("Not supported and throws an exception when used.")
+    override fun awaitTermination(l: Long, timeUnit: TimeUnit): Boolean {
+        throw UnsupportedOperationException()
+    }
+
+    /**
+     * Increases the priority of the thread for the [caller]. Multiple calls with same caller are
+     * ignored. The priority is reset once wall callers have restored priority
+     */
+    fun elevatePriority(@ElevationCaller caller: Int) {
+        val wasElevated = elevationFlags != 0
+        elevationFlags = elevationFlags.or(caller)
+        if (elevationFlags != 0 && !wasElevated)
+            Process.setThreadPriority(
+                (thread as HandlerThread).threadId,
+                THREAD_PRIORITY_FOREGROUND,
+            )
+    }
+
+    /** Restores to default priority if it was previously elevated */
+    fun restorePriority(@ElevationCaller caller: Int) {
+        val wasElevated = elevationFlags != 0
+        elevationFlags = elevationFlags.and(caller.inv())
+        if (elevationFlags == 0 && wasElevated)
+            Process.setThreadPriority((thread as HandlerThread).threadId, defaultPriority)
+    }
+
+    @Retention(SOURCE)
+    @IntDef(value = [CALLER_LOADER_TASK, CALLER_ICON_CACHE], flag = true)
+    annotation class ElevationCaller
+
+    companion object {
+        /** Utility method to get a started handler thread statically with the provided priority */
+        @JvmOverloads
+        @JvmStatic
+        fun createAndStartNewLooper(
+            name: String,
+            priority: Int = Process.THREAD_PRIORITY_DEFAULT,
+        ): Looper = HandlerThread(name, priority).apply { start() }.looper
+
+        const val CALLER_LOADER_TASK = 1 shl 0
+        const val CALLER_ICON_CACHE = 1 shl 1
+    }
+}
diff --git a/src/com/android/launcher3/util/MSDLPlayerWrapper.java b/src/com/android/launcher3/util/MSDLPlayerWrapper.java
index 8a1d923..fc3fa72 100644
--- a/src/com/android/launcher3/util/MSDLPlayerWrapper.java
+++ b/src/com/android/launcher3/util/MSDLPlayerWrapper.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.util;
 
-import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-
 import android.content.Context;
 import android.os.Vibrator;
 
@@ -50,7 +48,9 @@
     @Inject
     public MSDLPlayerWrapper(@ApplicationContext Context context) {
         Vibrator vibrator = context.getSystemService(Vibrator.class);
-        mMSDLPlayer = MSDLPlayer.Companion.createPlayer(vibrator, UI_HELPER_EXECUTOR, null);
+        mMSDLPlayer = MSDLPlayer.Companion.createPlayer(vibrator,
+                java.util.concurrent.Executors.newSingleThreadExecutor(),
+                null /* useHapticFeedbackForToken */);
     }
 
     /** Perform MSDL feedback for a token with interaction properties */
diff --git a/src/com/android/launcher3/util/MainThreadInitializedObject.java b/src/com/android/launcher3/util/MainThreadInitializedObject.java
deleted file mode 100644
index 356a551..0000000
--- a/src/com/android/launcher3/util/MainThreadInitializedObject.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.launcher3.util;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import android.content.Context;
-import android.os.Looper;
-import android.util.Log;
-
-import androidx.annotation.UiThread;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.launcher3.LauncherApplication;
-import com.android.launcher3.util.ResourceBasedOverride.Overrides;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
-
-/**
- * Utility class for defining singletons which are initiated on main thread.
- *
- * TODO(b/361850561): Do not delete MainThreadInitializedObject until we find a way to
- * unregister and understand how singleton objects are destroyed in dagger graph.
- */
-public class MainThreadInitializedObject<T extends SafeCloseable> {
-
-    private final ObjectProvider<T> mProvider;
-    private T mValue;
-
-    public MainThreadInitializedObject(ObjectProvider<T> provider) {
-        mProvider = provider;
-    }
-
-    public T get(Context context) {
-        Context app = context.getApplicationContext();
-        if (app instanceof ObjectSandbox sc) {
-            return sc.getObject(this);
-        }
-
-        if (mValue == null) {
-            if (Looper.myLooper() == Looper.getMainLooper()) {
-                mValue = TraceHelper.allowIpcs("main.thread.object", () -> mProvider.get(app));
-            } else {
-                try {
-                    return MAIN_EXECUTOR.submit(() -> get(context)).get();
-                } catch (InterruptedException|ExecutionException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-        return mValue;
-    }
-
-    /**
-     * Executes the callback is the value is already created
-     * @return true if the callback was executed, false otherwise
-     */
-    public boolean executeIfCreated(Consumer<T> callback) {
-        T v = mValue;
-        if (v != null) {
-            callback.accept(v);
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    @VisibleForTesting
-    public void initializeForTesting(T value) {
-        mValue = value;
-    }
-
-    /**
-     * Initializes a provider based on resource overrides
-     */
-    public static <T extends ResourceBasedOverride & SafeCloseable> MainThreadInitializedObject<T>
-            forOverride(Class<T> clazz, int resourceId) {
-        return new MainThreadInitializedObject<>(c -> Overrides.getObject(clazz, c, resourceId));
-    }
-
-    public interface ObjectProvider<T> {
-
-        T get(Context context);
-    }
-
-    /** Sandbox for isolating {@link MainThreadInitializedObject} instances from Launcher. */
-    public interface ObjectSandbox {
-
-        /**
-         * Find a cached object from mObjectMap if we have already created one. If not, generate
-         * an object using the provider.
-         */
-        <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object);
-
-
-        /**
-         * Put a value into cache, can be used to put mocked MainThreadInitializedObject
-         * instances.
-         */
-        <T extends SafeCloseable> void putObject(MainThreadInitializedObject<T> object, T value);
-
-        /**
-         * Returns whether this sandbox should cleanup all objects when its destroyed or leave it
-         * to the GC.
-         * These objects can have listeners attached to the system server and mey not be able to get
-         * GCed themselves when running on a device.
-         * Some environments like Robolectric tear down the whole system at the end of the test,
-         * so manual cleanup may not be required.
-         */
-        default boolean shouldCleanUpOnDestroy() {
-            return true;
-        }
-
-        @UiThread
-        default <T extends SafeCloseable> T createObject(MainThreadInitializedObject<T> object) {
-            return object.mProvider.get((Context) this);
-        }
-    }
-
-    /**
-     * Abstract Context which allows custom implementations for
-     * {@link MainThreadInitializedObject} providers
-     */
-    public static class SandboxContext extends LauncherApplication implements ObjectSandbox {
-
-        private static final String TAG = "SandboxContext";
-
-        private final Map<MainThreadInitializedObject, Object> mObjectMap = new HashMap<>();
-        private final ArrayList<SafeCloseable> mOrderedObjects = new ArrayList<>();
-
-        private final Object mDestroyLock = new Object();
-        private boolean mDestroyed = false;
-
-        public SandboxContext(Context base) {
-            attachBaseContext(base);
-        }
-
-        @Override
-        public Context getApplicationContext() {
-            return this;
-        }
-
-        @Override
-        public boolean shouldCleanUpOnDestroy() {
-            return (getBaseContext().getApplicationContext() instanceof ObjectSandbox os)
-                    ? os.shouldCleanUpOnDestroy() : true;
-        }
-
-        public void onDestroy() {
-            if (shouldCleanUpOnDestroy()) {
-                cleanUpObjects();
-            }
-        }
-
-        protected void cleanUpObjects() {
-            getAppComponent().getDaggerSingletonTracker().close();
-            synchronized (mDestroyLock) {
-                // Destroy in reverse order
-                for (int i = mOrderedObjects.size() - 1; i >= 0; i--) {
-                    mOrderedObjects.get(i).close();
-                }
-                mDestroyed = true;
-            }
-        }
-
-        @Override
-        public <T extends SafeCloseable> T getObject(MainThreadInitializedObject<T> object) {
-            synchronized (mDestroyLock) {
-                if (mDestroyed) {
-                    Log.e(TAG, "Static object access with a destroyed context");
-                }
-                T t = (T) mObjectMap.get(object);
-                if (t != null) {
-                    return t;
-                }
-                if (Looper.myLooper() == Looper.getMainLooper()) {
-                    t = createObject(object);
-                    mObjectMap.put(object, t);
-                    mOrderedObjects.add(t);
-                    return t;
-                }
-            }
-
-            try {
-                return MAIN_EXECUTOR.submit(() -> getObject(object)).get();
-            } catch (InterruptedException | ExecutionException e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        @Override
-        public <T extends SafeCloseable> void putObject(
-                MainThreadInitializedObject<T> object, T value) {
-            mObjectMap.put(object, value);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/util/MultiPropertyDelegate.kt b/src/com/android/launcher3/util/MultiPropertyDelegate.kt
new file mode 100644
index 0000000..837a586
--- /dev/null
+++ b/src/com/android/launcher3/util/MultiPropertyDelegate.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import kotlin.reflect.KProperty
+
+/** Delegate Kotlin mutable property (var) to a property in [MultiPropertyFactory] */
+class MultiPropertyDelegate(private val property: MultiPropertyFactory<*>.MultiProperty) {
+    constructor(factory: MultiPropertyFactory<*>, enum: Enum<*>) : this(factory[enum.ordinal])
+
+    operator fun getValue(thisRef: Any?, kProperty: KProperty<*>): Float = property.value
+
+    operator fun setValue(thisRef: Any?, kProperty: KProperty<*>, value: Float) {
+        property.value = value
+    }
+}
diff --git a/src/com/android/launcher3/util/MultiTranslateDelegate.java b/src/com/android/launcher3/util/MultiTranslateDelegate.java
index 38c87c8..ce006c4 100644
--- a/src/com/android/launcher3/util/MultiTranslateDelegate.java
+++ b/src/com/android/launcher3/util/MultiTranslateDelegate.java
@@ -38,6 +38,7 @@
     public static final int INDEX_TASKBAR_REVEAL_ANIM = 4;
     public static final int INDEX_TASKBAR_PINNING_ANIM = 5;
     public static final int INDEX_NAV_BAR_ANIM = 6;
+    public static final int INDEX_BUBBLE_BAR_ANIM = 7;
 
     // Affect all items inside of a MultipageCellLayout
     public static final int INDEX_CELLAYOUT_MULTIPAGE_SPACING = 3;
@@ -48,7 +49,7 @@
     // Specific for hotseat items when adjusting for bubbles
     public static final int INDEX_BUBBLE_ADJUSTMENT_ANIM = 3;
 
-    public static final int COUNT = 7;
+    public static final int COUNT = 8;
 
     private final MultiPropertyFactory<View> mTranslationX;
     private final MultiPropertyFactory<View> mTranslationY;
diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java
index 4b60d98..3d01f24 100644
--- a/src/com/android/launcher3/util/PackageManagerHelper.java
+++ b/src/com/android/launcher3/util/PackageManagerHelper.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.util;
 
-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;
@@ -41,7 +39,6 @@
 
 import com.android.launcher3.PendingAddItemInfo;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.dagger.ApplicationContext;
 import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.dagger.LauncherBaseAppComponent;
@@ -77,15 +74,11 @@
     @NonNull
     private final LauncherApps mLauncherApps;
 
-    private final String[] mLegacyMultiInstanceSupportedApps;
-
     @Inject
     public PackageManagerHelper(@ApplicationContext final Context context) {
         mContext = context;
         mPm = context.getPackageManager();
         mLauncherApps = Objects.requireNonNull(context.getSystemService(LauncherApps.class));
-        mLegacyMultiInstanceSupportedApps = mContext.getResources().getStringArray(
-                    R.array.config_appsSupportMultiInstancesSplit);
     }
 
     /**
@@ -193,39 +186,6 @@
     }
 
     /**
-     * Returns whether the given component or its application has the multi-instance property set.
-     */
-    public boolean supportsMultiInstance(@NonNull ComponentName component) {
-        // Check the legacy hardcoded allowlist first
-        for (String pkg : mLegacyMultiInstanceSupportedApps) {
-            if (pkg.equals(component.getPackageName())) {
-                return true;
-            }
-        }
-
-        // Check app multi-instance properties after V
-        if (!Utilities.ATLEAST_V) {
-            return false;
-        }
-
-        try {
-            // Check if the component has the multi-instance property
-            return mPm.getProperty(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI, component)
-                    .getBoolean();
-        } catch (PackageManager.NameNotFoundException e1) {
-            try {
-                // Check if the application has the multi-instance property
-                return mPm.getProperty(PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI,
-                                component.getPackageName())
-                    .getBoolean();
-            } catch (PackageManager.NameNotFoundException e2) {
-                // Fall through
-            }
-        }
-        return false;
-    }
-
-    /**
      * Returns whether two apps should be considered the same for multi-instance purposes, which
      * requires additional checks to ensure they can be started as multiple instances.
      */
diff --git a/src/com/android/launcher3/util/SandboxContext.kt b/src/com/android/launcher3/util/SandboxContext.kt
new file mode 100644
index 0000000..c6224e2
--- /dev/null
+++ b/src/com/android/launcher3/util/SandboxContext.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.Context
+import com.android.launcher3.LauncherApplication
+
+/** Abstract Context which allows custom implementations for dagger components. */
+open class SandboxContext(base: Context?) : LauncherApplication() {
+    init {
+        base?.let { attachBaseContext(it) }
+    }
+
+    override fun getApplicationContext(): Context {
+        return this
+    }
+
+    /**
+     * Returns whether this sandbox should cleanup all objects when its destroyed or leave it to the
+     * GC. These objects can have listeners attached to the system server and mey not be able to get
+     * GCed themselves when running on a device. Some environments like Robolectric tear down the
+     * whole system at the end of the test, so manual cleanup may not be required.
+     */
+    open fun shouldCleanUpOnDestroy(): Boolean {
+        return (getBaseContext().getApplicationContext() as? SandboxContext)
+            ?.shouldCleanUpOnDestroy() ?: true
+    }
+
+    fun onDestroy() {
+        if (shouldCleanUpOnDestroy()) {
+            cleanUpObjects()
+        }
+    }
+
+    open protected fun cleanUpObjects() {
+        appComponent.daggerSingletonTracker.close()
+    }
+
+    companion object {
+        private const val TAG = "SandboxContext"
+    }
+}
diff --git a/src/com/android/launcher3/util/ScreenOnTracker.java b/src/com/android/launcher3/util/ScreenOnTracker.java
index 50be98b..8ffe9ea 100644
--- a/src/com/android/launcher3/util/ScreenOnTracker.java
+++ b/src/com/android/launcher3/util/ScreenOnTracker.java
@@ -46,34 +46,31 @@
     private final SimpleBroadcastReceiver mReceiver;
     private final CopyOnWriteArrayList<ScreenOnListener> mListeners = new CopyOnWriteArrayList<>();
 
-    private final Context mContext;
     private boolean mIsScreenOn;
 
     @Inject
     ScreenOnTracker(@ApplicationContext Context context, DaggerSingletonTracker tracker) {
         // Assume that the screen is on to begin with
-        mContext = context;
-        mReceiver = new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, this::onReceive);
+        mReceiver = new SimpleBroadcastReceiver(context, UI_HELPER_EXECUTOR, this::onReceive);
         init(tracker);
     }
 
     @VisibleForTesting
     ScreenOnTracker(@ApplicationContext Context context, SimpleBroadcastReceiver receiver,
             DaggerSingletonTracker tracker) {
-        mContext = context;
         mReceiver = receiver;
         init(tracker);
     }
 
     private void init(DaggerSingletonTracker tracker) {
         mIsScreenOn = true;
-        mReceiver.register(mContext, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT);
+        mReceiver.register(ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT);
         tracker.addCloseable(this);
     }
 
     @Override
     public void close() {
-        mReceiver.unregisterReceiverSafely(mContext);
+        mReceiver.unregisterReceiverSafely();
     }
 
     @VisibleForTesting
diff --git a/src/com/android/launcher3/util/SettingsCache.java b/src/com/android/launcher3/util/SettingsCache.java
index 8fe6e93..fa183c8 100644
--- a/src/com/android/launcher3/util/SettingsCache.java
+++ b/src/com/android/launcher3/util/SettingsCache.java
@@ -34,11 +34,11 @@
 import com.android.launcher3.dagger.LauncherAppSingleton;
 import com.android.launcher3.dagger.LauncherBaseAppComponent;
 
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Function;
 
 import javax.inject.Inject;
 
@@ -57,7 +57,7 @@
  * Cache will also be updated if a key queried is missing (even if it has no listeners registered).
  */
 @LauncherAppSingleton
-public class SettingsCache extends ContentObserver implements SafeCloseable {
+public class SettingsCache extends ContentObserver {
 
     /** Hidden field Settings.Secure.NOTIFICATION_BADGING */
     public static final Uri NOTIFICATION_BADGING_URI =
@@ -79,11 +79,17 @@
     private static final String SYSTEM_URI_PREFIX = Settings.System.CONTENT_URI.toString();
     private static final String GLOBAL_URI_PREFIX = Settings.Global.CONTENT_URI.toString();
 
+    private final Function<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMapper = uri -> {
+        registerUriAsync(uri);
+        return new CopyOnWriteArrayList<>();
+    };
+
     /**
      * Caches the last seen value for registered keys.
      */
-    private Map<Uri, Boolean> mKeyCache = new ConcurrentHashMap<>();
-    private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap = new HashMap<>();
+    private final Map<Uri, Boolean> mKeyCache = new ConcurrentHashMap<>();
+    private final Map<Uri, CopyOnWriteArrayList<OnChangeListener>> mListenerMap =
+            new ConcurrentHashMap<>();
     protected final ContentResolver mResolver;
 
     /**
@@ -96,12 +102,8 @@
     SettingsCache(@ApplicationContext Context context, DaggerSingletonTracker tracker) {
         super(new Handler(Looper.getMainLooper()));
         mResolver = context.getContentResolver();
-        tracker.addCloseable(this);
-    }
-
-    @Override
-    public void close() {
-        UI_HELPER_EXECUTOR.execute(() -> mResolver.unregisterContentObserver(this));
+        tracker.addCloseable(() ->
+                UI_HELPER_EXECUTOR.execute(() -> mResolver.unregisterContentObserver(this)));
     }
 
     @Override
@@ -109,11 +111,12 @@
         // We use default of 1, but if we're getting an onChange call, can assume a non-default
         // value will exist
         boolean newVal = updateValue(uri, 1 /* Effectively Unused */);
-        if (!mListenerMap.containsKey(uri)) {
+        List<OnChangeListener> listeners = mListenerMap.get(uri);
+        if (listeners == null) {
             return;
         }
 
-        for (OnChangeListener listener : mListenerMap.get(uri)) {
+        for (OnChangeListener listener : listeners) {
             listener.onSettingsChanged(newVal);
         }
     }
@@ -138,22 +141,17 @@
         }
     }
 
+    private void registerUriAsync(Uri uri) {
+        UI_HELPER_EXECUTOR.execute(() -> mResolver.registerContentObserver(uri, false, this));
+    }
+
     /**
      * Does not de-dupe if you add same listeners for the same key multiple times.
      * Unregister once complete using {@link #unregister(Uri, OnChangeListener)}
      */
     @UiThread
     public void register(Uri uri, OnChangeListener changeListener) {
-        Preconditions.assertUIThread();
-        if (mListenerMap.containsKey(uri)) {
-            mListenerMap.get(uri).add(changeListener);
-        } else {
-            CopyOnWriteArrayList<OnChangeListener> l = new CopyOnWriteArrayList<>();
-            l.add(changeListener);
-            mListenerMap.put(uri, l);
-            UI_HELPER_EXECUTOR.execute(
-                    () -> mResolver.registerContentObserver(uri, false, this));
-        }
+        mListenerMap.computeIfAbsent(uri, mListenerMapper).add(changeListener);
     }
 
     private boolean updateValue(Uri keyUri, int defaultValue) {
diff --git a/src/com/android/launcher3/util/SimpleBroadcastReceiver.java b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
index 539a7cb..7a40abe 100644
--- a/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
+++ b/src/com/android/launcher3/util/SimpleBroadcastReceiver.java
@@ -25,22 +25,29 @@
 import android.text.TextUtils;
 
 import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import java.util.function.Consumer;
 
 public class SimpleBroadcastReceiver extends BroadcastReceiver {
+    public static final String TAG = "SimpleBroadcastReceiver";
+    // Keeps a strong reference to the context.
+    private final Context mContext;
 
     private final Consumer<Intent> mIntentConsumer;
 
     // Handler to register/unregister broadcast receiver
     private final Handler mHandler;
 
-    public SimpleBroadcastReceiver(LooperExecutor looperExecutor, Consumer<Intent> intentConsumer) {
-        this(looperExecutor.getHandler(), intentConsumer);
+    public SimpleBroadcastReceiver(@NonNull Context context, LooperExecutor looperExecutor,
+            Consumer<Intent> intentConsumer) {
+        this(context, looperExecutor.getHandler(), intentConsumer);
     }
 
-    public SimpleBroadcastReceiver(Handler handler, Consumer<Intent> intentConsumer) {
+    public SimpleBroadcastReceiver(@NonNull Context context, Handler handler,
+            Consumer<Intent> intentConsumer) {
+        mContext = context;
         mIntentConsumer = intentConsumer;
         mHandler = handler;
     }
@@ -50,18 +57,18 @@
         mIntentConsumer.accept(intent);
     }
 
-    /** Calls {@link #register(Context, Runnable, String...)} with null completionCallback. */
+    /** Calls {@link #register(Runnable, String...)} with null completionCallback. */
     @AnyThread
-    public void register(Context context, String... actions) {
-        register(context, null, actions);
+    public void register(String... actions) {
+        register(null, actions);
     }
 
     /**
-     * Calls {@link #register(Context, Runnable, int, String...)} with null completionCallback.
+     * Calls {@link #register(Runnable, int, String...)} with null completionCallback.
      */
     @AnyThread
-    public void register(Context context, int flags, String... actions) {
-        register(context, null, flags, actions);
+    public void register(int flags, String... actions) {
+        register(null, flags, actions);
     }
 
     /**
@@ -74,19 +81,18 @@
      *                           while registerReceiver() is executed on a binder call.
      */
     @AnyThread
-    public void register(
-            Context context, @Nullable Runnable completionCallback, String... actions) {
+    public void register(@Nullable Runnable completionCallback, String... actions) {
         if (Looper.myLooper() == mHandler.getLooper()) {
-            registerInternal(context, completionCallback, actions);
+            registerInternal(mContext, completionCallback, actions);
         } else {
-            mHandler.post(() -> registerInternal(context, completionCallback, actions));
+            mHandler.post(() -> registerInternal(mContext, completionCallback, actions));
         }
     }
 
     /** Register broadcast receiver and run completion callback if passed. */
     @AnyThread
     private void registerInternal(
-            Context context, @Nullable Runnable completionCallback, String... actions) {
+            @NonNull Context context, @Nullable Runnable completionCallback, String... actions) {
         context.registerReceiver(this, getFilter(actions));
         if (completionCallback != null) {
             completionCallback.run();
@@ -94,37 +100,37 @@
     }
 
     /**
-     * Same as {@link #register(Context, Runnable, String...)} above but with additional flags
-     * params.
+     * Same as {@link #register(Runnable, String...)} above but with additional flags
+     * params utilizine the original {@link Context}.
      */
     @AnyThread
-    public void register(
-            Context context, @Nullable Runnable completionCallback, int flags, String... actions) {
+    public void register(@Nullable Runnable completionCallback, int flags, String... actions) {
         if (Looper.myLooper() == mHandler.getLooper()) {
-            registerInternal(context, completionCallback, flags, actions);
+            registerInternal(mContext, completionCallback, flags, actions);
         } else {
-            mHandler.post(() -> registerInternal(context, completionCallback, flags, actions));
+            mHandler.post(() -> registerInternal(mContext, completionCallback, flags, actions));
         }
     }
 
     /** Register broadcast receiver and run completion callback if passed. */
     @AnyThread
     private void registerInternal(
-            Context context, @Nullable Runnable completionCallback, int flags, String... actions) {
+            @NonNull Context context, @Nullable Runnable completionCallback, int flags,
+            String... actions) {
         context.registerReceiver(this, getFilter(actions), flags);
         if (completionCallback != null) {
             completionCallback.run();
         }
     }
 
-    /** Same as {@link #register(Context, Runnable, String...)} above but with pkg name. */
+    /** Same as {@link #register(Runnable, String...)} above but with pkg name. */
     @AnyThread
-    public void registerPkgActions(Context context, @Nullable String pkg, String... actions) {
+    public void registerPkgActions(@Nullable String pkg, String... actions) {
         if (Looper.myLooper() == mHandler.getLooper()) {
-            context.registerReceiver(this, getPackageFilter(pkg, actions));
+            mContext.registerReceiver(this, getPackageFilter(pkg, actions));
         } else {
             mHandler.post(() -> {
-                context.registerReceiver(this, getPackageFilter(pkg, actions));
+                mContext.registerReceiver(this, getPackageFilter(pkg, actions));
             });
         }
     }
@@ -135,19 +141,19 @@
      * unregister happens on {@link #mHandler}'s looper.
      */
     @AnyThread
-    public void unregisterReceiverSafely(Context context) {
+    public void unregisterReceiverSafely() {
         if (Looper.myLooper() == mHandler.getLooper()) {
-            unregisterReceiverSafelyInternal(context);
+            unregisterReceiverSafelyInternal(mContext);
         } else {
             mHandler.post(() -> {
-                unregisterReceiverSafelyInternal(context);
+                unregisterReceiverSafelyInternal(mContext);
             });
         }
     }
 
     /** Unregister broadcast receiver ignoring any errors. */
     @AnyThread
-    private void unregisterReceiverSafelyInternal(Context context) {
+    private void unregisterReceiverSafelyInternal(@NonNull Context context) {
         try {
             context.unregisterReceiver(this);
         } catch (IllegalArgumentException e) {
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index 44a7c6f..e1ef77a 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -127,10 +127,10 @@
         /** This rect represents the actual gap between the two apps */
         public final Rect visualDividerBounds;
         // This class is orientation-agnostic, so we compute both for later use
-        public final float topTaskPercent;
-        public final float leftTaskPercent;
-        public final float dividerWidthPercent;
-        public final float dividerHeightPercent;
+        private final float topTaskPercent;
+        private final float leftTaskPercent;
+        private final float dividerWidthPercent;
+        private final float dividerHeightPercent;
         public final int snapPosition;
 
         /**
@@ -190,6 +190,39 @@
             dividerHeightPercent = visualDividerBounds.height() / totalHeight;
         }
 
+        /**
+         * Returns the percentage size of the left/top task (compared to the full width/height of
+         * the split pair). E.g. if the left task is 4 units wide, the divider is 2 units, and the
+         * right task is 4 units, this method will return 0.4f.
+         */
+        public float getLeftTopTaskPercent() {
+            // topTaskPercent and leftTaskPercent are defined at creation time, and are not updated
+            // on device rotate, so we have to check appsStackedVertically to return the right
+            // creation-time measurements.
+            return appsStackedVertically ? topTaskPercent : leftTaskPercent;
+        }
+
+        /**
+         * Returns the percentage size of the divider's thickness (compared to the full width/height
+         * of the split pair). E.g. if the left task is 4 units wide, the divider is 2 units, and
+         * the right task is 4 units, this method will return 0.2f.
+         */
+        public float getDividerPercent() {
+            // dividerHeightPercent and dividerWidthPercent are defined at creation time, and are
+            // not updated on device rotate, so we have to check appsStackedVertically to return
+            // the right creation-time measurements.
+            return appsStackedVertically ? dividerHeightPercent : dividerWidthPercent;
+        }
+
+        /**
+         * Returns the percentage size of the right/bottom task (compared to the full width/height
+         * of the split pair). E.g. if the left task is 4 units wide, the divider is 2 units, and
+         * the right task is 4 units, this method will return 0.4f.
+         */
+        public float getRightBottomTaskPercent() {
+            return 1 - (getLeftTopTaskPercent() + getDividerPercent());
+        }
+
         @Override
         public String toString() {
             return "LeftTop: " + leftTopBounds + ", taskId: " + leftTopTaskId + "\n"
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index 104040a..927a2a4 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -19,8 +19,6 @@
 import static android.app.WallpaperColors.HINT_SUPPORTS_DARK_TEXT;
 import static android.app.WallpaperColors.HINT_SUPPORTS_DARK_THEME;
 
-import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
-
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Color;
@@ -32,7 +30,6 @@
 
 import androidx.annotation.ColorInt;
 
-import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.icons.GraphicsUtils;
@@ -44,8 +41,6 @@
 @SuppressWarnings("NewApi")
 public class Themes {
 
-    public static final String KEY_THEMED_ICONS = "themed_icons";
-
     /** Gets the WallpaperColorHints and then uses those to get the correct activity theme res. */
     public static int getActivityThemeRes(Context context) {
         return getActivityThemeRes(context, WallpaperColorHints.get(context).getHints());
@@ -64,13 +59,6 @@
         }
     }
 
-    /**
-     * Returns true if workspace icon theming is enabled
-     */
-    public static boolean isThemedIconEnabled(Context context) {
-        return LauncherPrefs.get(context).get(THEMED_ICONS);
-    }
-
     public static String getDefaultBodyFont(Context context) {
         TypedArray ta = context.obtainStyledAttributes(android.R.style.TextAppearance_DeviceDefault,
                 new int[]{android.R.attr.fontFamily});
diff --git a/src/com/android/launcher3/util/ViewPool.java b/src/com/android/launcher3/util/ViewPool.java
index 2fa8bf4..1627057 100644
--- a/src/com/android/launcher3/util/ViewPool.java
+++ b/src/com/android/launcher3/util/ViewPool.java
@@ -17,6 +17,7 @@
 
 import android.content.Context;
 import android.os.Handler;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -33,6 +34,7 @@
  * During initialization, views are inflated on the background thread.
  */
 public class ViewPool<T extends View & Reusable> {
+    private static final String TAG = ViewPool.class.getSimpleName();
 
     private final Object[] mPool;
 
@@ -42,6 +44,9 @@
 
     private int mCurrentSize = 0;
 
+    @Nullable
+    private Thread mViewPoolInitThread;
+
     public ViewPool(Context context, @Nullable ViewGroup parent,
             int layoutId, int maxSize, int initialSize) {
         this(LayoutInflater.from(context).cloneInContext(context),
@@ -72,12 +77,15 @@
 
         // Inflate views on a non looper thread. This allows us to catch errors like calling
         // "new Handler()" in constructor easily.
-        new Thread(() -> {
+        mViewPoolInitThread = new Thread(() -> {
             for (int i = 0; i < initialSize; i++) {
                 T view = inflateNewView(inflater);
                 handler.post(() -> addToPool(view));
             }
-        }, "ViewPool-init").start();
+            Log.d(TAG, "initPool complete");
+            mViewPoolInitThread = null;
+        }, "ViewPool-init");
+        mViewPoolInitThread.start();
     }
 
     @UiThread
@@ -114,6 +122,12 @@
         return (T) inflater.inflate(mLayoutId, mParent, false);
     }
 
+    public void killOngoingInitializations() throws InterruptedException {
+        if (mViewPoolInitThread != null) {
+            mViewPoolInitThread.join();
+        }
+    }
+
     /**
      * Interface to indicate that a view is reusable
      */
diff --git a/src/com/android/launcher3/util/WallpaperColorHints.kt b/src/com/android/launcher3/util/WallpaperColorHints.kt
index 11d4c25..29fe31a 100644
--- a/src/com/android/launcher3/util/WallpaperColorHints.kt
+++ b/src/com/android/launcher3/util/WallpaperColorHints.kt
@@ -23,14 +23,21 @@
 import android.content.Context
 import androidx.annotation.MainThread
 import androidx.annotation.VisibleForTesting
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.util.Executors.MAIN_EXECUTOR
 import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
+import javax.inject.Inject
 
 /**
  * This class caches the system's wallpaper color hints for use by other classes as a performance
  * enhancer. It also centralizes all the WallpaperManager color hint code in one location.
  */
-class WallpaperColorHints(private val context: Context) : SafeCloseable {
+@LauncherAppSingleton
+class WallpaperColorHints
+@Inject
+constructor(@ApplicationContext private val context: Context, tracker: DaggerSingletonTracker) {
     var hints: Int = 0
         private set
 
@@ -38,7 +45,6 @@
         get() = context.getSystemService(WallpaperManager::class.java)!!
 
     private val onColorHintsChangedListeners = mutableListOf<OnColorHintListener>()
-    private val onClose: SafeCloseable
 
     init {
         hints = wallpaperManager.getWallpaperColors(FLAG_SYSTEM)?.colorHints ?: 0
@@ -51,7 +57,7 @@
                 MAIN_EXECUTOR.handler,
             )
         }
-        onClose = SafeCloseable {
+        tracker.addCloseable {
             UI_HELPER_EXECUTOR.execute {
                 wallpaperManager.removeOnColorsChangedListener(onColorsChangedListener)
             }
@@ -69,8 +75,6 @@
         }
     }
 
-    override fun close() = onClose.close()
-
     fun registerOnColorHintsChangedListener(listener: OnColorHintListener) {
         onColorHintsChangedListeners.add(listener)
     }
@@ -82,7 +86,7 @@
     companion object {
         @VisibleForTesting
         @JvmField
-        val INSTANCE = MainThreadInitializedObject { WallpaperColorHints(it) }
+        val INSTANCE = DaggerSingletonObject(LauncherAppComponent::getWallpaperColorHints)
 
         @JvmStatic fun get(context: Context): WallpaperColorHints = INSTANCE.get(context)
     }
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index f8cbe0d..26a04a5 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -31,8 +31,7 @@
     // Don't use all the wallpaper for parallax until you have at least this many pages
     private static final int MIN_PARALLAX_PAGE_SPAN = 4;
 
-    private final SimpleBroadcastReceiver mWallpaperChangeReceiver =
-            new SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, i -> onWallpaperChanged());
+    private final SimpleBroadcastReceiver mWallpaperChangeReceiver;
     private final Workspace<?> mWorkspace;
     private final boolean mIsRtl;
     private final Handler mHandler;
@@ -46,6 +45,8 @@
 
     public WallpaperOffsetInterpolator(Workspace<?> workspace) {
         mWorkspace = workspace;
+        mWallpaperChangeReceiver = new SimpleBroadcastReceiver(
+                workspace.getContext(), UI_HELPER_EXECUTOR, i -> onWallpaperChanged());
         mIsRtl = Utilities.isRtl(workspace.getResources());
         mHandler = new OffsetHandler(workspace.getContext());
     }
@@ -198,11 +199,10 @@
     public void setWindowToken(IBinder token) {
         mWindowToken = token;
         if (mWindowToken == null && mRegistered) {
-            mWallpaperChangeReceiver.unregisterReceiverSafely(mWorkspace.getContext());
+            mWallpaperChangeReceiver.unregisterReceiverSafely();
             mRegistered = false;
         } else if (mWindowToken != null && !mRegistered) {
-            mWallpaperChangeReceiver.register(
-                    mWorkspace.getContext(), ACTION_WALLPAPER_CHANGED);
+            mWallpaperChangeReceiver.register(ACTION_WALLPAPER_CHANGED);
             onWallpaperChanged();
             mRegistered = true;
         }
diff --git a/src/com/android/launcher3/util/WallpaperThemeManager.kt b/src/com/android/launcher3/util/WallpaperThemeManager.kt
new file mode 100644
index 0000000..c48ef07
--- /dev/null
+++ b/src/com/android/launcher3/util/WallpaperThemeManager.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.app.Activity
+import android.content.ComponentCallbacks
+import android.content.res.Configuration
+import android.os.Bundle
+import com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE_RECREATE_TO_UPDATE_THEME
+import com.android.launcher3.R
+
+/** Utility class to manage activity's theme in case it is wallpaper dependent */
+class WallpaperThemeManager private constructor(private val activity: Activity) :
+    OnColorHintListener, ActivityLifecycleCallbacksAdapter, ComponentCallbacks {
+
+    private var themeRes: Int = R.style.AppTheme
+
+    private var recreateToUpdateTheme = false
+
+    init {
+        // Update theme
+        WallpaperColorHints.get(activity).registerOnColorHintsChangedListener(this)
+        val expectedTheme = Themes.getActivityThemeRes(activity)
+        if (expectedTheme != themeRes) {
+            themeRes = expectedTheme
+            activity.setTheme(expectedTheme)
+        }
+
+        activity.registerActivityLifecycleCallbacks(this)
+        activity.registerComponentCallbacks(this)
+    }
+
+    override fun onActivitySaveInstanceState(activity: Activity, bundle: Bundle) =
+        bundle.putBoolean(RUNTIME_STATE_RECREATE_TO_UPDATE_THEME, recreateToUpdateTheme)
+
+    override fun onActivityDestroyed(unused: Activity) =
+        WallpaperColorHints.get(activity).unregisterOnColorsChangedListener(this)
+
+    override fun onConfigurationChanged(config: Configuration) = updateTheme()
+
+    override fun onLowMemory() {}
+
+    override fun onColorHintsChanged(colorHints: Int) = updateTheme()
+
+    private fun updateTheme() {
+        if (themeRes != Themes.getActivityThemeRes(activity)) {
+            recreateToUpdateTheme = true
+            activity.recreate()
+        }
+    }
+
+    companion object {
+
+        /**
+         * Sets a wallpaper dependent theme on this activity. The activity is automatically
+         * recreated when a wallpaper change can potentially change the theme.
+         */
+        @JvmStatic
+        fun Activity.setWallpaperDependentTheme() {
+            if (!isDestroyed) {
+                WallpaperThemeManager(this)
+            }
+        }
+    }
+}
diff --git a/src/com/android/launcher3/util/WeakCleanupSet.kt b/src/com/android/launcher3/util/WeakCleanupSet.kt
new file mode 100644
index 0000000..7bf3289
--- /dev/null
+++ b/src/com/android/launcher3/util/WeakCleanupSet.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import java.util.Collections
+import java.util.WeakHashMap
+
+/**
+ * Utility class which maintains a list of cleanup callbacks using weak-references. These callbacks
+ * are called when the [owner] is destroyed, but can also be cleared when the caller is GCed
+ */
+class WeakCleanupSet(owner: LifecycleOwner) {
+
+    private val callbacks = Collections.newSetFromMap<OnOwnerDestroyedCallback>(WeakHashMap())
+    private var destroyed = false
+
+    init {
+        MAIN_EXECUTOR.execute {
+            owner.lifecycle.addObserver(
+                object : DefaultLifecycleObserver {
+
+                    override fun onDestroy(owner: LifecycleOwner) {
+                        destroyed = true
+                        callbacks.forEach { it.onOwnerDestroyed() }
+                    }
+                }
+            )
+        }
+    }
+
+    fun addOnOwnerDestroyedCallback(callback: OnOwnerDestroyedCallback) {
+        if (destroyed) callback.onOwnerDestroyed() else callbacks.add(callback)
+    }
+
+    /** Callback when the owner is destroyed */
+    interface OnOwnerDestroyedCallback {
+        fun onOwnerDestroyed()
+    }
+}
diff --git a/src/com/android/launcher3/util/coroutines/DispatcherProvider.kt b/src/com/android/launcher3/util/coroutines/DispatcherProvider.kt
index 8877535..1f01b07 100644
--- a/src/com/android/launcher3/util/coroutines/DispatcherProvider.kt
+++ b/src/com/android/launcher3/util/coroutines/DispatcherProvider.kt
@@ -33,7 +33,7 @@
 
     override val default: CoroutineDispatcher = Dispatchers.Default
     override val background: CoroutineDispatcher = bgDispatcher
-    override val main: CoroutineDispatcher = Dispatchers.Main
+    override val main: CoroutineDispatcher = Dispatchers.Main.immediate
     override val unconfined: CoroutineDispatcher = Dispatchers.Unconfined
 }
 
diff --git a/src/com/android/launcher3/util/rects/Rects.kt b/src/com/android/launcher3/util/rects/Rects.kt
index 1e6d717..2f1942a 100644
--- a/src/com/android/launcher3/util/rects/Rects.kt
+++ b/src/com/android/launcher3/util/rects/Rects.kt
@@ -18,6 +18,24 @@
 
 import android.graphics.Rect
 import android.view.View
+import com.android.launcher3.Utilities
+
+/**
+ * Linearly interpolate between two rectangles. The result is stored in the rect the function is
+ * called on.
+ *
+ * @param start the starting rectangle
+ * @param end the ending rectangle
+ * @param t the interpolation factor, where 0 is the start and 1 is the end
+ */
+fun Rect.lerpRect(start: Rect, end: Rect, t: Float) {
+    set(
+        Utilities.mapRange(t, start.left.toFloat(), end.left.toFloat()).toInt(),
+        Utilities.mapRange(t, start.top.toFloat(), end.top.toFloat()).toInt(),
+        Utilities.mapRange(t, start.right.toFloat(), end.right.toFloat()).toInt(),
+        Utilities.mapRange(t, start.bottom.toFloat(), end.bottom.toFloat()).toInt(),
+    )
+}
 
 /** Copy the coordinates of the [view] relative to its parent into this rectangle. */
 fun Rect.set(view: View) {
diff --git a/src/com/android/launcher3/util/window/WindowManagerProxy.java b/src/com/android/launcher3/util/window/WindowManagerProxy.java
index 84b4a36..68e0324 100644
--- a/src/com/android/launcher3/util/window/WindowManagerProxy.java
+++ b/src/com/android/launcher3/util/window/WindowManagerProxy.java
@@ -27,7 +27,6 @@
 import static com.android.launcher3.testing.shared.ResourceUtils.STATUS_BAR_HEIGHT;
 import static com.android.launcher3.testing.shared.ResourceUtils.STATUS_BAR_HEIGHT_LANDSCAPE;
 import static com.android.launcher3.testing.shared.ResourceUtils.STATUS_BAR_HEIGHT_PORTRAIT;
-import static com.android.launcher3.util.MainThreadInitializedObject.forOverride;
 import static com.android.launcher3.util.RotationUtils.deltaRotation;
 import static com.android.launcher3.util.RotationUtils.rotateRect;
 import static com.android.launcher3.util.RotationUtils.rotateSize;
@@ -52,37 +51,33 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.R;
+import com.android.launcher3.dagger.LauncherAppSingleton;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
 import com.android.launcher3.testing.shared.ResourceUtils;
-import com.android.launcher3.util.MainThreadInitializedObject;
+import com.android.launcher3.util.DaggerSingletonObject;
 import com.android.launcher3.util.NavigationMode;
-import com.android.launcher3.util.ResourceBasedOverride;
-import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.WindowBounds;
 
 import java.util.ArrayList;
 import java.util.List;
 
+import javax.inject.Inject;
+
 /**
  * Utility class for mocking some window manager behaviours
  */
-public class WindowManagerProxy implements ResourceBasedOverride, SafeCloseable {
+@LauncherAppSingleton
+public class WindowManagerProxy {
 
     private static final String TAG = "WindowManagerProxy";
     public static final int MIN_TABLET_WIDTH = 600;
 
-    public static final MainThreadInitializedObject<WindowManagerProxy> INSTANCE =
-            forOverride(WindowManagerProxy.class, R.string.window_manager_proxy_class);
+    public static final DaggerSingletonObject<WindowManagerProxy> INSTANCE =
+            new DaggerSingletonObject<>(LauncherBaseAppComponent::getWmProxy);
 
     protected final boolean mTaskbarDrawnInProcess;
 
-    /**
-     * Creates a new instance of proxy, applying any overrides
-     */
-    public static WindowManagerProxy newInstance(Context context) {
-        return Overrides.getObject(WindowManagerProxy.class, context,
-                R.string.window_manager_proxy_class);
-    }
-
+    @Inject
     public WindowManagerProxy() {
         this(false);
     }
@@ -114,7 +109,7 @@
     /**
      * Returns if we are in desktop mode or not.
      */
-    public boolean isInDesktopMode() {
+    public boolean isInDesktopMode(int displayId) {
         return false;
     }
 
@@ -126,6 +121,14 @@
     }
 
     /**
+     * Returns whether the display is a freeform display for which taskbar should be pinned
+     * and showing desktop tasks.
+     */
+    public boolean showDesktopTaskbarForFreeformDisplay(Context displayInfoContext) {
+        return false;
+    }
+
+    /**
      * Returns if the home is visible.
      */
     public boolean isHomeVisible(Context context) {
@@ -483,9 +486,6 @@
         return NavigationMode.NO_BUTTON;
     }
 
-    @Override
-    public void close() { }
-
     /**
      * @see DisplayCutout#getSafeInsets
      */
@@ -493,4 +493,60 @@
         return new Rect(cutout.getSafeInsetLeft(), cutout.getSafeInsetTop(),
                 cutout.getSafeInsetRight(), cutout.getSafeInsetBottom());
     }
+
+    /** Registers a listener for Taskbar changes in Desktop Mode.  */
+    public void registerDesktopVisibilityListener(DesktopVisibilityListener listener) { }
+
+    /** Removes a previously registered listener for Taskbar changes in Desktop Mode.  */
+    public void unregisterDesktopVisibilityListener(DesktopVisibilityListener listener) { }
+
+    /** A listener for when the user enters/exits Desktop Mode.  */
+    public interface DesktopVisibilityListener {
+        /**
+         * Called when the desktop mode state on the display whose ID is `displayId` changes.
+         *
+         * @param displayId The ID of the display for which this notification is triggering.
+         * @param isInDesktopModeAndNotInOverview True if a desktop is currently active on the given
+         *                                        display, and Overview is currently inactive.
+         */
+        default void onIsInDesktopModeChanged(int displayId,
+                boolean isInDesktopModeAndNotInOverview) {
+        }
+
+        /**
+         * Called whenever the conditions that allow the creation of desks change.
+         *
+         * @param canCreateDesks whether it is possible to create new desks.
+         */
+        default void onCanCreateDesksChanged(boolean canCreateDesks) {
+        }
+
+        /**
+         * Called when a new desk is added.
+         *
+         * @param displayId The ID of the display on which the desk was added.
+         * @param deskId The ID of the newly added desk.
+         */
+        default void onDeskAdded(int displayId, int deskId) {}
+
+        /**
+         * Called when an existing desk is removed.
+         *
+         * @param displayId The ID of the display on which the desk was removed.
+         * @param deskId The ID of the desk that was removed.
+         */
+        default void onDeskRemoved(int displayId, int deskId) {}
+
+        /**
+         * Called when the active desk changes.
+         *
+         * @param displayId The ID of the display on which the desk activation change is happening.
+         * @param newActiveDesk The ID of the new active desk or -1 if no desk is active anymore
+         *                      (i.e. exit desktop mode).
+         * @param oldActiveDesk The ID of the desk that was previously active, or -1 if no desk was
+         *                      active before.
+         */
+        default void onActiveDeskChanged(int displayId, int newActiveDesk, int oldActiveDesk) {}
+    }
+
 }
diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java
index b8481c5..cbf5341 100644
--- a/src/com/android/launcher3/views/ActivityContext.java
+++ b/src/com/android/launcher3/views/ActivityContext.java
@@ -43,7 +43,6 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.util.Log;
-import android.view.ContextThemeWrapper;
 import android.view.Display;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -57,11 +56,13 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.core.view.WindowInsetsCompat;
+import androidx.savedstate.SavedStateRegistryOwner;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.DropTargetHandler;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
@@ -80,11 +81,13 @@
 import com.android.launcher3.popup.PopupDataProvider;
 import com.android.launcher3.util.ActivityOptionsWrapper;
 import com.android.launcher3.util.ApplicationInfoWrapper;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SystemUiController;
 import com.android.launcher3.util.ViewCache;
+import com.android.launcher3.util.WeakCleanupSet;
 import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
 
 import java.util.List;
@@ -93,7 +96,7 @@
  * An interface to be used along with a context for various activities in Launcher. This allows a
  * generic class to depend on Context subclass instead of an Activity.
  */
-public interface ActivityContext {
+public interface ActivityContext extends SavedStateRegistryOwner {
 
     String TAG = "ActivityContext";
 
@@ -101,17 +104,6 @@
         return false;
     }
 
-    default DotInfo getDotInfoForItem(ItemInfo info) {
-        return null;
-    }
-
-    /**
-     * For items with tree hierarchy, notifies the activity to invalidate the parent when a root
-     * is invalidated
-     * @param info info associated with a root node.
-     */
-    default void invalidateParent(ItemInfo info) { }
-
     default AccessibilityDelegate getAccessibilityDelegate() {
         return null;
     }
@@ -173,6 +165,11 @@
         return false;
     }
 
+    /** Returns the RootView */
+    default View getRootView() {
+        return getDragLayer();
+    }
+
     /**
      * The root view to support drag-and-drop and popup support.
      */
@@ -196,6 +193,14 @@
     }
 
     /**
+     * Returns the primary content of this context
+     */
+    @NonNull
+    default LauncherBindableItemsContainer getContent() {
+        return op -> null;
+    }
+
+    /**
      * The all apps container, if it exists in this context.
      */
     default ActivityAllAppsContainerView<?> getAppsView() {
@@ -226,9 +231,7 @@
         getOnDeviceProfileChangeListeners().remove(listener);
     }
 
-    default ViewCache getViewCache() {
-        return new ViewCache();
-    }
+    ViewCache getViewCache();
 
     /**
      * Controller for supporting item drag-and-drop
@@ -272,11 +275,6 @@
      */
     default void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) { }
 
-    /** Returns {@code true} if items are currently being bound within this context. */
-    default boolean isBindingItems() {
-        return false;
-    }
-
     default View.OnClickListener getItemOnClickListener() {
         return v -> {
             // No op.
@@ -288,9 +286,13 @@
         return v -> false;
     }
 
-    @Nullable
+    @NonNull
     default PopupDataProvider getPopupDataProvider() {
-        return null;
+        return new PopupDataProvider(this);
+    }
+
+    default DotInfo getDotInfoForItem(ItemInfo info) {
+        return getPopupDataProvider().getDotInfoForItem(info);
     }
 
     /**
@@ -417,7 +419,8 @@
             View v, Intent intent, @Nullable ItemInfo item) {
         Preconditions.assertUIThread();
         Context context = (Context) this;
-        if (isAppBlockedForSafeMode() && !new ApplicationInfoWrapper(context, intent).isSystem()) {
+        if (LauncherAppState.getInstance(context).isSafeModeEnabled()
+                && !new ApplicationInfoWrapper(context, intent).isSystem()) {
             Toast.makeText(context, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
             return null;
         }
@@ -463,11 +466,6 @@
         return null;
     }
 
-    /** Returns {@code true} if an app launch is blocked due to safe mode. */
-    default boolean isAppBlockedForSafeMode() {
-        return false;
-    }
-
     /**
      * Creates and logs a new app launch event.
      */
@@ -483,6 +481,7 @@
      * @param v View initiating a launch.
      * @param item Item associated with the view.
      */
+    @NonNull
     default ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
         int left = 0, top = 0;
         int width = v.getMeasuredWidth(), height = v.getMeasuredHeight();
@@ -522,6 +521,9 @@
         return new CellPosMapper(dp.isVerticalBarLayout(), dp.numShownHotseatIcons);
     }
 
+    /** Set to manage objects that can be cleaned up along with the context */
+    WeakCleanupSet getOwnerCleanupSet();
+
     /** Whether bubbles are enabled. */
     default boolean isBubbleBarEnabled() {
         return false;
@@ -532,6 +534,11 @@
         return false;
     }
 
+    /** Returns the current ActivityContext as context */
+    default Context asContext() {
+        return (Context) this;
+    }
+
     /**
      * Returns the ActivityContext associated with the given Context, or throws an exception if
      * the Context is not associated with any ActivityContext.
@@ -551,21 +558,10 @@
     static <T extends Context & ActivityContext> T lookupContextNoThrow(Context context) {
         if (context instanceof ActivityContext) {
             return (T) context;
-        } else if (context instanceof ActivityContextDelegate acd) {
-            return (T) acd.mDelegate;
-        } else if (context instanceof ContextWrapper) {
-            return lookupContextNoThrow(((ContextWrapper) context).getBaseContext());
+        } else if (context instanceof ContextWrapper cw) {
+            return lookupContextNoThrow(cw.getBaseContext());
         } else {
             return null;
         }
     }
-
-    class ActivityContextDelegate extends ContextThemeWrapper {
-        public final ActivityContext mDelegate;
-
-        public ActivityContextDelegate(Context base, int themeResId, ActivityContext delegate) {
-            super(base, themeResId);
-            mDelegate = delegate;
-        }
-    }
 }
diff --git a/src/com/android/launcher3/views/ArrowTipView.java b/src/com/android/launcher3/views/ArrowTipView.java
index bb4f040..abb0081 100644
--- a/src/com/android/launcher3/views/ArrowTipView.java
+++ b/src/com/android/launcher3/views/ArrowTipView.java
@@ -73,7 +73,7 @@
                 }
             };
 
-    private final ActivityContext mActivityContext;
+    protected final ActivityContext mActivityContext;
     private final Handler mHandler = new Handler();
     private boolean mIsPointingUp;
     private Runnable mOnClosed;
@@ -103,16 +103,26 @@
                 R.dimen.arrow_toast_arrow_width);
         mArrowMinOffset = context.getResources().getDimensionPixelSize(
                 R.dimen.dynamic_grid_cell_border_spacing);
-        TypedArray ta = context.obtainStyledAttributes(R.styleable.ArrowTipView);
+        Context localContext = context;
+        TypedArray ta = localContext.obtainStyledAttributes(R.styleable.ArrowTipView);
         // Set style to default to avoid inflation issues with missing attributes.
         if (!ta.hasValue(R.styleable.ArrowTipView_arrowTipBackground)
                 || !ta.hasValue(R.styleable.ArrowTipView_arrowTipTextColor)) {
-            context = new ContextThemeWrapper(context, R.style.ArrowTipStyle);
+            localContext = new ContextThemeWrapper(localContext, R.style.ArrowTipStyle);
         }
-        mArrowViewPaintColor = ta.getColor(R.styleable.ArrowTipView_arrowTipBackground,
+        mArrowViewPaintColor = applyArrowPaintColor(ta, localContext);
+        init(localContext, layoutId);
+    }
+
+    protected int applyArrowPaintColor(TypedArray typedArray, Context context) {
+        int arrowPaintColor = typedArray.getColor(R.styleable.ArrowTipView_arrowTipBackground,
                 context.getColor(R.color.arrow_tip_view_bg));
-        ta.recycle();
-        init(context, layoutId);
+        typedArray.recycle();
+        return arrowPaintColor;
+    }
+
+    protected int getArrowId() {
+        return R.id.arrow;
     }
 
     @Override
@@ -154,7 +164,7 @@
         inflate(context, layoutId, this);
         setOrientation(LinearLayout.VERTICAL);
 
-        mArrowView = findViewById(R.id.arrow);
+        mArrowView = findViewById(getArrowId());
         updateArrowTipInView(mIsPointingUp);
         setAlpha(0);
 
@@ -343,6 +353,34 @@
             parent.addView(this);
             requestLayout();
         }
+        return showAtLocation(arrowXCoord, yCoordDownPointingTip, yCoordUpPointingTip,
+                minViewMargin, parentViewWidth, parentViewHeight, shouldAutoClose);
+    }
+
+    /**
+     * Show the ArrowTipView (tooltip) custom aligned. The tooltip is vertically flipped if it
+     * cannot fit on screen in the requested orientation.
+     *
+     * @param arrowXCoord The X coordinate for the arrow on the tooltip. The arrow is usually in the
+     *                    center of tooltip unless the tooltip goes beyond screen margin.
+     * @param yCoordDownPointingTip The Y coordinate of the pointed tip end of the tooltip when the
+     *                              tooltip is placed pointing downwards.
+     * @param yCoordUpPointingTip The Y coordinate of the pointed tip end of the tooltip when the
+     *                            tooltip is placed pointing upwards.
+     * @param minViewMargin The view margin in pixels from the tip end to the y coordinate.
+     * @param parentViewWidth The width in pixels of the parent view.
+     * @param parentViewHeight The height in pixels of the parent view.
+     * @param shouldAutoClose If Tooltip should be auto close.
+     * @return The tool tip view. {@code null} if the tip can not be shown.
+     */
+    protected ArrowTipView showAtLocation(
+            @Px int arrowXCoord,
+            @Px int yCoordDownPointingTip,
+            @Px int yCoordUpPointingTip,
+            @Px int minViewMargin,
+            @Px int parentViewWidth,
+            @Px int parentViewHeight,
+            boolean shouldAutoClose) {
 
         post(() -> {
             // Adjust the tooltip horizontally.
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
index f90a3e4..a295d6b 100644
--- a/src/com/android/launcher3/views/ClipIconView.java
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -20,6 +20,7 @@
 import static com.android.launcher3.Utilities.boundToRange;
 import static com.android.launcher3.Utilities.mapToRange;
 import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
+import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 
 import static java.lang.Math.max;
@@ -44,10 +45,11 @@
 import androidx.core.util.Consumer;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
-import com.android.launcher3.graphics.IconShape;
+import com.android.launcher3.graphics.ThemeManager;
 
 /**
  * A view used to draw both layers of an {@link AdaptiveIconDrawable}.
@@ -67,6 +69,7 @@
     private boolean mIsAdaptiveIcon = false;
 
     private ValueAnimator mRevealAnimator;
+    private float mIconScale;
 
     private final Rect mStartRevealRect = new Rect();
     private final Rect mEndRevealRect = new Rect();
@@ -172,9 +175,12 @@
 
         mTaskCornerRadius = cornerRadius / scale;
         if (mIsAdaptiveIcon) {
-            if (!isOpening && progress >= shapeProgressStart) {
+            final ThemeManager themeManager = ThemeManager.INSTANCE.get(getContext());
+            mIconScale = themeManager.getIconState().getIconScale();
+            if ((!isOpening || Flags.enableLauncherIconShapes())
+                    && progress >= shapeProgressStart) {
                 if (mRevealAnimator == null) {
-                    mRevealAnimator = IconShape.INSTANCE.get(getContext()).getShape()
+                    mRevealAnimator = themeManager.getIconShape()
                             .createRevealAnimator(this, mStartRevealRect,
                                     mOutline, mTaskCornerRadius, !isOpening);
                     mRevealAnimator.addListener(forEndCallback(() -> mRevealAnimator = null));
@@ -258,8 +264,7 @@
             mStartRevealRect.set(0, 0, originalWidth, originalHeight);
 
             if (!isFolderIcon) {
-                Utilities.scaleRectAboutCenter(mStartRevealRect,
-                        IconShape.INSTANCE.get(getContext()).getNormalizationScale());
+                Utilities.scaleRectAboutCenter(mStartRevealRect, ICON_VISIBLE_AREA_FACTOR);
             }
 
             if (dp.isLandscape) {
@@ -309,17 +314,24 @@
 
     @Override
     public void draw(Canvas canvas) {
-        int count = canvas.save();
+        int count1 = canvas.save();
         if (mClipPath != null) {
             canvas.clipPath(mClipPath);
         }
-        super.draw(canvas);
+        int count2 = canvas.save();
+        float iconCenterX =
+                (mFinalDrawableBounds.right - mFinalDrawableBounds.left) / 2f * mIconScale;
+        float iconCenterY =
+                (mFinalDrawableBounds.bottom - mFinalDrawableBounds.top) / 2f * mIconScale;
+        canvas.scale(mIconScale, mIconScale, iconCenterX, iconCenterY);
         if (mBackground != null) {
             mBackground.draw(canvas);
         }
         if (mForeground != null) {
             mForeground.draw(canvas);
         }
+        canvas.restoreToCount(count2);
+        super.draw(canvas);
         if (mTaskViewArtist != null) {
             canvas.saveLayerAlpha(
                     0,
@@ -333,7 +345,7 @@
             canvas.scale(drawScale, drawScale);
             mTaskViewArtist.taskViewDrawCallback.accept(canvas);
         }
-        canvas.restoreToCount(count);
+        canvas.restoreToCount(count1);
     }
 
     void recycle() {
diff --git a/src/com/android/launcher3/views/ComposeInitializer.java b/src/com/android/launcher3/views/ComposeInitializer.java
deleted file mode 100644
index 0929885..0000000
--- a/src/com/android/launcher3/views/ComposeInitializer.java
+++ /dev/null
@@ -1,229 +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.views;
-
-import android.os.Build;
-import android.view.View;
-import android.view.ViewParent;
-import android.view.ViewTreeObserver;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RequiresApi;
-import androidx.lifecycle.Lifecycle;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LifecycleRegistry;
-import androidx.lifecycle.ViewTreeLifecycleOwner;
-import androidx.savedstate.SavedStateRegistry;
-import androidx.savedstate.SavedStateRegistryController;
-import androidx.savedstate.SavedStateRegistryOwner;
-import androidx.savedstate.ViewTreeSavedStateRegistryOwner;
-
-import com.android.launcher3.Utilities;
-
-/**
- * An initializer to use Compose for classes implementing {@code ActivityContext}. This allows
- * adding ComposeView to ViewTree outside a {@link androidx.activity.ComponentActivity}.
- */
-public final class ComposeInitializer {
-    /**
-     * Performs the initialization to use Compose in the ViewTree of {@code target}.
-     */
-    public static void initCompose(ActivityContext target) {
-        getContentChild(target).addOnAttachStateChangeListener(
-                new View.OnAttachStateChangeListener() {
-
-                    @Override
-                    public void onViewAttachedToWindow(View v) {
-                        ComposeInitializer.onAttachedToWindow(v);
-                    }
-
-                    @Override
-                    public void onViewDetachedFromWindow(View v) {
-                        ComposeInitializer.onDetachedFromWindow(v);
-                    }
-                });
-    }
-
-    /**
-     * Find the "content child" for {@code target}.
-     *
-     * @see "WindowRecomposer.android.kt: [View.contentChild]"
-     */
-    private static View getContentChild(ActivityContext target) {
-        View self = target.getDragLayer();
-        ViewParent parent = self.getParent();
-        while (parent instanceof View parentView) {
-            if (parentView.getId() == android.R.id.content) return self;
-            self = parentView;
-            parent = self.getParent();
-        }
-        return self;
-    }
-
-    /**
-     * Function to be called on your window root view's [View.onAttachedToWindow] function.
-     */
-    private static void onAttachedToWindow(View root) {
-        if (ViewTreeLifecycleOwner.get(root) != null) {
-            throw new IllegalStateException(
-                    "View " + root + " already has a LifecycleOwner");
-        }
-
-        ViewParent parent = root.getParent();
-        if (parent instanceof View && ((View) parent).getId() != android.R.id.content) {
-            throw new IllegalStateException(
-                    "ComposeInitializer.onContentChildAttachedToWindow(View) must be called on "
-                            + "the content child. Outside of activities and dialogs, this is "
-                            + "usually the top-most View of a window.");
-        }
-
-        // The lifecycle owner, which is STARTED when [root] is visible and RESUMED when [root]
-        // is both visible and focused.
-        ViewLifecycleOwner lifecycleOwner = new ViewLifecycleOwner(root);
-
-        // We must call [ViewLifecycleOwner.onCreate] after creating the
-        // [SavedStateRegistryOwner] because `onCreate` might move the lifecycle state to STARTED
-        // which will make [SavedStateRegistryController.performRestore] throw.
-        lifecycleOwner.onCreate();
-
-        // Set the owners on the root. They will be reused by any ComposeView inside the root
-        // hierarchy.
-        ViewTreeLifecycleOwner.set(root, lifecycleOwner);
-        ViewTreeSavedStateRegistryOwner.set(root, lifecycleOwner);
-    }
-
-    /**
-     * Function to be called on your window root view's [View.onDetachedFromWindow] function.
-     */
-    private static void onDetachedFromWindow(View root) {
-        final LifecycleOwner lifecycleOwner = ViewTreeLifecycleOwner.get(root);
-        if (lifecycleOwner != null) {
-            ((ViewLifecycleOwner) lifecycleOwner).onDestroy();
-        }
-        ViewTreeLifecycleOwner.set(root, null);
-        ViewTreeSavedStateRegistryOwner.set(root, null);
-    }
-
-    /**
-     * A [LifecycleOwner] for a [View] that updates lifecycle state based on window state.
-     *
-     * Also a trivial implementation of [SavedStateRegistryOwner] that does not do any save or
-     * restore. This works for processes similar to the SystemUI process, which is always running
-     * and top-level windows using this initialization are created once, when the process is
-     * started.
-     *
-     * The implementation requires the caller to call [onCreate] and [onDestroy] when the view is
-     * attached to or detached from a view hierarchy. After [onCreate] and before [onDestroy] is
-     * called, the implementation monitors window state in the following way
-     * * If the window is not visible, we are in the [Lifecycle.State.CREATED] state
-     * * If the window is visible but not focused, we are in the [Lifecycle.State.STARTED] state
-     * * If the window is visible and focused, we are in the [Lifecycle.State.RESUMED] state
-     *
-     * Or in table format:
-     * ```
-     * ┌───────────────┬───────────────────┬──────────────┬─────────────────┐
-     * │ View attached │ Window Visibility │ Window Focus │ Lifecycle State │
-     * ├───────────────┼───────────────────┴──────────────┼─────────────────┤
-     * │ Not attached  │                 Any              │       N/A       │
-     * ├───────────────┼───────────────────┬──────────────┼─────────────────┤
-     * │               │    Not visible    │     Any      │     CREATED     │
-     * │               ├───────────────────┼──────────────┼─────────────────┤
-     * │   Attached    │                   │   No focus   │     STARTED     │
-     * │               │      Visible      ├──────────────┼─────────────────┤
-     * │               │                   │  Has focus   │     RESUMED     │
-     * └───────────────┴───────────────────┴──────────────┴─────────────────┘
-     * ```
-     */
-    private static class ViewLifecycleOwner implements SavedStateRegistryOwner {
-        private final ViewTreeObserver.OnWindowFocusChangeListener mWindowFocusListener =
-                hasFocus -> updateState();
-        private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
-
-        private final SavedStateRegistryController mSavedStateRegistryController =
-                SavedStateRegistryController.create(this);
-
-        private final View mView;
-        private final Api34Impl mApi34Impl;
-
-        ViewLifecycleOwner(View view) {
-            mView = view;
-            if (Utilities.ATLEAST_U) {
-                mApi34Impl = new Api34Impl();
-            } else {
-                mApi34Impl = null;
-            }
-
-            mSavedStateRegistryController.performRestore(null);
-        }
-
-        @NonNull
-        @Override
-        public Lifecycle getLifecycle() {
-            return mLifecycleRegistry;
-        }
-
-        @NonNull
-        @Override
-        public SavedStateRegistry getSavedStateRegistry() {
-            return mSavedStateRegistryController.getSavedStateRegistry();
-        }
-
-        void onCreate() {
-            mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
-            if (Utilities.ATLEAST_U) {
-                mApi34Impl.addOnWindowVisibilityChangeListener();
-            }
-            mView.getViewTreeObserver().addOnWindowFocusChangeListener(
-                    mWindowFocusListener);
-            updateState();
-        }
-
-        void onDestroy() {
-            if (Utilities.ATLEAST_U) {
-                mApi34Impl.removeOnWindowVisibilityChangeListener();
-            }
-            mView.getViewTreeObserver().removeOnWindowFocusChangeListener(
-                    mWindowFocusListener);
-            mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
-        }
-
-        private void updateState() {
-            Lifecycle.State state =
-                    mView.getWindowVisibility() != View.VISIBLE ? Lifecycle.State.CREATED
-                            : (!mView.hasWindowFocus() ? Lifecycle.State.STARTED
-                                    : Lifecycle.State.RESUMED);
-            mLifecycleRegistry.setCurrentState(state);
-        }
-
-        @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
-        private class Api34Impl {
-            private final ViewTreeObserver.OnWindowVisibilityChangeListener
-                    mWindowVisibilityListener =
-                    visibility -> updateState();
-
-            void addOnWindowVisibilityChangeListener() {
-                mView.getViewTreeObserver().addOnWindowVisibilityChangeListener(
-                        mWindowVisibilityListener);
-            }
-
-            void removeOnWindowVisibilityChangeListener() {
-                mView.getViewTreeObserver().removeOnWindowVisibilityChangeListener(
-                        mWindowVisibilityListener);
-            }
-        }
-    }
-}
diff --git a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
index 392d9a7..05bc4d8 100644
--- a/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
+++ b/src/com/android/launcher3/views/DoubleShadowBubbleTextView.java
@@ -25,6 +25,7 @@
 import android.os.Build;
 import android.text.Spannable;
 import android.text.SpannableString;
+import android.text.TextUtils;
 import android.text.style.ImageSpan;
 import android.util.AttributeSet;
 import android.util.Log;
@@ -102,7 +103,7 @@
 
     @Override
     public void onDraw(Canvas canvas) {
-        if (shouldDrawAppContrastTile()) {
+        if (shouldDrawAppContrastTile() && !TextUtils.isEmpty(getText())) {
             drawAppContrastTile(canvas);
         }
         // If text is transparent or shadow alpha is 0, don't draw any shadow
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 6739387..5b3abc3 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -55,7 +55,7 @@
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.icons.FastBitmapDrawable;
-import com.android.launcher3.icons.LauncherIcons;
+import com.android.launcher3.icons.IconNormalizer;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.popup.SystemShortcut;
@@ -463,11 +463,7 @@
         Rect bounds = new Rect(0, 0, (int) position.width() + blurSizeOutline,
                 (int) position.height() + blurSizeOutline);
         bounds.inset(blurSizeOutline / 2, blurSizeOutline / 2);
-
-        try (LauncherIcons li = LauncherIcons.obtain(l)) {
-            Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(drawable, null,
-                    null, null));
-        }
+        Utilities.scaleRectAboutCenter(bounds, IconNormalizer.ICON_VISIBLE_AREA_FACTOR);
 
         bounds.inset(
                 (int) (-bounds.width() * AdaptiveIconDrawable.getExtraInsetFraction()),
diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java
index 5f8e2c0..a4055b6 100644
--- a/src/com/android/launcher3/views/FloatingSurfaceView.java
+++ b/src/com/android/launcher3/views/FloatingSurfaceView.java
@@ -159,9 +159,8 @@
         if (mContract == null) {
             return;
         }
-        View icon = mLauncher.getFirstMatchForAppClose(null /* StableViewInfo */,
-                mContract.componentName.getPackageName(), mContract.user,
-                false /* supportsAllAppsState */);
+        View icon = mLauncher.getFirstHomeElementForAppClose(null /* StableViewInfo */,
+                mContract.componentName.getPackageName(), mContract.user);
 
         boolean iconChanged = mIcon != icon;
         if (iconChanged) {
diff --git a/src/com/android/launcher3/widget/BaseWidgetSheet.java b/src/com/android/launcher3/widget/BaseWidgetSheet.java
index fda5175..af31276 100644
--- a/src/com/android/launcher3/widget/BaseWidgetSheet.java
+++ b/src/com/android/launcher3/widget/BaseWidgetSheet.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.widget;
 
 import static com.android.app.animation.Interpolators.EMPHASIZED;
-import static com.android.launcher3.Flags.enableWidgetTapToAdd;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGET_ADD_BUTTON_TAP;
@@ -150,40 +149,36 @@
             return;
         }
 
-        if (enableWidgetTapToAdd()) {
-            scrollToWidgetCell(wc);
+        scrollToWidgetCell(wc);
 
-            if (mWidgetCellWithAddButton != null) {
-                if (mWidgetCellWithAddButton.isShowingAddButton()) {
-                    // If there is a add button currently showing, hide it.
-                    mWidgetCellWithAddButton.hideAddButton(/* animate= */ true);
-                } else {
-                    // The last recorded widget cell to show an add button is no longer showing it,
-                    // likely because the widget cell has been recycled or lost focus. If this is
-                    // the cell that has been clicked, we will show it below.
-                    mWidgetCellWithAddButton = null;
-                }
-            }
-
-            if (mWidgetCellWithAddButton != wc) {
-                // If click is on a cell not showing an add button, show it now.
-                final PendingAddItemInfo info = (PendingAddItemInfo) wc.getTag();
-                if (mActivityContext instanceof Launcher) {
-                    wc.showAddButton((view) -> addWidget(info));
-                } else {
-                    wc.showAddButton((view) -> mActivityContext.getItemOnClickListener()
-                            .onClick(wc));
-                }
-            }
-
-            mWidgetCellWithAddButton = mWidgetCellWithAddButton != wc ? wc : null;
-            if (mWidgetCellWithAddButton != null) {
-                mLastSelectedWidgetItem = mWidgetCellWithAddButton.getWidgetItem();
+        if (mWidgetCellWithAddButton != null) {
+            if (mWidgetCellWithAddButton.isShowingAddButton()) {
+                // If there is a add button currently showing, hide it.
+                mWidgetCellWithAddButton.hideAddButton(/* animate= */ true);
             } else {
-                mLastSelectedWidgetItem = null;
+                // The last recorded widget cell to show an add button is no longer showing it,
+                // likely because the widget cell has been recycled or lost focus. If this is
+                // the cell that has been clicked, we will show it below.
+                mWidgetCellWithAddButton = null;
             }
+        }
+
+        if (mWidgetCellWithAddButton != wc) {
+            // If click is on a cell not showing an add button, show it now.
+            final PendingAddItemInfo info = (PendingAddItemInfo) wc.getTag();
+            if (mActivityContext instanceof Launcher) {
+                wc.showAddButton((view) -> addWidget(info));
+            } else {
+                wc.showAddButton((view) -> mActivityContext.getItemOnClickListener()
+                        .onClick(wc));
+            }
+        }
+
+        mWidgetCellWithAddButton = mWidgetCellWithAddButton != wc ? wc : null;
+        if (mWidgetCellWithAddButton != null) {
+            mLastSelectedWidgetItem = mWidgetCellWithAddButton.getWidgetItem();
         } else {
-            mActivityContext.getItemOnClickListener().onClick(wc);
+            mLastSelectedWidgetItem = null;
         }
     }
 
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index b07d807..7a44c6a 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -79,7 +79,8 @@
     private Runnable mAutoAdvanceRunnable;
 
     private long mDeferUpdatesUntilMillis = 0;
-    RemoteViews mLastRemoteViews;
+    private RemoteViews mLastRemoteViews;
+    private boolean mReapplyOnResumeUpdates = false;
 
     private boolean mTrackingWidgetUpdate = false;
 
@@ -138,11 +139,11 @@
                     TRACE_METHOD_NAME + getAppWidgetInfo().provider, getAppWidgetId());
             mTrackingWidgetUpdate = false;
         }
-        if (isDeferringUpdates()) {
-            mLastRemoteViews = remoteViews;
+        mLastRemoteViews = remoteViews;
+        mReapplyOnResumeUpdates = isDeferringUpdates();
+        if (mReapplyOnResumeUpdates) {
             return;
         }
-        mLastRemoteViews = null;
 
         super.updateAppWidget(remoteViews);
 
@@ -150,6 +151,18 @@
         checkIfAutoAdvance();
     }
 
+    @Override
+    public void onViewAdded(View child) {
+        super.onViewAdded(child);
+        mReapplyOnResumeUpdates |= isDeferringUpdates();
+    }
+
+    @Override
+    public void onViewRemoved(View child) {
+        super.onViewRemoved(child);
+        mReapplyOnResumeUpdates |= isDeferringUpdates();
+    }
+
     private boolean checkScrollableRecursively(ViewGroup viewGroup) {
         if (viewGroup instanceof AdapterView) {
             return true;
@@ -204,18 +217,16 @@
      * {@link #updateAppWidget} and apply any deferred updates.
      */
     public void endDeferringUpdates() {
-        RemoteViews remoteViews;
         mDeferUpdatesUntilMillis = 0;
-        remoteViews = mLastRemoteViews;
-
-        if (remoteViews != null) {
-            updateAppWidget(remoteViews);
+        if (mReapplyOnResumeUpdates) {
+            updateAppWidget(mLastRemoteViews);
         }
     }
 
+    @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            BaseDragLayer dragLayer = mActivityContext.getDragLayer();
+            BaseDragLayer<?> dragLayer = mActivityContext.getDragLayer();
             if (mIsScrollable) {
                 dragLayer.requestDisallowInterceptTouchEvent(true);
             }
@@ -225,6 +236,7 @@
         return mLongPressHelper.hasPerformedLongPress();
     }
 
+    @Override
     public boolean onTouchEvent(MotionEvent ev) {
         mLongPressHelper.onTouchEvent(ev);
         // We want to keep receiving though events to be able to cancel long press on ACTION_UP
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
index b877d7a..94ae69c 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfo.java
@@ -1,5 +1,7 @@
 package com.android.launcher3.widget;
 
+import static com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE;
+
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.Context;
@@ -14,6 +16,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.icons.cache.BaseIconCache;
@@ -108,6 +111,13 @@
 
         Point cellSize = new Point();
         for (DeviceProfile dp : idp.supportedProfiles) {
+            // On phones we no longer support regular landscape, only fixed landscape for this
+            // reason we don't need to take regular landscape into account in phones
+            if (Flags.oneGridSpecs() && dp.inv.deviceType == TYPE_PHONE
+                    && dp.inv.isFixedLandscape != dp.isLandscape) {
+                continue;
+            }
+
             dp.getCellSize(cellSize);
             Rect widgetPadding = dp.widgetPadding;
 
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index f499fca..78197e2 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -41,7 +41,6 @@
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.BaseActivity;
-import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.model.data.ItemInfo;
@@ -50,6 +49,7 @@
 import com.android.launcher3.util.LooperExecutor;
 import com.android.launcher3.util.ResourceBasedOverride;
 import com.android.launcher3.util.SafeCloseable;
+import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.widget.custom.CustomWidgetManager;
 
 import java.util.ArrayList;
@@ -218,8 +218,7 @@
      * @param widgetId The ID of the widget
      * @param requestCode The request code
      */
-    public void startConfigActivity(@NonNull BaseDraggingActivity activity, int widgetId,
-            int requestCode) {
+    public void startConfigActivity(@NonNull BaseActivity activity, int widgetId, int requestCode) {
         if (!WIDGETS_ENABLED) {
             sendActionCancelled(activity, requestCode);
             return;
@@ -245,7 +244,7 @@
      * the configuration of the {@code widgetId} app widget, or null of options cannot be produced.
      */
     @Nullable
-    protected Bundle getConfigurationActivityOptions(@NonNull BaseDraggingActivity activity,
+    protected Bundle getConfigurationActivityOptions(@NonNull ActivityContext activity,
             int widgetId) {
         LauncherAppWidgetHostView view = mViews.get(widgetId);
         if (view == null) {
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 9c9b80d..cd8e457 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -21,7 +21,7 @@
 import static android.graphics.Paint.FILTER_BITMAP_FLAG;
 
 import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
-import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
+import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import android.appwidget.AppWidgetProviderInfo;
@@ -37,6 +37,9 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.text.Layout;
 import android.text.StaticLayout;
 import android.text.TextPaint;
@@ -60,8 +63,10 @@
 import com.android.launcher3.model.data.ItemInfoWithIcon;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
 import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SafeCloseable;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.widget.LauncherWidgetHolder.ProviderChangedListener;
 
 import java.util.List;
 
@@ -81,6 +86,8 @@
     private final Matrix mMatrix = new Matrix();
     private final RectF mPreviewBitmapRect = new RectF();
     private final RectF mCanvasRect = new RectF();
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final RunnableList mOnDetachCleanup = new RunnableList();
 
     private final LauncherWidgetHolder mWidgetHolder;
     private final LauncherAppWidgetProviderInfo mAppwidget;
@@ -90,7 +97,6 @@
     private final CharSequence mLabel;
 
     private OnClickListener mClickListener;
-    private SafeCloseable mOnDetachCleanup;
 
     private int mDragFlags;
 
@@ -210,16 +216,15 @@
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
 
+        mOnDetachCleanup.executeAllAndClear();
         if ((mAppwidget != null)
                 && !mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)
                 && mInfo.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED) {
             // If the widget is not completely restored, but has a valid ID, then listen of
             // updates from provider app for potential restore complete.
-            if (mOnDetachCleanup != null) {
-                mOnDetachCleanup.close();
-            }
-            mOnDetachCleanup = mWidgetHolder.addOnUpdateListener(
+            SafeCloseable updateCleanup = mWidgetHolder.addOnUpdateListener(
                     mInfo.appWidgetId, mAppwidget, this::checkIfRestored);
+            mOnDetachCleanup.add(updateCleanup::close);
             checkIfRestored();
         }
     }
@@ -227,10 +232,7 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        if (mOnDetachCleanup != null) {
-            mOnDetachCleanup.close();
-            mOnDetachCleanup = null;
-        }
+        mOnDetachCleanup.executeAllAndClear();
     }
 
     /**
@@ -295,43 +297,30 @@
             mCenterDrawable.setCallback(null);
             mCenterDrawable = null;
         }
-        mDragFlags = 0;
-        if (info.bitmap.icon != null) {
-            mDragFlags = FLAG_DRAW_ICON;
+        mDragFlags = FLAG_DRAW_ICON;
 
-            Drawable widgetCategoryIcon = getWidgetCategoryIcon();
-            // The view displays three modes,
-            //   1) App icon in the center
-            //   2) Preload icon in the center
-            //   3) App icon in the center with a setup icon on the top left corner.
-            if (mDisabledForSafeMode) {
-                if (widgetCategoryIcon == null) {
-                    FastBitmapDrawable disabledIcon = info.newIcon(getContext());
-                    disabledIcon.setIsDisabled(true);
-                    mCenterDrawable = disabledIcon;
-                } else {
-                    widgetCategoryIcon.setColorFilter(getDisabledColorFilter());
-                    mCenterDrawable = widgetCategoryIcon;
-                }
-                mSettingIconDrawable = null;
-            } else if (isReadyForClickSetup()) {
-                mCenterDrawable = widgetCategoryIcon == null
-                        ? info.newIcon(getContext())
-                        : widgetCategoryIcon;
-                mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
-                updateSettingColor(info.bitmap.color);
+        // The view displays three modes,
+        //   1) App icon in the center
+        //   2) Preload icon in the center
+        //   3) App icon in the center with a setup icon on the top left corner.
+        if (mDisabledForSafeMode) {
+            FastBitmapDrawable disabledIcon = info.newIcon(getContext());
+            disabledIcon.setIsDisabled(true);
+            mCenterDrawable = disabledIcon;
+            mSettingIconDrawable = null;
+        } else if (isReadyForClickSetup()) {
+            mCenterDrawable = info.newIcon(getContext());
+            mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
+            updateSettingColor(info.bitmap.color);
 
-                mDragFlags |= FLAG_DRAW_SETTINGS | FLAG_DRAW_LABEL;
-            } else {
-                mCenterDrawable = widgetCategoryIcon == null
-                        ? newPendingIcon(getContext(), info)
-                        : widgetCategoryIcon;
-                mSettingIconDrawable = null;
-                applyState();
-            }
-            mCenterDrawable.setCallback(this);
-            mDrawableSizeChanged = true;
+            mDragFlags |= FLAG_DRAW_SETTINGS | FLAG_DRAW_LABEL;
+        } else {
+            mCenterDrawable = newPendingIcon(getContext(), info);
+            mSettingIconDrawable = null;
+            applyState();
         }
+        mCenterDrawable.setCallback(this);
+        mDrawableSizeChanged = true;
         invalidate();
     }
 
@@ -350,6 +339,11 @@
     }
 
     public void applyState() {
+        if (mCenterDrawable instanceof FastBitmapDrawable fb
+                && mInfo.pendingItemInfo != null
+                && !fb.isSameInfo(mInfo.pendingItemInfo.bitmap)) {
+            reapplyItemInfo(mInfo.pendingItemInfo);
+        }
         if (mCenterDrawable != null) {
             mCenterDrawable.setLevel(Math.max(mInfo.installProgress, 0));
         }
@@ -486,16 +480,72 @@
     }
 
     /**
-     * Returns the widget category icon for {@link #mInfo}.
-     *
-     * <p>If {@link #mInfo}'s category is {@code PackageItemInfo#NO_CATEGORY} or unknown, returns
-     * {@code null}.
+     * Creates a runnable runnable which tries to refresh the widget if it is restored
      */
-    @Nullable
-    private Drawable getWidgetCategoryIcon() {
-        if (mInfo.pendingItemInfo.widgetCategory == WidgetSections.NO_CATEGORY) {
-            return null;
+    public void postProviderAvailabilityCheck() {
+        if (!mInfo.hasRestoreFlag(FLAG_PROVIDER_NOT_READY) && getAppWidgetInfo() == null) {
+            // If the info state suggests that the provider is ready, but there is no
+            // provider info attached on this pending view, recreate when the provider is available
+            DeferredWidgetRefresh restoreRunnable = new DeferredWidgetRefresh();
+            mOnDetachCleanup.add(restoreRunnable::cleanup);
+            mHandler.post(restoreRunnable::notifyWidgetProvidersChanged);
         }
-        return mInfo.pendingItemInfo.newIcon(getContext());
+    }
+
+    /**
+     * Used as a workaround to ensure that the AppWidgetService receives the
+     * PACKAGE_ADDED broadcast before updating widgets.
+     *
+     * This class will periodically check for the availability of the WidgetProvider as a result
+     * of providerChanged callback from the host. When the provider is available or a timeout of
+     * 10-sec is reached, it reinflates the pending-widget which in-turn goes through the process
+     * of re-evaluating the pending state of the widget,
+     */
+    private class DeferredWidgetRefresh implements Runnable, ProviderChangedListener {
+        private boolean mRefreshPending = true;
+
+        DeferredWidgetRefresh() {
+            mWidgetHolder.addProviderChangeListener(this);
+            // Force refresh after 10 seconds, if we don't get the provider changed event.
+            // This could happen when the provider is no longer available in the app.
+            Message msg = Message.obtain(getHandler(), this);
+            msg.obj = DeferredWidgetRefresh.class;
+            mHandler.sendMessageDelayed(msg, 10000);
+        }
+
+        /**
+         * Reinflate the widget if it is still attached.
+         */
+        @Override
+        public void run() {
+            cleanup();
+            if (mRefreshPending) {
+                reInflate();
+                mRefreshPending = false;
+            }
+        }
+
+        @Override
+        public void notifyWidgetProvidersChanged() {
+            final AppWidgetProviderInfo widgetInfo;
+            WidgetManagerHelper widgetHelper = new WidgetManagerHelper(getContext());
+            if (mInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) {
+                widgetInfo = widgetHelper.findProvider(mInfo.providerName, mInfo.user);
+            } else {
+                widgetInfo = widgetHelper.getLauncherAppWidgetInfo(mInfo.appWidgetId,
+                        mInfo.getTargetComponent());
+            }
+            if (widgetInfo != null) {
+                run();
+            }
+        }
+
+        /**
+         * Removes any scheduled callbacks and change listeners, no-op if nothing is scheduled
+         */
+        public void cleanup() {
+            mWidgetHolder.removeProviderChangeListener(this);
+            mHandler.removeCallbacks(this);
+        }
     }
 }
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index 4811a17..130843b 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -18,7 +18,6 @@
 
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
 
-import static com.android.launcher3.Flags.enableWidgetTapToAdd;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_TRAY;
 import static com.android.launcher3.widget.util.WidgetSizes.getWidgetItemSizePx;
 
@@ -152,22 +151,22 @@
         mWidgetTextContainer = findViewById(R.id.widget_text_container);
         mWidgetAddButton = findViewById(R.id.widget_add_button);
 
-        if (enableWidgetTapToAdd()) {
-
-            setAccessibilityDelegate(new AccessibilityDelegate() {
-                @Override
-                public void onInitializeAccessibilityNodeInfo(View host,
-                        AccessibilityNodeInfo info) {
-                    super.onInitializeAccessibilityNodeInfo(host, info);
-                    String accessibilityLabel = getResources().getString(mWidgetAddButton.isShown()
-                            ? R.string.widget_cell_tap_to_hide_add_button_label
-                            : R.string.widget_cell_tap_to_show_add_button_label);
+        setAccessibilityDelegate(new AccessibilityDelegate() {
+            @Override
+            public void onInitializeAccessibilityNodeInfo(View host,
+                    AccessibilityNodeInfo info) {
+                super.onInitializeAccessibilityNodeInfo(host, info);
+                if (hasOnClickListeners()) {
+                    String accessibilityLabel = getResources().getString(
+                            mWidgetAddButton.isShown()
+                                    ? R.string.widget_cell_tap_to_hide_add_button_label
+                                    : R.string.widget_cell_tap_to_show_add_button_label);
                     info.addAction(new AccessibilityNodeInfo.AccessibilityAction(ACTION_CLICK,
                             accessibilityLabel));
                 }
-            });
-            mWidgetAddButton.setVisibility(INVISIBLE);
-        }
+            }
+        });
+        mWidgetAddButton.setVisibility(INVISIBLE);
     }
 
     public void setRemoteViewsPreview(RemoteViews view) {
@@ -208,9 +207,7 @@
         showDescription(true);
         showDimensions(true);
 
-        if (enableWidgetTapToAdd()) {
-            hideAddButton(/* animate= */ false);
-        }
+        hideAddButton(/* animate= */ false);
 
         if (mActiveRequest != null) {
             mActiveRequest.cancel();
diff --git a/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java b/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java
index 5b1da5b..a761ecd 100644
--- a/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetListSpaceEntry.java
@@ -33,4 +33,9 @@
                 Collections.EMPTY_LIST);
         mPkgItem.title = "";
     }
+
+    @Override
+    public WidgetsListBaseEntry copy() {
+        return new WidgetListSpaceEntry();
+    }
 }
diff --git a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
index 0003b76..9246e45 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListBaseEntry.java
@@ -43,4 +43,7 @@
         this.mWidgets =
                 items.stream().sorted(new WidgetItemComparator()).collect(Collectors.toList());
     }
+
+
+    public abstract WidgetsListBaseEntry copy();
 }
diff --git a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
index d709196..cc1739f 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListContentEntry.java
@@ -58,6 +58,11 @@
     }
 
     @Override
+    public WidgetsListBaseEntry copy() {
+        return new WidgetsListContentEntry(mPkgItem, mTitleSectionName, mWidgets, mMaxSpanSize);
+    }
+
+    @Override
     public String toString() {
         return "Content:" + mPkgItem.packageName + ":" + mWidgets.size() + " maxSpanSize: "
                 + mMaxSpanSize;
diff --git a/src/com/android/launcher3/widget/model/WidgetsListExpandActionEntry.java b/src/com/android/launcher3/widget/model/WidgetsListExpandActionEntry.java
index 8c84030..7519bb7 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListExpandActionEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListExpandActionEntry.java
@@ -35,4 +35,9 @@
                 /*items=*/ Collections.EMPTY_LIST);
         mPkgItem.title = "";
     }
+
+    @Override
+    public WidgetsListBaseEntry copy() {
+        return new WidgetsListExpandActionEntry();
+    }
 }
diff --git a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
index 0d775c3..e2ea068 100644
--- a/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
+++ b/src/com/android/launcher3/widget/model/WidgetsListHeaderEntry.java
@@ -83,6 +83,12 @@
         mIsWidgetListShown = isWidgetListShown;
     }
 
+    @Override
+    public WidgetsListBaseEntry copy() {
+        return new WidgetsListHeaderEntry(mPkgItem, mTitleSectionName, mWidgets,
+                mVisibleWidgetsCount, mIsSearchEntry, mIsWidgetListShown);
+    }
+
     /** Returns {@code true} if the widgets list associated with this header is shown. */
     public boolean isWidgetListShown() {
         return mIsWidgetListShown;
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
index f8dc6b0..8f34fe3 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProvider.java
@@ -35,19 +35,7 @@
  * own implementation. Method {@code getWidgetRecommendationCategory} is called per widget to get
  * the category.</p>
  */
-public class WidgetRecommendationCategoryProvider implements ResourceBasedOverride {
-    private static final String TAG = "WidgetRecommendationCategoryProvider";
-
-    /**
-     * Retrieve instance of this object that can be overridden in runtime based on the build
-     * variant of the application.
-     */
-    public static WidgetRecommendationCategoryProvider newInstance(Context context) {
-        Preconditions.assertWorkerThread();
-        return Overrides.getObject(
-                WidgetRecommendationCategoryProvider.class, context.getApplicationContext(),
-                R.string.widget_recommendation_category_provider_class);
-    }
+public class WidgetRecommendationCategoryProvider {
 
     /**
      * Returns a {@link WidgetRecommendationCategory} for the provided widget item that can be used
diff --git a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
index ef87db5..4ccf16b 100644
--- a/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
+++ b/src/com/android/launcher3/widget/picker/WidgetRecommendationsView.java
@@ -291,16 +291,28 @@
             }
             setCurrentPage(requestedPage);
             mPageIndicator.setActiveMarker(requestedPage);
-            mRecommendationPageTitle.setText(mCategoryTitles.get(requestedPage));
+            updatePageTitle(requestedPage);
         }
     }
 
     @Override
+    protected boolean canAnnouncePageDescription() {
+        // Disable announcement as our page title reads out the needed page description
+        return false;
+    }
+
+    private void updatePageTitle(int requestedPage) {
+        String title = mCategoryTitles.get(requestedPage);
+        mRecommendationPageTitle.setText(title);
+        mRecommendationPageTitle.setContentDescription(title + ", " + getCurrentPageDescription());
+    }
+
+    @Override
     protected void notifyPageSwitchListener(int prevPage) {
         if (getPageCount() > 1) {
             // Since the title is outside the paging scroll, we update the title on page switch.
             int nextPage = getNextPage();
-            mRecommendationPageTitle.setText(mCategoryTitles.get(nextPage));
+            updatePageTitle(nextPage);
             mPageSwitchListeners.forEach(listener -> listener.accept(nextPage));
             super.notifyPageSwitchListener(prevPage);
         }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 9ef9349..b0abf23 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -15,15 +15,14 @@
  */
 package com.android.launcher3.widget.picker;
 
-import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
 import static com.android.launcher3.Flags.enableTieredWidgetsByDefaultInPicker;
-import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker;
 import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_EXPAND_PRESS;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED;
 import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
 import static com.android.launcher3.views.RecyclerViewFastScroller.FastScrollerLocation.WIDGET_SCROLLER;
 
+import static java.lang.Math.abs;
 import static java.util.Collections.emptyList;
 
 import android.animation.Animator;
@@ -41,6 +40,7 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.ViewParent;
 import android.view.WindowInsets;
@@ -80,12 +80,10 @@
 import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Predicate;
-import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
 /**
@@ -120,6 +118,10 @@
     protected int mRecommendationsCurrentPage = 0;
     protected final SparseArray<AdapterHolder> mAdapters = new SparseArray();
 
+    // Helps with removing focus from searchbar by analyzing motion events.
+    private final SearchClearFocusHelper mSearchClearFocusHelper = new SearchClearFocusHelper();
+    private final float mTouchSlop; // initialized in constructor
+
     private final OnAttachStateChangeListener mBindScrollbarInSearchMode =
             new OnAttachStateChangeListener() {
                 @Override
@@ -166,6 +168,7 @@
 
     public WidgetsFullSheet(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         mDeviceProfile = mActivityContext.getDeviceProfile();
         mUserCache = UserCache.INSTANCE.get(context);
         mHasWorkProfile = mUserCache.getUserProfiles()
@@ -571,14 +574,11 @@
     public void exitSearchMode() {
         if (!mIsInSearchMode) return;
         onSearchResults(new ArrayList<>());
-        WidgetsRecyclerView searchRecyclerView = mAdapters.get(
-                AdapterHolder.SEARCH).mWidgetsRecyclerView;
         // Remove all views when exiting the search mode; this prevents animating from stale results
         // to new ones the next time we enter search mode. By the time recycler view is hidden,
         // layout may not have happened to clear up existing results. So, instead of waiting for it
         // to happen, we clear the views here.
-        searchRecyclerView.swapAdapter(
-                searchRecyclerView.getAdapter(), /*removeAndRecycleExistingViews=*/ true);
+        mAdapters.get(AdapterHolder.SEARCH).reset();
         setViewVisibilityBasedOnSearch(/*isInSearchMode=*/ false);
         if (mHasWorkProfile) {
             mViewPager.snapToPage(AdapterHolder.PRIMARY);
@@ -607,13 +607,12 @@
             mNoWidgetsView.setVisibility(GONE);
         } else {
             mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.setVisibility(GONE);
-            mAdapters.get(getCurrentAdapterHolderType()).mWidgetsRecyclerView.setVisibility(
-                    VISIBLE);
-            if (mRecommendedWidgetsCount > 0) {
-                // Display recommendations immediately, if present, so that other parts of sticky
-                // header (e.g. personal / work tabs) don't flash in interim.
-                mWidgetRecommendationsContainer.setVisibility(VISIBLE);
-            }
+            AdapterHolder currentAdapterHolder = mAdapters.get(getCurrentAdapterHolderType());
+            // Remove all views when exiting the search mode; this prevents animating / flashing old
+            // list position / state.
+            currentAdapterHolder.reset();
+            currentAdapterHolder.mWidgetsRecyclerView.setVisibility(VISIBLE);
+            post(this::onRecommendedWidgetsBound);
             // Visibility of recycler views and headers are handled in methods below.
             onWidgetsBound();
         }
@@ -629,35 +628,19 @@
         if (mIsInSearchMode) {
             return;
         }
-        if (enableCategorizedWidgetSuggestions()) {
-            // We avoid applying new recommendations when some are already displayed.
-            if (mRecommendedWidgetsMap.isEmpty()) {
-                mRecommendedWidgetsMap =
-                        mActivityContext.getWidgetPickerDataProvider().get().getRecommendations();
-            }
-            mRecommendedWidgetsCount = mWidgetRecommendationsView.setRecommendations(
-                    mRecommendedWidgetsMap,
-                    mDeviceProfile,
-                    /* availableHeight= */ getMaxAvailableHeightForRecommendations(),
-                    /* availableWidth= */ mMaxSpanPerRow,
-                    /* cellPadding= */ mWidgetCellHorizontalPadding,
-                    /* requestedPage= */ mRecommendationsCurrentPage
-            );
-        } else {
-            if (mRecommendedWidgets.isEmpty()) {
-                mRecommendedWidgets = mActivityContext.getWidgetPickerDataProvider().get()
-                        .getRecommendations()
-                        .values().stream()
-                        .flatMap(Collection::stream).collect(Collectors.toList());
-                mRecommendedWidgetsCount = mWidgetRecommendationsView.setRecommendations(
-                        mRecommendedWidgets,
-                        mDeviceProfile,
-                        /* availableHeight= */ getMaxAvailableHeightForRecommendations(),
-                        /* availableWidth= */ mMaxSpanPerRow,
-                        /* cellPadding= */ mWidgetCellHorizontalPadding
-                );
-            }
+        // We avoid applying new recommendations when some are already displayed.
+        if (mRecommendedWidgetsMap.isEmpty()) {
+            mRecommendedWidgetsMap =
+                    mActivityContext.getWidgetPickerDataProvider().get().getRecommendations();
         }
+        mRecommendedWidgetsCount = mWidgetRecommendationsView.setRecommendations(
+                mRecommendedWidgetsMap,
+                mDeviceProfile,
+                /* availableHeight= */ getMaxAvailableHeightForRecommendations(),
+                /* availableWidth= */ mMaxSpanPerRow,
+                /* cellPadding= */ mWidgetCellHorizontalPadding,
+                /* requestedPage= */ mRecommendationsCurrentPage
+        );
 
         mWidgetRecommendationsContainer.setVisibility(
                 mRecommendedWidgetsCount > 0 ? VISIBLE : GONE);
@@ -715,10 +698,14 @@
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
             mNoIntercept = shouldScroll(ev);
-            if (mSearchBar.isSearchBarFocused()
-                    && !getPopupContainer().isEventOverView(mSearchBarContainer, ev)) {
-                mSearchBar.clearSearchBarFocus();
-            }
+        }
+
+        // Clear focus only if user touched outside of search area and handling focus out ourselves
+        // was necessary (e.g. when it's not predictive back, but other user interaction).
+        if (mSearchBar.isSearchBarFocused()
+                && !getPopupContainer().isEventOverView(mSearchBarContainer, ev)
+                && mSearchClearFocusHelper.shouldClearFocus(ev, mTouchSlop)) {
+            mSearchBar.clearSearchBarFocus();
         }
 
         return super.onControllerInterceptTouchEvent(ev);
@@ -785,13 +772,7 @@
     }
 
     private static int getWidgetSheetId(BaseActivity activity) {
-        boolean isTwoPane = (activity.getDeviceProfile().isTablet
-                // Enables two pane picker for tablets in all orientations when the
-                // enableCategorizedWidgetSuggestions flag is on.
-                && (activity.getDeviceProfile().isLandscape || enableCategorizedWidgetSuggestions())
-                && !activity.getDeviceProfile().isTwoPanels)
-                // Enables two pane picker for unfolded foldables if the flag is on.
-                || (activity.getDeviceProfile().isTwoPanels && enableUnfoldedTwoPanePicker());
+        boolean isTwoPane = activity.getDeviceProfile().isTablet;
 
         return isTwoPane ? R.layout.widgets_two_pane_sheet : R.layout.widgets_full_sheet;
     }
@@ -938,16 +919,7 @@
     private static boolean shouldRecreateLayout(DeviceProfile oldDp, DeviceProfile newDp) {
         // When folding/unfolding the foldables, we need to switch between the regular widget picker
         // and the two pane picker, so we rebuild the picker with the correct layout.
-        boolean isFoldUnFold =
-                oldDp.isTwoPanels != newDp.isTwoPanels && enableUnfoldedTwoPanePicker();
-        // In tablets, on orientation change we switch between single and two pane picker unless the
-        // categorized suggestions flag was on. With the categorized suggestions feature, we use a
-        // two pane picker across all orientations.
-        boolean useDifferentLayoutOnOrientationChange =
-                (!enableCategorizedWidgetSuggestions() && (newDp.isTablet && !newDp.isTwoPanels
-                        && oldDp.isLandscape != newDp.isLandscape));
-
-        return isFoldUnFold || useDifferentLayoutOnOrientationChange;
+        return oldDp.isTwoPanels != newDp.isTwoPanels;
     }
 
     /**
@@ -1116,6 +1088,21 @@
             mWidgetsListItemAnimator = new WidgetsListItemAnimator();
         }
 
+        /**
+         * Swaps the adapter to existing adapter to prevent the recycler view from using stale view
+         * to animate in the new visibility update.
+         *
+         * <p>For instance, when clearing search text and re-entering search with new list shouldn't
+         * use stale results to animate in new results. Alternative is setting list animators to
+         * null, but, we need animations with the default item animator.
+         */
+        private void reset() {
+            mWidgetsRecyclerView.swapAdapter(
+                    mWidgetsListAdapter,
+                    /*removeAndRecycleExistingViews=*/ true
+            );
+        }
+
         private int getEmptySpaceHeight() {
             return mStickyHeaderLayout != null ? mStickyHeaderLayout.getHeaderHeight() : 0;
         }
@@ -1142,4 +1129,53 @@
             mWidgetsListAdapter.setMaxHorizontalSpansPxPerRow(mMaxSpanPerRow);
         }
     }
+
+    /**
+     * Helper to identify if searchbar's focus can be cleared when user performs an action
+     * outside search.
+     */
+    private static class SearchClearFocusHelper {
+        private float mFirstInteractionX = -1f;
+        private float mFirstInteractionY = -1f;
+
+        /**
+         * For a given [MotionEvent] indicates if we should clear focus from search (and hide IME).
+         */
+        boolean shouldClearFocus(MotionEvent ev, float touchSlop) {
+            int action = ev.getAction();
+            boolean clearFocus = false;
+
+            if (action == MotionEvent.ACTION_DOWN) {
+                mFirstInteractionX = ev.getX();
+                mFirstInteractionY = ev.getY();
+            } else if (action == MotionEvent.ACTION_CANCEL) {
+                // This is when user performed a gesture e.g. predictive back
+                // We don't handle it ourselves and let IME handle the close.
+                mFirstInteractionY = -1;
+                mFirstInteractionX = -1;
+            } else if (action == MotionEvent.ACTION_UP) {
+                // Its clear that user action wasn't predictive back - but press / scroll etc. that
+                // should hide the keyboard.
+                clearFocus = true;
+                mFirstInteractionY = -1;
+                mFirstInteractionX = -1;
+            } else if (action == MotionEvent.ACTION_MOVE) {
+                // Sometimes, on move, we may not receive ACTION_UP, but if the move was within
+                // touch slop and we didn't know if its moved or cancelled, we can clear focus.
+                // Example case: Apps list is small and you do a little scroll on list - in such, we
+                // want to still hide the keyboard.
+                if (mFirstInteractionX != -1 && mFirstInteractionY != -1) {
+                    float distY = abs(mFirstInteractionY - ev.getY());
+                    float distX = abs(mFirstInteractionX - ev.getX());
+                    if (distY >= touchSlop || distX >= touchSlop) {
+                        clearFocus = true;
+                        mFirstInteractionY = -1;
+                        mFirstInteractionX = -1;
+                    }
+                }
+            }
+
+            return clearFocus;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
index d164dd0..2f123a3 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsListHeader.java
@@ -234,12 +234,9 @@
             mIconLoadRequest.cancel();
             mIconLoadRequest = null;
         }
-        if (getTag() instanceof ItemInfoWithIcon) {
-            ItemInfoWithIcon info = (ItemInfoWithIcon) getTag();
-            if (info.usingLowResIcon()) {
-                mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache()
-                        .updateIconInBackground(this, info);
-            }
+        if (getTag() instanceof ItemInfoWithIcon info && info.getMatchingLookupFlag().useLowRes()) {
+            mIconLoadRequest = LauncherAppState.getInstance(getContext()).getIconCache()
+                    .updateIconInBackground(this, info);
         }
     }
 }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
index 216f4d4..9ee9150 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsRecommendationTableLayout.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.widget.picker;
 
-import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
 import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
 import static com.android.launcher3.widget.util.WidgetSizes.getWidgetSizePx;
 import static com.android.launcher3.widget.util.WidgetsTableUtils.WIDGETS_TABLE_ROW_COUNT_COMPARATOR;
@@ -112,10 +111,8 @@
                 WidgetCell widgetCell = addItemCell(tableRow);
                 widgetCell.applyFromCellItem(widgetItem);
                 widgetCell.showAppIconInWidgetTitle(true);
-                if (enableCategorizedWidgetSuggestions()) {
-                    widgetCell.showDescription(false);
-                    widgetCell.showDimensions(false);
-                }
+                widgetCell.showDescription(false);
+                widgetCell.showDimensions(false);
             }
             addView(tableRow);
         }
diff --git a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
index f9bd5f1..9ffaf51 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsTwoPaneSheet.java
@@ -15,13 +15,12 @@
  */
 package com.android.launcher3.widget.picker;
 
-import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
 import static com.android.launcher3.Flags.enableTieredWidgetsByDefaultInPicker;
-import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker;
 import static com.android.launcher3.UtilitiesKt.CLIP_CHILDREN_FALSE_MODIFIER;
 import static com.android.launcher3.UtilitiesKt.CLIP_TO_PADDING_FALSE_MODIFIER;
 import static com.android.launcher3.UtilitiesKt.modifyAttributesOnViewTree;
 import static com.android.launcher3.UtilitiesKt.restoreAttributesOnViewTree;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.widget.picker.WidgetsListItemAnimator.WIDGET_LIST_ITEM_APPEARANCE_DELAY;
 import static com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.findContentEntryForPackageUser;
 
@@ -51,6 +50,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.cache.CacheLookupFlag;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.recyclerview.ViewHolderBinder;
@@ -213,22 +213,9 @@
 
     @Override
     protected int getTabletHorizontalMargin(DeviceProfile deviceProfile) {
-        if (enableCategorizedWidgetSuggestions()) {
-            // two pane picker is full width for fold as well as tablet.
-            return getResources().getDimensionPixelSize(
-                    R.dimen.widget_picker_two_panels_left_right_margin);
-        }
-        if (deviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) {
-            // enableUnfoldedTwoPanePicker made two pane picker full-width for fold only.
-            return getResources().getDimensionPixelSize(
-                    R.dimen.widget_picker_two_panels_left_right_margin);
-        }
-        if (deviceProfile.isLandscape && !deviceProfile.isTwoPanels) {
-            // non-fold tablet landscape margins (ag/22163531)
-            return getResources().getDimensionPixelSize(
-                    R.dimen.widget_picker_landscape_tablet_left_right_margin);
-        }
-        return deviceProfile.allAppsLeftRightMargin;
+        // two pane picker is full width for fold as well as tablet.
+        return getResources().getDimensionPixelSize(
+                R.dimen.widget_picker_two_panels_left_right_margin);
     }
 
     @Override
@@ -255,7 +242,7 @@
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
-        if (changed && mDeviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) {
+        if (changed && mDeviceProfile.isTwoPanels) {
             LinearLayout layout = mContent.findViewById(R.id.linear_layout_container);
             FrameLayout leftPane = layout.findViewById(R.id.recycler_view_container);
             LinearLayout.LayoutParams layoutParams = (LayoutParams) leftPane.getLayoutParams();
@@ -425,7 +412,7 @@
     protected int getAvailableWidthForSuggestions(int pickerAvailableWidth) {
         int rightPaneWidth = (int) Math.ceil(0.67 * pickerAvailableWidth);
 
-        if (mDeviceProfile.isTwoPanels && enableUnfoldedTwoPanePicker()) {
+        if (mDeviceProfile.isTwoPanels) {
             // See onLayout
             int leftPaneWidth = (int) (0.33 * pickerAvailableWidth);
             @Px int minLeftPaneWidthPx = Utilities.dpToPx(MINIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP);
@@ -550,13 +537,9 @@
                 }
 
                 WidgetsListContentEntry contentEntryToBind;
-                if (enableCategorizedWidgetSuggestions()) {
-                    // Setting max span size enables row to understand how to fit more than one item
-                    // in a row.
-                    contentEntryToBind = contentEntry.withMaxSpanSize(mMaxSpanPerRow);
-                } else {
-                    contentEntryToBind = contentEntry;
-                }
+                // Setting max span size enables row to understand how to fit more than one item
+                // in a row.
+                contentEntryToBind = contentEntry.withMaxSpanSize(mMaxSpanPerRow);
 
                 WidgetsRowViewHolder widgetsRowViewHolder =
                         mWidgetsListTableViewHolderBinder.newViewHolder(mRightPane);
@@ -691,8 +674,8 @@
         }
 
         @Override
-        public boolean usingLowResIcon() {
-            return false;
+        public CacheLookupFlag getMatchingLookupFlag() {
+            return DEFAULT_LOOKUP_FLAG;
         }
     }
 }
diff --git a/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProvider.kt b/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProvider.kt
index 46d3e7a..5b97a49 100644
--- a/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProvider.kt
+++ b/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProvider.kt
@@ -16,43 +16,79 @@
 
 package com.android.launcher3.widget.picker.model
 
+import android.content.Context
+import com.android.launcher3.dagger.LauncherComponentProvider.appComponent
+import com.android.launcher3.model.WidgetItem
+import com.android.launcher3.model.WidgetsFilterDataProvider
+import com.android.launcher3.model.WidgetsFilterDataProvider.WidgetsFilterLoadedCallback
 import com.android.launcher3.model.data.ItemInfo
 import com.android.launcher3.widget.model.WidgetsListBaseEntry
 import com.android.launcher3.widget.picker.model.data.WidgetPickerData
 import com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.withRecommendedWidgets
 import com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.withWidgets
 import java.io.PrintWriter
+import java.util.function.Predicate
 
 /**
  * Provides [WidgetPickerData] to various views such as widget picker, app-specific widget picker,
  * widgets shortcut.
  */
-class WidgetPickerDataProvider {
+class WidgetPickerDataProvider(private val filterProvider: WidgetsFilterDataProvider) :
+    WidgetsFilterLoadedCallback {
+
+    constructor(context: Context) : this(context.appComponent.widgetsFilterDataProvider)
+
     /** All the widgets data provided for the views */
     private var mWidgetPickerData: WidgetPickerData = WidgetPickerData()
 
     private var changeListener: WidgetPickerDataChangeListener? = null
 
+    var hostSpecifiedDefaultWidgetsFilter: Predicate<WidgetItem>? = null
+
+    private var allWidgets: List<WidgetsListBaseEntry> = emptyList()
+
     /** Sets a listener to be called back when widget data is updated. */
     fun setChangeListener(changeListener: WidgetPickerDataChangeListener?) {
         this.changeListener = changeListener
     }
 
+    init {
+        filterProvider.addFilterChangeCallback(this)
+    }
+
     /** Returns the current snapshot of [WidgetPickerData]. */
     fun get(): WidgetPickerData {
         return mWidgetPickerData
     }
 
+    override fun onWidgetsFilterLoaded() {
+        setWidgets(allWidgets)
+    }
+
     /**
      * Updates the widgets available to the widget picker.
      *
      * Generally called when the widgets model has new data.
      */
-    @JvmOverloads
-    fun setWidgets(
-        allWidgets: List<WidgetsListBaseEntry>,
-        defaultWidgets: List<WidgetsListBaseEntry> = listOf()
-    ) {
+    fun setWidgets(allWidgets: List<WidgetsListBaseEntry>) {
+        this.allWidgets = allWidgets
+
+        val currentFilter = filterProvider.defaultWidgetsFilter
+        val finalFilter =
+            when {
+                currentFilter != null && hostSpecifiedDefaultWidgetsFilter != null ->
+                    currentFilter.and(hostSpecifiedDefaultWidgetsFilter)
+                hostSpecifiedDefaultWidgetsFilter != null -> hostSpecifiedDefaultWidgetsFilter
+                else -> currentFilter
+            }
+
+        val defaultWidgets =
+            if (finalFilter != null)
+                allWidgets
+                    .map { it.copy().apply { mWidgets.removeIf(finalFilter) } }
+                    .filter { it.mWidgets.isNotEmpty() }
+            else emptyList()
+
         mWidgetPickerData =
             mWidgetPickerData.withWidgets(allWidgets = allWidgets, defaultWidgets = defaultWidgets)
         changeListener?.onWidgetsBound()
@@ -74,6 +110,10 @@
         writer.println("$prefix\twidgetPickerData:$mWidgetPickerData")
     }
 
+    fun destroy() {
+        filterProvider.removeFilterChangeCallback(this)
+    }
+
     interface WidgetPickerDataChangeListener {
         /** A callback to get notified when widgets are bound. */
         fun onWidgetsBound()
diff --git a/src_no_quickstep/com/android/launcher3/dagger/LauncherAppComponent.java b/src_no_quickstep/com/android/launcher3/dagger/LauncherAppComponent.java
index 63d87e8..f4f5a08 100644
--- a/src_no_quickstep/com/android/launcher3/dagger/LauncherAppComponent.java
+++ b/src_no_quickstep/com/android/launcher3/dagger/LauncherAppComponent.java
@@ -22,7 +22,7 @@
  * Root component for Dagger injection for Launcher AOSP.
  */
 @LauncherAppSingleton
-@Component
+@Component(modules = LauncherAppModule.class)
 public interface LauncherAppComponent extends LauncherBaseAppComponent {
     /** Builder for aosp LauncherAppComponent. */
     @Component.Builder
diff --git a/src_no_quickstep/com/android/launcher3/dagger/Modules.kt b/src_no_quickstep/com/android/launcher3/dagger/Modules.kt
new file mode 100644
index 0000000..c3bf7c5
--- /dev/null
+++ b/src_no_quickstep/com/android/launcher3/dagger/Modules.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.dagger
+
+import dagger.Module
+
+private object Modules {}
+
+@Module abstract class WindowManagerProxyModule {}
+
+@Module abstract class ApiWrapperModule {}
+
+@Module abstract class PluginManagerWrapperModule {}
+
+@Module object StaticObjectModule {}
+
+// Module containing bindings for the final derivative app
+@Module abstract class AppModule {}
diff --git a/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java
index 9865516..03a5535 100644
--- a/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/src_no_quickstep/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -18,8 +18,6 @@
 import static com.android.app.animation.Interpolators.DECELERATE;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_ALLAPPS;
 
-import android.content.Context;
-
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
@@ -31,8 +29,6 @@
  */
 public class AllAppsState extends LauncherState {
 
-    private static final float PARALLAX_COEFFICIENT = .125f;
-
     private static final int STATE_FLAGS = FLAG_WORKSPACE_INACCESSIBLE;
 
     public AllAppsState(int id) {
@@ -40,8 +36,7 @@
     }
 
     @Override
-    public <DEVICE_PROFILE_CONTEXT extends Context & ActivityContext>
-    int getTransitionDuration(DEVICE_PROFILE_CONTEXT context, boolean isToState) {
+    public int getTransitionDuration(ActivityContext context, boolean isToState) {
         return isToState
                 ? context.getDeviceProfile().allAppsOpenDuration
                 : context.getDeviceProfile().allAppsCloseDuration;
diff --git a/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java b/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java
index 7a228c4..532a338 100644
--- a/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/src_no_quickstep/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -17,12 +17,11 @@
 
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW;
 
-import android.content.Context;
-
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
 import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
 
 /**
  * Definition for overview state
@@ -34,7 +33,7 @@
     }
 
     @Override
-    public int getTransitionDuration(Context context, boolean isToState) {
+    public int getTransitionDuration(ActivityContext context, boolean isToState) {
         return 250;
     }
 
diff --git a/tests/Android.bp b/tests/Android.bp
index f6e2d07..fc08e86 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -151,19 +151,6 @@
     test_suites: ["general-tests"],
 }
 
-// Shared between tests and launcher
-android_library {
-    name: "launcher-testing-shared",
-    srcs: [
-        "multivalentTests/shared/com/android/launcher3/testing/shared/**/*.java",
-        "multivalentTests/shared/com/android/launcher3/testing/shared/**/*.kt",
-    ],
-    resource_dirs: [],
-    manifest: "multivalentTests/shared/AndroidManifest.xml",
-    sdk_version: "current",
-    min_sdk_version: min_launcher3_sdk_version,
-}
-
 filegroup {
     name: "launcher-testing-helpers-robo",
     srcs: [
@@ -181,6 +168,7 @@
         "src/**/*Test.java",
         "src/**/*Test.kt",
         "src/**/RoboApiWrapper.kt",
+        "src/**/EventsRule.kt",
         "multivalentTests/src/**/*Test.java",
         "multivalentTests/src/**/*Test.kt",
     ],
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 5cf96c8..862b862 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -29,6 +29,8 @@
         android:functionalTest="false"
         android:handleProfiling="false"
         android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.launcher3" >
+      android:targetPackage="com.android.launcher3" >
+      <meta-data android:name="listener"
+      android:value="com.android.launcher3.util.GlobalTestRunListener"/>
     </instrumentation>
 </manifest>
diff --git a/tests/Launcher3Tests.xml b/tests/Launcher3Tests.xml
index a876860..da86357 100644
--- a/tests/Launcher3Tests.xml
+++ b/tests/Launcher3Tests.xml
@@ -50,6 +50,8 @@
         <option name="run-command" value="settings put system show_touches 1" />
 
         <option name="run-command" value="setprop pixel_legal_joint_permission_v2 true" />
+
+        <option name="run-command" value="settings put global verifier_verify_adb_installs 0" />
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
@@ -60,6 +62,7 @@
 
     <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
         <option name="directory-keys" value="/data/user/0/com.android.launcher3/files" />
+        <option name="directory-keys" value="/data/user/10/com.android.launcher3/files" />
         <option name="collect-on-run-ended-only" value="true" />
     </metrics_collector>
 
diff --git a/tests/assets/databases/GridMigrationTest/result5x5to5x8.db b/tests/assets/databases/GridMigrationTest/result5x5to5x8.db
index 311a112..d750774 100644
--- a/tests/assets/databases/GridMigrationTest/result5x5to5x8.db
+++ b/tests/assets/databases/GridMigrationTest/result5x5to5x8.db
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/result5x5to5x8WithShift.db b/tests/assets/databases/GridMigrationTest/result5x5to5x8WithShift.db
deleted file mode 100644
index d750774..0000000
--- a/tests/assets/databases/GridMigrationTest/result5x5to5x8WithShift.db
+++ /dev/null
Binary files differ
diff --git a/tests/assets/databases/GridMigrationTest/test_launcher_2.db b/tests/assets/databases/GridMigrationTest/test_launcher_2.db
deleted file mode 100644
index b538e26..0000000
--- a/tests/assets/databases/GridMigrationTest/test_launcher_2.db
+++ /dev/null
Binary files differ
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
index 4c366c3..a13d63b 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait.txt
@@ -55,7 +55,7 @@
 	bottomSheetCloseDuration: 267
 	bottomSheetWorkspaceScale: 1.0
 	bottomSheetDepth: 0.0
-	allAppsShiftRange: 788.0px (300.1905dp)
+	allAppsShiftRange: 2400.0px (914.2857dp)
 	allAppsOpenDuration: 600
 	allAppsCloseDuration: 300
 	allAppsIconSizePx: 147.0px (56.0dp)
@@ -66,7 +66,7 @@
 	allAppsBorderSpacePxX: 42.0px (16.0dp)
 	allAppsBorderSpacePxY: 42.0px (16.0dp)
 	numShownAllAppsColumns: 5
-	allAppsPadding.top: 0.0px (0.0dp)
+	allAppsPadding.top: 118.0px (44.95238dp)
 	allAppsPadding.left: 0.0px (0.0dp)
 	allAppsPadding.right: 0.0px (0.0dp)
 	allAppsLeftRightMargin: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
index 6db9534..3c24885 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phonePortrait3Button.txt
@@ -55,7 +55,7 @@
 	bottomSheetCloseDuration: 267
 	bottomSheetWorkspaceScale: 1.0
 	bottomSheetDepth: 0.0
-	allAppsShiftRange: 788.0px (300.1905dp)
+	allAppsShiftRange: 2400.0px (914.2857dp)
 	allAppsOpenDuration: 600
 	allAppsCloseDuration: 300
 	allAppsIconSizePx: 147.0px (56.0dp)
@@ -66,7 +66,7 @@
 	allAppsBorderSpacePxX: 42.0px (16.0dp)
 	allAppsBorderSpacePxY: 42.0px (16.0dp)
 	numShownAllAppsColumns: 5
-	allAppsPadding.top: 0.0px (0.0dp)
+	allAppsPadding.top: 118.0px (44.95238dp)
 	allAppsPadding.left: 0.0px (0.0dp)
 	allAppsPadding.right: 0.0px (0.0dp)
 	allAppsLeftRightMargin: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
index 6e76b13..5e06513 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar.txt
@@ -55,7 +55,7 @@
 	bottomSheetCloseDuration: 267
 	bottomSheetWorkspaceScale: 1.0
 	bottomSheetDepth: 0.0
-	allAppsShiftRange: 788.0px (300.1905dp)
+	allAppsShiftRange: 1080.0px (411.42856dp)
 	allAppsOpenDuration: 600
 	allAppsCloseDuration: 300
 	allAppsIconSizePx: 147.0px (56.0dp)
@@ -66,7 +66,7 @@
 	allAppsBorderSpacePxX: 42.0px (16.0dp)
 	allAppsBorderSpacePxY: 42.0px (16.0dp)
 	numShownAllAppsColumns: 5
-	allAppsPadding.top: 0.0px (0.0dp)
+	allAppsPadding.top: 74.0px (28.190475dp)
 	allAppsPadding.left: 0.0px (0.0dp)
 	allAppsPadding.right: 0.0px (0.0dp)
 	allAppsLeftRightMargin: 0.0px (0.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
index 1af9215..d107988 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/phoneVerticalBar3Button.txt
@@ -55,7 +55,7 @@
 	bottomSheetCloseDuration: 267
 	bottomSheetWorkspaceScale: 1.0
 	bottomSheetDepth: 0.0
-	allAppsShiftRange: 788.0px (300.1905dp)
+	allAppsShiftRange: 1080.0px (411.42856dp)
 	allAppsOpenDuration: 600
 	allAppsCloseDuration: 300
 	allAppsIconSizePx: 147.0px (56.0dp)
@@ -66,7 +66,7 @@
 	allAppsBorderSpacePxX: 42.0px (16.0dp)
 	allAppsBorderSpacePxY: 42.0px (16.0dp)
 	numShownAllAppsColumns: 5
-	allAppsPadding.top: 0.0px (0.0dp)
+	allAppsPadding.top: 74.0px (28.190475dp)
 	allAppsPadding.left: 0.0px (0.0dp)
 	allAppsPadding.right: 0.0px (0.0dp)
 	allAppsLeftRightMargin: 0.0px (0.0dp)
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/HotseatCellCenterRequest.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/HotseatCellCenterRequest.java
deleted file mode 100644
index 7eb035a..0000000
--- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/HotseatCellCenterRequest.java
+++ /dev/null
@@ -1,99 +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.testing.shared;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Request object for querying a hotseat cell region in Rect.
- */
-public class HotseatCellCenterRequest implements TestInformationRequest {
-    public final int cellInd;
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(cellInd);
-    }
-
-    @Override
-    public String getRequestName() {
-        return TestProtocol.REQUEST_HOTSEAT_CELL_CENTER;
-    }
-
-    public static final Parcelable.Creator<HotseatCellCenterRequest> CREATOR =
-            new Parcelable.Creator<HotseatCellCenterRequest>() {
-
-                @Override
-                public HotseatCellCenterRequest createFromParcel(Parcel source) {
-                    return new HotseatCellCenterRequest(source);
-                }
-
-                @Override
-                public HotseatCellCenterRequest[] newArray(int size) {
-                    return new HotseatCellCenterRequest[size];
-                }
-            };
-
-    private HotseatCellCenterRequest(int cellInd) {
-        this.cellInd = cellInd;
-    }
-
-    private HotseatCellCenterRequest(Parcel in) {
-        this(in.readInt());
-    }
-
-    /**
-     * Create a builder for HotseatCellCenterRequest.
-     *
-     * @return HotseatCellCenterRequest builder.
-     */
-    public static HotseatCellCenterRequest.Builder builder() {
-        return new HotseatCellCenterRequest.Builder();
-    }
-
-    /**
-     * HotseatCellCenterRequest Builder.
-     */
-    public static final class Builder {
-        private int mCellInd;
-
-        private Builder() {
-            mCellInd = 0;
-        }
-
-        /**
-         * Set the index of hotseat cells.
-         */
-        public HotseatCellCenterRequest.Builder setCellInd(int i) {
-            this.mCellInd = i;
-            return this;
-        }
-
-        /**
-         * build the HotseatCellCenterRequest.
-         */
-        public HotseatCellCenterRequest build() {
-            return new HotseatCellCenterRequest(mCellInd);
-        }
-    }
-}
diff --git a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/WorkspaceCellCenterRequest.java b/tests/multivalentTests/shared/com/android/launcher3/testing/shared/WorkspaceCellCenterRequest.java
deleted file mode 100644
index e2cd8ea..0000000
--- a/tests/multivalentTests/shared/com/android/launcher3/testing/shared/WorkspaceCellCenterRequest.java
+++ /dev/null
@@ -1,138 +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.testing.shared;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Request object for querying a workspace cell region in Rect.
- */
-public class WorkspaceCellCenterRequest implements TestInformationRequest {
-    public final int cellX;
-    public final int cellY;
-    public final int spanX;
-    public final int spanY;
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(cellX);
-        dest.writeInt(cellY);
-        dest.writeInt(spanX);
-        dest.writeInt(spanY);
-    }
-
-    public static final Parcelable.Creator<WorkspaceCellCenterRequest> CREATOR =
-            new Parcelable.Creator<WorkspaceCellCenterRequest>() {
-
-                @Override
-                public WorkspaceCellCenterRequest createFromParcel(Parcel source) {
-                    return new WorkspaceCellCenterRequest(source);
-                }
-
-                @Override
-                public WorkspaceCellCenterRequest[] newArray(int size) {
-                    return new WorkspaceCellCenterRequest[size];
-                }
-            };
-
-    private WorkspaceCellCenterRequest(int cellX, int cellY, int spanX, int spanY) {
-        this.cellX = cellX;
-        this.cellY = cellY;
-        this.spanX = spanX;
-        this.spanY = spanY;
-    }
-
-    private WorkspaceCellCenterRequest(Parcel in) {
-        this(in.readInt(), in.readInt(), in.readInt(), in.readInt());
-    }
-
-    /**
-     * Create a builder for WorkspaceCellRectRequest.
-     *
-     * @return WorkspaceCellRectRequest builder.
-     */
-    public static WorkspaceCellCenterRequest.Builder builder() {
-        return new WorkspaceCellCenterRequest.Builder();
-    }
-
-    @Override
-    public String getRequestName() {
-        return TestProtocol.REQUEST_WORKSPACE_CELL_CENTER;
-    }
-
-    /**
-     * WorkspaceCellRectRequest Builder.
-     */
-    public static final class Builder {
-        private int mCellX;
-        private int mCellY;
-        private int mSpanX;
-        private int mSpanY;
-
-        private Builder() {
-            this.mCellX = 0;
-            this.mCellY = 0;
-            this.mSpanX = 1;
-            this.mSpanY = 1;
-        }
-
-        /**
-         * Set X coordinate of upper left corner expressed as a cell position
-         */
-        public WorkspaceCellCenterRequest.Builder setCellX(int x) {
-            this.mCellX = x;
-            return this;
-        }
-
-        /**
-         * Set Y coordinate of upper left corner expressed as a cell position
-         */
-        public WorkspaceCellCenterRequest.Builder setCellY(int y) {
-            this.mCellY = y;
-            return this;
-        }
-
-        /**
-         * Set span Width in cells
-         */
-        public WorkspaceCellCenterRequest.Builder setSpanX(int x) {
-            this.mSpanX = x;
-            return this;
-        }
-
-        /**
-         * Set span Height in cells
-         */
-        public WorkspaceCellCenterRequest.Builder setSpanY(int y) {
-            this.mSpanY = y;
-            return this;
-        }
-
-        /**
-         * build the WorkspaceCellRectRequest.
-         */
-        public WorkspaceCellCenterRequest build() {
-            return new WorkspaceCellCenterRequest(mCellX, mCellY, mSpanX, mSpanY);
-        }
-    }
-}
diff --git a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index c4519eb..a30261e 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -26,24 +26,33 @@
 import android.platform.test.rule.LimitDevicesRule
 import android.util.DisplayMetrics
 import android.view.Surface
-import androidx.test.core.app.ApplicationProvider
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
 import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.LauncherPrefs.Companion.GRID_NAME
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.testing.shared.ResourceUtils
+import com.android.launcher3.util.AllModulesMinusWMProxy
 import com.android.launcher3.util.DisplayController
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.android.launcher3.util.FakePrefsModule
 import com.android.launcher3.util.NavigationMode
+import com.android.launcher3.util.SandboxContext
 import com.android.launcher3.util.WindowBounds
 import com.android.launcher3.util.rule.TestStabilityRule
+import com.android.launcher3.util.rule.ZipFilesRule
 import com.android.launcher3.util.rule.setFlags
 import com.android.launcher3.util.window.CachedDisplayInfo
 import com.android.launcher3.util.window.WindowManagerProxy
 import com.google.common.truth.Truth
+import dagger.BindsInstance
+import dagger.Component
 import java.io.BufferedReader
 import java.io.File
 import java.io.PrintWriter
 import java.io.StringWriter
 import kotlin.math.max
 import kotlin.math.min
+import org.junit.ClassRule
 import org.junit.Rule
 import org.mockito.kotlin.any
 import org.mockito.kotlin.doReturn
@@ -62,10 +71,10 @@
 abstract class AbstractDeviceProfileTest {
     protected val testContext: Context = InstrumentationRegistry.getInstrumentation().context
     protected lateinit var context: SandboxContext
-    protected open val runningContext: Context = ApplicationProvider.getApplicationContext()
+    protected open val runningContext: Context = getApplicationContext()
     private val displayController: DisplayController = mock()
     private val windowManagerProxy: WindowManagerProxy = mock()
-    private val launcherPrefs: LauncherPrefs = mock()
+    private lateinit var launcherPrefs: LauncherPrefs
 
     @get:Rule val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
 
@@ -73,6 +82,13 @@
 
     @Rule @JvmField val limitDevicesRule = LimitDevicesRule()
 
+    companion object {
+        @ClassRule
+        @JvmField
+        val resultZipRule =
+            ZipFilesRule(InstrumentationRegistry.getInstrumentation().targetContext, "DumpTest")
+    }
+
     class DeviceSpec(
         val naturalSize: Pair<Int, Int>,
         var densityDpi: Int,
@@ -127,6 +143,7 @@
         isGestureMode: Boolean = true,
         isVerticalBar: Boolean = false,
         isFixedLandscape: Boolean = false,
+        gridName: String? = GRID_NAME.defaultValue,
     ) {
         val (naturalX, naturalY) = deviceSpec.naturalSize
         val windowsBounds = phoneWindowsBounds(deviceSpec, isGestureMode, naturalX, naturalY)
@@ -140,6 +157,7 @@
             isGestureMode,
             densityDpi = deviceSpec.densityDpi,
             isFixedLandscape = isFixedLandscape,
+            gridName = gridName,
         )
     }
 
@@ -147,6 +165,7 @@
         deviceSpec: DeviceSpec,
         isLandscape: Boolean = false,
         isGestureMode: Boolean = true,
+        gridName: String? = GRID_NAME.defaultValue,
     ) {
         val (naturalX, naturalY) = deviceSpec.naturalSize
         val windowsBounds = tabletWindowsBounds(deviceSpec, naturalX, naturalY)
@@ -159,6 +178,7 @@
             rotation = if (isLandscape) Surface.ROTATION_0 else Surface.ROTATION_90,
             isGestureMode,
             densityDpi = deviceSpec.densityDpi,
+            gridName = gridName,
         )
     }
 
@@ -168,6 +188,7 @@
         isLandscape: Boolean = false,
         isGestureMode: Boolean = true,
         isFolded: Boolean = false,
+        gridName: String? = GRID_NAME.defaultValue,
     ) {
         val (unfoldedNaturalX, unfoldedNaturalY) = deviceSpecUnfolded.naturalSize
         val unfoldedWindowsBounds =
@@ -194,6 +215,7 @@
                 rotation = if (isLandscape) Surface.ROTATION_90 else Surface.ROTATION_0,
                 isGestureMode = isGestureMode,
                 densityDpi = deviceSpecFolded.densityDpi,
+                gridName = gridName,
             )
         } else {
             initializeCommonVars(
@@ -202,6 +224,7 @@
                 rotation = if (isLandscape) Surface.ROTATION_0 else Surface.ROTATION_90,
                 isGestureMode = isGestureMode,
                 densityDpi = deviceSpecUnfolded.densityDpi,
+                gridName = gridName,
             )
         }
     }
@@ -277,9 +300,11 @@
         isGestureMode: Boolean = true,
         densityDpi: Int,
         isFixedLandscape: Boolean = false,
+        gridName: String? = GRID_NAME.defaultValue,
     ) {
         setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
-        LauncherPrefs.get(testContext).put(LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE, true)
+        // TODO: re-enable as part of b/396211437
+        setFlagsRule.setFlags(false, Flags.FLAG_ENABLE_LAUNCHER_ICON_SHAPES)
         val windowsBounds = perDisplayBoundsCache[displayInfo]!!
         val realBounds = windowsBounds[rotation]
         whenever(windowManagerProxy.getDisplayInfo(any())).thenReturn(displayInfo)
@@ -290,9 +315,9 @@
             .thenReturn(
                 if (isGestureMode) NavigationMode.NO_BUTTON else NavigationMode.THREE_BUTTONS
             )
-        doReturn(WindowManagerProxy.INSTANCE[runningContext].isTaskbarDrawnInProcess)
+        doReturn(WindowManagerProxy.INSTANCE[getApplicationContext()].isTaskbarDrawnInProcess)
             .whenever(windowManagerProxy)
-            .isTaskbarDrawnInProcess()
+            .isTaskbarDrawnInProcess
 
         val density = densityDpi / DisplayMetrics.DENSITY_DEFAULT.toFloat()
         val config =
@@ -304,17 +329,26 @@
             }
         val configurationContext = runningContext.createConfigurationContext(config)
         context = SandboxContext(configurationContext)
-        context.putObject(DisplayController.INSTANCE, displayController)
-        context.putObject(WindowManagerProxy.INSTANCE, windowManagerProxy)
-        context.putObject(LauncherPrefs.INSTANCE, launcherPrefs)
+        context.initDaggerComponent(
+            DaggerAbsDPTestSandboxComponent.builder()
+                .bindWMProxy(windowManagerProxy)
+                .bindDisplayController(displayController)
+        )
+        launcherPrefs = context.appComponent.launcherPrefs
+        launcherPrefs.put(
+            LauncherPrefs.TASKBAR_PINNING.to(false),
+            LauncherPrefs.TASKBAR_PINNING_IN_DESKTOP_MODE.to(true),
+            LauncherPrefs.FIXED_LANDSCAPE_MODE.to(isFixedLandscape),
+            LauncherPrefs.HOTSEAT_COUNT.to(-1),
+            LauncherPrefs.DEVICE_TYPE.to(-1),
+            LauncherPrefs.WORKSPACE_SIZE.to(""),
+            LauncherPrefs.DB_FILE.to(""),
+            LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.to(true),
+        )
+        if (gridName != null) {
+            launcherPrefs.put(GRID_NAME, gridName)
+        }
 
-        whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING)).thenReturn(false)
-        whenever(launcherPrefs.get(LauncherPrefs.TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(true)
-        whenever(launcherPrefs.get(LauncherPrefs.FIXED_LANDSCAPE_MODE)).thenReturn(isFixedLandscape)
-        whenever(launcherPrefs.get(LauncherPrefs.HOTSEAT_COUNT)).thenReturn(-1)
-        whenever(launcherPrefs.get(LauncherPrefs.DEVICE_TYPE)).thenReturn(-1)
-        whenever(launcherPrefs.get(LauncherPrefs.WORKSPACE_SIZE)).thenReturn("")
-        whenever(launcherPrefs.get(LauncherPrefs.DB_FILE)).thenReturn("")
         val info = spy(DisplayController.Info(context, windowManagerProxy, perDisplayBoundsCache))
         whenever(displayController.info).thenReturn(info)
         whenever(info.isTransientTaskbar).thenReturn(isGestureMode)
@@ -339,7 +373,9 @@
         context.assets.open("dumpTests/$fileName").bufferedReader().use(BufferedReader::readText)
 
     private fun writeToDevice(context: Context, fileName: String, content: String) {
-        File(context.getDir("dumpTests", Context.MODE_PRIVATE), fileName).writeText(content)
+        val file = File(context.getDir("dumpTests", Context.MODE_PRIVATE), fileName)
+        file.writeText(content)
+        resultZipRule.write(file)
     }
 
     protected fun Float.dpToPx(): Float {
@@ -355,3 +391,17 @@
         return context.resources.getIdentifier(this, "xml", context.packageName)
     }
 }
+
+@LauncherAppSingleton
+@Component(modules = [AllModulesMinusWMProxy::class, FakePrefsModule::class])
+interface AbsDPTestSandboxComponent : LauncherAppComponent {
+
+    @Component.Builder
+    interface Builder : LauncherAppComponent.Builder {
+        @BindsInstance fun bindWMProxy(proxy: WindowManagerProxy): Builder
+
+        @BindsInstance fun bindDisplayController(displayController: DisplayController): Builder
+
+        override fun build(): AbsDPTestSandboxComponent
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt b/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
index 0e06051..6483bd5 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AppWidgetsRestoredReceiverTest.kt
@@ -6,7 +6,7 @@
 import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS
 import android.appwidget.AppWidgetManager.EXTRA_HOST_ID
 import android.content.Intent
-import android.platform.uiautomator_helpers.DeviceHelpers
+import android.platform.uiautomatorhelpers.DeviceHelpers
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.launcher3.LauncherPrefs.Companion.APP_WIDGET_IDS
diff --git a/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt b/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt
index f73a9d3..97ecafe 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AutoInstallsLayoutTest.kt
@@ -45,6 +45,7 @@
 import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.model.data.AppInfo
 import com.android.launcher3.pm.UserCache
+import com.android.launcher3.util.AllModulesMinusApiWrapper
 import com.android.launcher3.util.ApiWrapper
 import com.android.launcher3.util.Executors
 import com.android.launcher3.util.LauncherLayoutBuilder
@@ -66,7 +67,7 @@
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.spy
+import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
 
 /** Tests for [AutoInstallsLayout] */
@@ -165,7 +166,9 @@
 
     @Test
     fun work_item_added_to_home() {
-        val apiWrapperMock = spy(ApiWrapper.INSTANCE[targetContext])
+        val original = ApiWrapper.INSTANCE[targetContext]
+        val apiWrapperMock =
+            mock<MyApiWrapper>(defaultAnswer = { it.method.invoke(original, *it.arguments) })
         targetContext.initDaggerComponent(
             DaggerAutoInstallsLayoutTestComponent.builder().bindApiWrapper(apiWrapperMock)
         )
@@ -221,9 +224,12 @@
     }
 }
 
+class MyApiWrapper : ApiWrapper(null)
+
 @LauncherAppSingleton
-@Component
+@Component(modules = [AllModulesMinusApiWrapper::class])
 interface AutoInstallsLayoutTestComponent : LauncherAppComponent {
+
     @Component.Builder
     interface Builder : LauncherAppComponent.Builder {
         @BindsInstance fun bindApiWrapper(wrapper: ApiWrapper): Builder
diff --git a/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
index 954dc8f..f855c51 100644
--- a/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/FakeInvariantDeviceProfileTest.kt
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3
 
-import android.content.Context
 import android.graphics.PointF
 import android.graphics.Rect
 import android.platform.test.rule.AllowedDevices
@@ -23,10 +22,11 @@
 import android.platform.test.rule.IgnoreLimit
 import android.platform.test.rule.LimitDevicesRule
 import android.util.SparseArray
-import androidx.test.core.app.ApplicationProvider
 import com.android.launcher3.DeviceProfile.DEFAULT_DIMENSION_PROVIDER
 import com.android.launcher3.DeviceProfile.DEFAULT_PROVIDER
+import com.android.launcher3.testing.shared.ResourceUtils.INVALID_RESOURCE_HANDLE
 import com.android.launcher3.util.DisplayController.Info
+import com.android.launcher3.util.SandboxApplication
 import com.android.launcher3.util.WindowBounds
 import java.io.PrintWriter
 import java.io.StringWriter
@@ -46,7 +46,8 @@
 @IgnoreLimit(ignoreLimit = BuildConfig.IS_STUDIO_BUILD)
 abstract class FakeInvariantDeviceProfileTest {
 
-    protected lateinit var context: Context
+    @get:Rule val context = SandboxApplication()
+
     protected lateinit var inv: InvariantDeviceProfile
     protected val info = mock<Info>()
     protected lateinit var windowBounds: WindowBounds
@@ -58,8 +59,7 @@
     @Rule @JvmField val limitDevicesRule = LimitDevicesRule()
 
     @Before
-    fun setUp() {
-        context = ApplicationProvider.getApplicationContext()
+    open fun setUp() {
         // make sure to reset values
         useTwoPanels = false
         isGestureMode = true
@@ -70,6 +70,8 @@
             context,
             inv,
             info,
+            context.appComponent.wmProxy,
+            context.appComponent.themeManager,
             windowBounds,
             SparseArray(),
             /*isMultiWindowMode=*/ false,
@@ -83,7 +85,7 @@
 
     protected fun initializeVarsForPhone(
         isGestureMode: Boolean = true,
-        isVerticalBar: Boolean = false
+        isVerticalBar: Boolean = false,
     ) {
         val (x, y) = if (isVerticalBar) Pair(2400, 1080) else Pair(1080, 2400)
 
@@ -94,8 +96,8 @@
                     if (isVerticalBar) 118 else 0,
                     if (isVerticalBar) 74 else 118,
                     if (!isGestureMode && isVerticalBar) 126 else 0,
-                    if (isGestureMode) 63 else if (isVerticalBar) 0 else 126
-                )
+                    if (isGestureMode) 63 else if (isVerticalBar) 0 else 126,
+                ),
             )
 
         whenever(info.isTablet(any())).thenReturn(false)
@@ -107,7 +109,7 @@
         transposeLayoutWithOrientation = true
 
         inv =
-            InvariantDeviceProfile().apply {
+            context.appComponent.idp.apply {
                 numRows = 5
                 numColumns = 4
                 numSearchContainerColumns = 4
@@ -121,7 +123,7 @@
                             PointF(80f, 104f),
                             PointF(80f, 104f),
                             PointF(80f, 104f),
-                            PointF(80f, 104f)
+                            PointF(80f, 104f),
                         )
                         .toTypedArray()
 
@@ -143,7 +145,7 @@
                             PointF(80f, 104f),
                             PointF(80f, 104f),
                             PointF(80f, 104f),
-                            PointF(80f, 104f)
+                            PointF(80f, 104f),
                         )
                         .toTypedArray()
                 allAppsIconSize = floatArrayOf(60f, 60f, 60f, 60f)
@@ -169,12 +171,20 @@
                 inlineQsb = BooleanArray(4) { false }
 
                 devicePaddingId = R.xml.paddings_handhelds
+
+                isFixedLandscape = false
+                workspaceSpecsId = INVALID_RESOURCE_HANDLE
+                allAppsSpecsId = INVALID_RESOURCE_HANDLE
+                folderSpecsId = INVALID_RESOURCE_HANDLE
+                hotseatSpecsId = INVALID_RESOURCE_HANDLE
+                workspaceCellSpecsId = INVALID_RESOURCE_HANDLE
+                allAppsCellSpecsId = INVALID_RESOURCE_HANDLE
             }
     }
 
     protected fun initializeVarsForTablet(
         isLandscape: Boolean = false,
-        isGestureMode: Boolean = true
+        isGestureMode: Boolean = true,
     ) {
         val (x, y) = if (isLandscape) Pair(2560, 1600) else Pair(1600, 2560)
 
@@ -189,7 +199,7 @@
         useTwoPanels = false
 
         inv =
-            InvariantDeviceProfile().apply {
+            context.appComponent.idp.apply {
                 numRows = 5
                 numColumns = 6
                 numSearchContainerColumns = 3
@@ -203,7 +213,7 @@
                             PointF(102f, 120f),
                             PointF(120f, 104f),
                             PointF(102f, 120f),
-                            PointF(102f, 120f)
+                            PointF(102f, 120f),
                         )
                         .toTypedArray()
 
@@ -225,7 +235,7 @@
                             PointF(96f, 142f),
                             PointF(126f, 126f),
                             PointF(96f, 142f),
-                            PointF(96f, 142f)
+                            PointF(96f, 142f),
                         )
                         .toTypedArray()
                 allAppsIconSize = FloatArray(4) { 60f }
@@ -252,6 +262,14 @@
                 inlineQsb = booleanArrayOf(false, true, false, false)
 
                 devicePaddingId = R.xml.paddings_handhelds
+
+                isFixedLandscape = false
+                workspaceSpecsId = INVALID_RESOURCE_HANDLE
+                allAppsSpecsId = INVALID_RESOURCE_HANDLE
+                folderSpecsId = INVALID_RESOURCE_HANDLE
+                hotseatSpecsId = INVALID_RESOURCE_HANDLE
+                workspaceCellSpecsId = INVALID_RESOURCE_HANDLE
+                allAppsCellSpecsId = INVALID_RESOURCE_HANDLE
             }
     }
 
@@ -274,7 +292,7 @@
         useTwoPanels = true
 
         inv =
-            InvariantDeviceProfile().apply {
+            context.appComponent.idp.apply {
                 numRows = rows
                 numColumns = cols
                 numSearchContainerColumns = cols
@@ -288,7 +306,7 @@
                             PointF(80f, 104f),
                             PointF(80f, 104f),
                             PointF(68f, 116f),
-                            PointF(80f, 102f)
+                            PointF(80f, 102f),
                         )
                         .toTypedArray()
 
@@ -332,6 +350,14 @@
                 inlineQsb = booleanArrayOf(false, false, false, false)
 
                 devicePaddingId = R.xml.paddings_handhelds
+
+                isFixedLandscape = false
+                workspaceSpecsId = INVALID_RESOURCE_HANDLE
+                allAppsSpecsId = INVALID_RESOURCE_HANDLE
+                folderSpecsId = INVALID_RESOURCE_HANDLE
+                hotseatSpecsId = INVALID_RESOURCE_HANDLE
+                workspaceCellSpecsId = INVALID_RESOURCE_HANDLE
+                allAppsCellSpecsId = INVALID_RESOURCE_HANDLE
             }
     }
 
diff --git a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt
index 946bbc5..4d01d4d 100644
--- a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefs.kt
@@ -17,61 +17,28 @@
 package com.android.launcher3
 
 import android.content.Context
-import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import android.content.Context.MODE_PRIVATE
+import android.content.SharedPreferences
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.DaggerSingletonTracker
+import java.util.UUID
+import javax.inject.Inject
 
 /** Emulates Launcher preferences for a test environment. */
-class FakeLauncherPrefs(private val context: Context) : LauncherPrefs() {
-    private val prefsMap = mutableMapOf<String, Any>()
-    private val listeners = mutableSetOf<LauncherPrefChangeListener>()
+@LauncherAppSingleton
+class FakeLauncherPrefs
+@Inject
+constructor(@ApplicationContext context: Context, lifeCycle: DaggerSingletonTracker) :
+    LauncherPrefs(context) {
 
-    @Suppress("UNCHECKED_CAST")
-    override fun <T> get(item: ContextualItem<T>): T {
-        return prefsMap.getOrDefault(item.sharedPrefKey, item.defaultValueFromContext(context)) as T
+    private val prefName = "fake-pref-" + UUID.randomUUID().toString()
+
+    private val backingPrefs = context.getSharedPreferences(prefName, MODE_PRIVATE)
+
+    init {
+        lifeCycle.addCloseable { context.deleteSharedPreferences(prefName) }
     }
 
-    @Suppress("UNCHECKED_CAST")
-    override fun <T> get(item: ConstantItem<T>): T {
-        return prefsMap.getOrDefault(item.sharedPrefKey, item.defaultValue) as T
-    }
-
-    override fun put(vararg itemsToValues: Pair<Item, Any>) = putSync(*itemsToValues)
-
-    override fun <T : Any> put(item: Item, value: T) = putSync(item to value)
-
-    override fun putSync(vararg itemsToValues: Pair<Item, Any>) {
-        itemsToValues
-            .map { (i, v) -> i.sharedPrefKey to v }
-            .forEach { (k, v) ->
-                prefsMap[k] = v
-                notifyChange(k)
-            }
-    }
-
-    override fun addListener(listener: LauncherPrefChangeListener, vararg items: Item) {
-        listeners.add(listener)
-    }
-
-    override fun removeListener(listener: LauncherPrefChangeListener, vararg items: Item) {
-        listeners.remove(listener)
-    }
-
-    override fun has(vararg items: Item) = items.all { it.sharedPrefKey in prefsMap }
-
-    override fun remove(vararg items: Item) = removeSync(*items)
-
-    override fun removeSync(vararg items: Item) {
-        items
-            .filter { it.sharedPrefKey in prefsMap }
-            .forEach {
-                prefsMap.remove(it.sharedPrefKey)
-                notifyChange(it.sharedPrefKey)
-            }
-    }
-
-    override fun close() = Unit
-
-    private fun notifyChange(key: String) {
-        // Mimics SharedPreferencesImpl#notifyListeners main thread dispatching.
-        MAIN_EXECUTOR.execute { listeners.forEach { it.onPrefChanged(key) } }
-    }
+    override fun getSharedPrefs(item: Item): SharedPreferences = backingPrefs
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt
index 2463c93..0941c79 100644
--- a/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/FakeLauncherPrefsTest.kt
@@ -18,10 +18,14 @@
 
 import androidx.test.core.app.ApplicationProvider.getApplicationContext
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.launcher3.util.DaggerSingletonTracker
 import com.android.launcher3.util.LauncherMultivalentJUnit
 import com.google.common.truth.Truth.assertThat
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
 
 private val TEST_CONSTANT_ITEM = LauncherPrefs.nonRestorableItem("TEST_BOOLEAN_ITEM", false)
 
@@ -36,7 +40,15 @@
 
 @RunWith(LauncherMultivalentJUnit::class)
 class FakeLauncherPrefsTest {
-    private val launcherPrefs = FakeLauncherPrefs(getApplicationContext())
+
+    @Mock lateinit var lifeCycle: DaggerSingletonTracker
+    private lateinit var launcherPrefs: FakeLauncherPrefs
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        launcherPrefs = FakeLauncherPrefs(getApplicationContext(), lifeCycle)
+    }
 
     @Test
     fun testGet_constantItemNotInPrefs_returnsDefaultValue() {
@@ -128,7 +140,7 @@
         val listener = LauncherPrefChangeListener { changedKey = it }
         launcherPrefs.addListener(listener, TEST_CONSTANT_ITEM)
 
-        launcherPrefs.removeListener(listener)
+        launcherPrefs.removeListener(listener, TEST_CONSTANT_ITEM)
         getInstrumentation().runOnMainSync { launcherPrefs.put(TEST_CONSTANT_ITEM, true) }
         assertThat(changedKey).isNull()
     }
diff --git a/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
index 4aeef2e..da9cc86 100644
--- a/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/LauncherPrefsTest.kt
@@ -24,12 +24,18 @@
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
+import org.junit.Assert.assertThrows
 import org.junit.Test
 import org.junit.runner.RunWith
 
 private val TEST_BOOLEAN_ITEM = LauncherPrefs.nonRestorableItem("1", false)
 private val TEST_STRING_ITEM = LauncherPrefs.nonRestorableItem("2", "( ͡❛ ͜ʖ ͡❛)")
 private val TEST_INT_ITEM = LauncherPrefs.nonRestorableItem("3", -1)
+private val TEST_FLOAT_ITEM = LauncherPrefs.nonRestorableItem("4", -1f)
+private val TEST_LONG_ITEM = LauncherPrefs.nonRestorableItem("5", -1L)
+private val TEST_SET_ITEM = LauncherPrefs.nonRestorableItem("6", setOf<String>())
+private val TEST_HASHSET_ITEM = LauncherPrefs.nonRestorableItem("7", hashSetOf<String>())
+
 private val TEST_CONTEXTUAL_ITEM =
     ContextualItem("4", true, { true }, EncryptionType.ENCRYPTED, Boolean::class.java)
 
@@ -144,15 +150,49 @@
     }
 
     @Test
+    fun whenItemType_isInvalid_thenThrowException() {
+        val badItem = LauncherPrefs.nonRestorableItem("8", mapOf<String, String>())
+        with(launcherPrefs) {
+            assertThrows(IllegalArgumentException::class.java) {
+                putSync(badItem.to(badItem.defaultValue))
+            }
+            assertThrows(IllegalArgumentException::class.java) { get(badItem) }
+        }
+    }
+
+    @Test
     fun put_storesListOfItemsInLauncherPrefs_successfully() {
         with(launcherPrefs) {
             putSync(
                 TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue),
                 TEST_INT_ITEM.to(TEST_INT_ITEM.defaultValue),
                 TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue),
+                TEST_FLOAT_ITEM.to(TEST_FLOAT_ITEM.defaultValue),
+                TEST_LONG_ITEM.to(TEST_LONG_ITEM.defaultValue),
+                TEST_SET_ITEM.to(TEST_SET_ITEM.defaultValue),
+                TEST_HASHSET_ITEM.to(TEST_HASHSET_ITEM.defaultValue),
             )
-            assertThat(has(TEST_BOOLEAN_ITEM, TEST_INT_ITEM, TEST_STRING_ITEM)).isTrue()
-            remove(TEST_STRING_ITEM, TEST_INT_ITEM, TEST_BOOLEAN_ITEM)
+            assertThat(
+                    has(
+                        TEST_STRING_ITEM,
+                        TEST_INT_ITEM,
+                        TEST_BOOLEAN_ITEM,
+                        TEST_FLOAT_ITEM,
+                        TEST_LONG_ITEM,
+                        TEST_SET_ITEM,
+                        TEST_HASHSET_ITEM,
+                    )
+                )
+                .isTrue()
+            remove(
+                TEST_STRING_ITEM,
+                TEST_INT_ITEM,
+                TEST_BOOLEAN_ITEM,
+                TEST_FLOAT_ITEM,
+                TEST_LONG_ITEM,
+                TEST_SET_ITEM,
+                TEST_HASHSET_ITEM,
+            )
         }
     }
 
diff --git a/tests/multivalentTests/src/com/android/launcher3/ProxyPrefsTest.kt b/tests/multivalentTests/src/com/android/launcher3/ProxyPrefsTest.kt
new file mode 100644
index 0000000..54f6f63
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/ProxyPrefsTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3
+
+import android.content.Context.MODE_PRIVATE
+import android.platform.uiautomatorhelpers.DeviceHelpers.context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.LauncherPrefs.Companion.backedUpItem
+import com.google.common.truth.Truth.assertThat
+import java.util.UUID
+import org.junit.After
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ProxyPrefsTest {
+
+    private val prefName = "pref-test-" + UUID.randomUUID().toString()
+
+    private val proxyPrefs by lazy {
+        ProxyPrefs(
+            context,
+            context.getSharedPreferences(prefName, MODE_PRIVATE).apply { edit().clear().commit() },
+        )
+    }
+    private val launcherPrefs by lazy { LauncherPrefs(context) }
+
+    @After
+    fun tearDown() {
+        context.deleteSharedPreferences(prefName)
+    }
+
+    @Test
+    fun `returns fallback value if present`() {
+        launcherPrefs.putSync(TEST_ENTRY.to("new_value"))
+        assertThat(proxyPrefs.get(TEST_ENTRY)).isEqualTo("new_value")
+    }
+
+    @Test
+    fun `returns default value if not present`() {
+        launcherPrefs.removeSync(TEST_ENTRY)
+        assertThat(proxyPrefs.get(TEST_ENTRY)).isEqualTo("default_value")
+    }
+
+    @Test
+    fun `returns overridden value if present`() {
+        launcherPrefs.putSync(TEST_ENTRY.to("new_value"))
+        proxyPrefs.putSync(TEST_ENTRY.to("overridden_value"))
+        assertThat(proxyPrefs.get(TEST_ENTRY)).isEqualTo("overridden_value")
+    }
+
+    @Test
+    fun `value not present when removed`() {
+        launcherPrefs.putSync(TEST_ENTRY.to("new_value"))
+        proxyPrefs.removeSync(TEST_ENTRY)
+        assertThat(proxyPrefs.has(TEST_ENTRY)).isFalse()
+    }
+
+    @Test
+    fun `returns default if removed`() {
+        launcherPrefs.putSync(TEST_ENTRY.to("new_value"))
+        proxyPrefs.removeSync(TEST_ENTRY)
+        assertThat(proxyPrefs.get(TEST_ENTRY)).isEqualTo("default_value")
+    }
+
+    @Test
+    fun `value present on init`() {
+        launcherPrefs.putSync(TEST_ENTRY.to("new_value"))
+        assertThat(proxyPrefs.has(TEST_ENTRY)).isTrue()
+    }
+
+    @Test
+    fun `value absent on init`() {
+        launcherPrefs.removeSync(TEST_ENTRY)
+        assertThat(proxyPrefs.has(TEST_ENTRY)).isFalse()
+    }
+
+    companion object {
+
+        val TEST_ENTRY =
+            backedUpItem("test_prefs", "default_value", EncryptionType.DEVICE_PROTECTED)
+    }
+}
diff --git a/tests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt b/tests/multivalentTests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
similarity index 83%
rename from tests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
index cf03adc..7ddd859 100644
--- a/tests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/allapps/FloatingMaskViewTest.kt
@@ -20,28 +20,33 @@
 import android.view.ViewGroup.MarginLayoutParams
 import android.widget.ImageView
 import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.launcher3.util.ActivityContextWrapper
 import com.google.common.truth.Truth
 import org.junit.Before
 import org.junit.Test
+import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.MockitoAnnotations
 
+@RunWith(AndroidJUnit4::class)
 class FloatingMaskViewTest {
-    @Mock
-    private val mockAllAppsRecyclerView: AllAppsRecyclerView? = null
+    @Mock private val mockAllAppsRecyclerView: AllAppsRecyclerView? = null
 
-    @Mock
-    private val mockBottomBox: ImageView? = null
+    @Mock private val mockBottomBox: ImageView? = null
     private var mVut: FloatingMaskView? = null
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         val context: Context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
         mVut = FloatingMaskView(context)
-        mVut!!.layoutParams = MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
-                ViewGroup.LayoutParams.WRAP_CONTENT)
+        mVut!!.layoutParams =
+            MarginLayoutParams(
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+            )
     }
 
     @Test
diff --git a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java b/tests/multivalentTests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
similarity index 99%
rename from tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
rename to tests/multivalentTests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
index 430e496..d757d10 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/allapps/PrivateProfileManagerTest.java
@@ -42,7 +42,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.launcher3.logging.StatsLogManager;
 import com.android.launcher3.pm.UserCache;
diff --git a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java b/tests/multivalentTests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
similarity index 98%
rename from tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
rename to tests/multivalentTests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
index 398f9c5..23b00c2 100644
--- a/tests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/allapps/PrivateSpaceHeaderViewTest.java
@@ -52,8 +52,8 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.R;
 import com.android.launcher3.logging.StatsLogManager;
@@ -300,7 +300,7 @@
                 && info.user.equals(MAIN_HANDLE));
 
         int rows = (int) (ALL_APPS_HEIGHT - PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT);
-        int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+        int position = rows * NUM_APP_COLS - (NUM_APP_COLS - 1) + 1;
 
         assertEquals(NUM_PRIVATE_SPACE_APPS + MAIN_USER_APP_COUNT
                         + CONTAINER_HEADER_ELEMENT_COUNT + VIEW_AT_END_OF_APP_LIST,
@@ -335,7 +335,7 @@
                 && info.user.equals(MAIN_HANDLE));
 
         int rows = (int) (ALL_APPS_HEIGHT - PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT) - 1;
-        int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+        int position = rows * NUM_APP_COLS - (NUM_APP_COLS - 1) + 1;
 
         assertEquals(NUM_PRIVATE_SPACE_APPS + MAIN_USER_APP_COUNT
                         + CONTAINER_HEADER_ELEMENT_COUNT + VIEW_AT_END_OF_APP_LIST,
@@ -370,7 +370,7 @@
                 && info.user.equals(MAIN_HANDLE));
 
         int rows = (int) (ALL_APPS_HEIGHT - BIGGER_PS_HEADER_HEIGHT - HEADER_PROTECTION_HEIGHT);
-        int position = rows * NUM_APP_COLS - (NUM_APP_COLS-1) + 1;
+        int position = rows * NUM_APP_COLS - (NUM_APP_COLS - 1) + 1;
 
         assertEquals(NUM_PRIVATE_SPACE_APPS + MAIN_USER_APP_COUNT
                         + CONTAINER_HEADER_ELEMENT_COUNT + VIEW_AT_END_OF_APP_LIST,
diff --git a/tests/src/com/android/launcher3/allapps/WorkUtilityViewTest.java b/tests/multivalentTests/src/com/android/launcher3/allapps/WorkUtilityViewTest.java
similarity index 100%
rename from tests/src/com/android/launcher3/allapps/WorkUtilityViewTest.java
rename to tests/multivalentTests/src/com/android/launcher3/allapps/WorkUtilityViewTest.java
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
index a9082e2..47110fa 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
@@ -61,9 +61,11 @@
             ModelDbController controller = model.getModelDbController();
             // Migrate any previous data so that the DB state is correct
             if (Flags.gridMigrationRefactor()) {
-                controller.attemptMigrateDb(null /* restoreEventLogger */);
+                controller.attemptMigrateDb(
+                        null /* restoreEventLogger */, model.getModelDelegate());
             } else {
-                controller.tryMigrateDB(null /* restoreEventLogger */);
+                controller.tryMigrateDB(null /* restoreEventLogger */,
+                        model.getModelDelegate());
             }
 
             // Create DB again to load fresh data
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
index a62258c..bf8e8b1 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/ReorderAlgorithmUnitTest.java
@@ -17,9 +17,6 @@
 
 import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
@@ -41,7 +38,6 @@
 import com.android.launcher3.celllayout.testgenerator.RandomMultiBoardGenerator;
 import com.android.launcher3.util.ActivityContextWrapper;
 import com.android.launcher3.util.rule.TestStabilityRule;
-import com.android.launcher3.util.rule.TestStabilityRule.Stability;
 import com.android.launcher3.views.DoubleShadowBubbleTextView;
 
 import org.junit.Rule;
@@ -82,7 +78,6 @@
      * This test reads existing test cases and makes sure the CellLayout produces the same
      * output for each of them for a given input.
      */
-    @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
     @Test
     public void testAllCases() throws IOException {
         List<ReorderAlgorithmUnitTestCase> testCases = getTestCases(
@@ -125,7 +120,6 @@
     /**
      * Same as above but testing the Multipage CellLayout.
      */
-    @Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
     @Test
     public void generateValidTests_Multi() {
         Random generator = new Random(SEED);
diff --git a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.kt
index 8952b85..2e556e8 100644
--- a/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/board/TestWorkspaceBuilder.kt
@@ -80,6 +80,15 @@
             )
     }
 
+    /**
+     * Sets the test app for app icons to the specified Component
+     *
+     * @param testAppComponent ComponentName to use for app icons
+     */
+    fun setTestAppComponent(testAppComponent: ComponentName) {
+        appComponentName = testAppComponent
+    }
+
     private fun addCorrespondingWidgetRect(
         widgetRect: WidgetRect,
         transaction: FavoriteItemsTransaction,
diff --git a/tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt
similarity index 95%
rename from tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt
rename to tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt
index a006fd7..ad80b2d 100644
--- a/tests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/celllayout/testgenerator/ValidGridMigrationTestCaseGenerator.kt
@@ -32,7 +32,7 @@
  */
 fun generateItemsForTest(
     boards: List<CellLayoutBoard>,
-    repeatAfterRange: Point
+    repeatAfterRange: Point,
 ): List<WorkspaceItem> {
     val id = AtomicInteger(0)
     val widgetId = AtomicInteger(LauncherAppWidgetInfo.CUSTOM_WIDGET_ID - 1)
@@ -56,7 +56,7 @@
                 appWidgetProvider = "Hotseat icons don't have a provider",
                 intent = getIntent(id.get()),
                 type = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION,
-                container = LauncherSettings.Favorites.CONTAINER_HOTSEAT
+                container = LauncherSettings.Favorites.CONTAINER_HOTSEAT,
             )
         }
     var widgetEntries =
@@ -75,7 +75,7 @@
                     appWidgetProvider = getProvider(id.get()),
                     intent = "Widgets don't have intent",
                     type = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET,
-                    container = LauncherSettings.Favorites.CONTAINER_DESKTOP
+                    container = LauncherSettings.Favorites.CONTAINER_DESKTOP,
                 )
             }
     widgetEntries = widgetEntries.filter { it.appWidgetProvider.contains("Provider4") }
@@ -95,7 +95,7 @@
                     appWidgetProvider = "Icons don't have providers",
                     intent = getIntent(id.get()),
                     type = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION,
-                    container = LauncherSettings.Favorites.CONTAINER_DESKTOP
+                    container = LauncherSettings.Favorites.CONTAINER_DESKTOP,
                 )
             }
     return widgetEntries + hotseatEntries + iconEntries
@@ -106,7 +106,7 @@
     val destBoards: List<CellLayoutBoard>,
     val srcSize: Point,
     val targetSize: Point,
-    val seed: Long
+    val seed: Long,
 )
 
 class ValidGridMigrationTestCaseGenerator(private val generator: Random) :
@@ -122,7 +122,7 @@
         boardGenerator: RandomBoardGenerator,
         width: Int,
         height: Int,
-        boardCount: Int
+        boardCount: Int,
     ): List<CellLayoutBoard> {
         val boards = mutableListOf<CellLayoutBoard>()
         for (i in 0 until boardCount) {
@@ -130,7 +130,7 @@
                 boardGenerator.generateBoard(
                     width,
                     height,
-                    boardGenerator.getRandom(0, width * height)
+                    boardGenerator.getRandom(0, width * height),
                 )
             )
         }
@@ -145,7 +145,7 @@
         val targetSize =
             Point(
                 randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE),
-                randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE)
+                randomBoardGenerator.getRandom(3, MAX_BOARD_SIZE),
             )
         val destBoards =
             if (isDestEmpty) {
@@ -155,7 +155,7 @@
                     boardGenerator = randomBoardGenerator,
                     width = targetSize.x,
                     height = targetSize.y,
-                    boardCount = randomBoardGenerator.getRandom(3, MAX_BOARD_COUNT)
+                    boardCount = randomBoardGenerator.getRandom(3, MAX_BOARD_COUNT),
                 )
             }
         return GridMigrationUnitTestCase(
@@ -164,12 +164,12 @@
                     boardGenerator = randomBoardGenerator,
                     width = width,
                     height = height,
-                    boardCount = randomBoardGenerator.getRandom(3, MAX_BOARD_COUNT)
+                    boardCount = randomBoardGenerator.getRandom(3, MAX_BOARD_COUNT),
                 ),
             destBoards = destBoards,
             srcSize = Point(width, height),
             targetSize = targetSize,
-            seed = seed
+            seed = seed,
         )
     }
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/FolderTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/FolderTest.kt
index 4eb335e..bcf5f0d 100644
--- a/tests/multivalentTests/src/com/android/launcher3/folder/FolderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/folder/FolderTest.kt
@@ -58,6 +58,7 @@
 import junit.framework.TestCase.assertNull
 import junit.framework.TestCase.assertTrue
 import org.junit.After
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito
@@ -74,10 +75,16 @@
 @RunWith(AndroidJUnit4::class)
 class FolderTest {
 
-    private val context: Context =
-        ActivityContextWrapper(ApplicationProvider.getApplicationContext())
-    private val workspaceBuilder = TestWorkspaceBuilder(context)
-    private val folder: Folder = spy(Folder(context, null))
+    private lateinit var context: Context
+    private lateinit var workspaceBuilder: TestWorkspaceBuilder
+    private lateinit var folder: Folder
+
+    @Before
+    fun setUp() {
+        context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
+        workspaceBuilder = TestWorkspaceBuilder(context)
+        folder = spy(Folder(context, null))
+    }
 
     @After
     fun tearDown() {
@@ -412,7 +419,7 @@
             folder.onEditorAction(
                 Mockito.mock(TextView::class.java),
                 EditorInfo.IME_ACTION_DONE,
-                Mockito.mock(KeyEvent::class.java)
+                Mockito.mock(KeyEvent::class.java),
             )
 
         assertTrue(result)
@@ -427,7 +434,7 @@
             folder.onEditorAction(
                 Mockito.mock(TextView::class.java),
                 EditorInfo.IME_ACTION_NONE,
-                Mockito.mock(KeyEvent::class.java)
+                Mockito.mock(KeyEvent::class.java),
             )
 
         assertFalse(result)
@@ -824,7 +831,7 @@
         val items =
             arrayListOf<ItemInfo>(
                 Mockito.mock(ItemInfo::class.java),
-                Mockito.mock(ItemInfo::class.java)
+                Mockito.mock(ItemInfo::class.java),
             )
         val view1 = Mockito.mock(View::class.java)
         val view2 = Mockito.mock(View::class.java)
@@ -845,7 +852,7 @@
         val items =
             arrayListOf<ItemInfo>(
                 Mockito.mock(ItemInfo::class.java),
-                Mockito.mock(ItemInfo::class.java)
+                Mockito.mock(ItemInfo::class.java),
             )
         val view1 = Mockito.mock(View::class.java)
         val view2 = Mockito.mock(View::class.java)
@@ -867,7 +874,7 @@
         val items =
             arrayListOf<ItemInfo>(
                 Mockito.mock(ItemInfo::class.java),
-                Mockito.mock(ItemInfo::class.java)
+                Mockito.mock(ItemInfo::class.java),
             )
         val view1 = Mockito.mock(View::class.java)
         val view2 = Mockito.mock(View::class.java)
@@ -887,7 +894,7 @@
         val items =
             arrayListOf<ItemInfo>(
                 Mockito.mock(ItemInfo::class.java),
-                Mockito.mock(ItemInfo::class.java)
+                Mockito.mock(ItemInfo::class.java),
             )
         val view1 = Mockito.mock(View::class.java)
         val view2 = Mockito.mock(View::class.java)
@@ -908,7 +915,7 @@
         val items =
             arrayListOf<ItemInfo>(
                 Mockito.mock(ItemInfo::class.java),
-                Mockito.mock(ItemInfo::class.java)
+                Mockito.mock(ItemInfo::class.java),
             )
         val view1 = Mockito.mock(View::class.java)
         val view2 = Mockito.mock(View::class.java)
diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
deleted file mode 100644
index 7f481b7..0000000
--- a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
+++ /dev/null
@@ -1,237 +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.folder
-
-import android.R
-import android.content.Context
-import android.graphics.Bitmap
-import android.os.Process
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
-import com.android.launcher3.LauncherPrefs.Companion.THEMED_ICONS
-import com.android.launcher3.LauncherPrefs.Companion.get
-import com.android.launcher3.graphics.PreloadIconDrawable
-import com.android.launcher3.icons.FastBitmapDrawable
-import com.android.launcher3.icons.UserBadgeDrawable
-import com.android.launcher3.icons.mono.MonoThemedBitmap
-import com.android.launcher3.model.data.FolderInfo
-import com.android.launcher3.model.data.ItemInfo
-import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED
-import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE
-import com.android.launcher3.model.data.WorkspaceItemInfo
-import com.android.launcher3.util.ActivityContextWrapper
-import com.android.launcher3.util.Executors
-import com.android.launcher3.util.FlagOp
-import com.android.launcher3.util.LauncherLayoutBuilder
-import com.android.launcher3.util.LauncherModelHelper
-import com.android.launcher3.util.TestUtil
-import com.android.launcher3.util.UserIconInfo
-import com.google.common.truth.Truth.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/** Tests for [PreviewItemManager] */
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class PreviewItemManagerTest {
-
-    private lateinit var previewItemManager: PreviewItemManager
-    private lateinit var context: Context
-    private lateinit var folderItems: ArrayList<ItemInfo>
-    private lateinit var modelHelper: LauncherModelHelper
-    private lateinit var folderIcon: FolderIcon
-
-    private var defaultThemedIcons = false
-
-    @Before
-    fun setup() {
-        getInstrumentation().runOnMainSync {
-            folderIcon =
-                FolderIcon(ActivityContextWrapper(ApplicationProvider.getApplicationContext()))
-        }
-        context = getInstrumentation().targetContext
-        previewItemManager = PreviewItemManager(folderIcon)
-        modelHelper = LauncherModelHelper()
-        modelHelper
-            .setupDefaultLayoutProvider(
-                LauncherLayoutBuilder()
-                    .atWorkspace(0, 0, 1)
-                    .putFolder(R.string.copy)
-                    .addApp(LauncherModelHelper.TEST_PACKAGE, LauncherModelHelper.TEST_ACTIVITY)
-                    .addApp(LauncherModelHelper.TEST_PACKAGE, LauncherModelHelper.TEST_ACTIVITY2)
-                    .addApp(LauncherModelHelper.TEST_PACKAGE, LauncherModelHelper.TEST_ACTIVITY3)
-                    .addApp(LauncherModelHelper.TEST_PACKAGE, LauncherModelHelper.TEST_ACTIVITY4)
-                    .build()
-            )
-            .loadModelSync()
-        folderItems = modelHelper.bgDataModel.collections.valueAt(0).getContents()
-        folderIcon.mInfo = modelHelper.bgDataModel.collections.valueAt(0) as FolderInfo
-        folderIcon.mInfo.getContents().addAll(folderItems)
-
-        // Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps
-        val folderApps = modelHelper.bgDataModel.collections.valueAt(0).getAppContents()
-        // Set first icon to be themed.
-        folderApps[0].bitmap.themedBitmap =
-            MonoThemedBitmap(
-                folderApps[0].bitmap.icon,
-                Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
-            )
-
-        // Set second icon to be non-themed.
-        folderApps[1].bitmap.themedBitmap = null
-
-        // Set third icon to be themed with badge.
-        folderApps[2].bitmap.themedBitmap =
-            MonoThemedBitmap(
-                folderApps[2].bitmap.icon,
-                Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888),
-            )
-        folderApps[2].bitmap = folderApps[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
-
-        // Set fourth icon to be non-themed with badge.
-        folderApps[3].bitmap = folderApps[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
-        folderApps[3].bitmap.themedBitmap = null
-
-        defaultThemedIcons = get(context).get(THEMED_ICONS)
-    }
-
-    @After
-    @Throws(Exception::class)
-    fun tearDown() {
-        get(context).put(THEMED_ICONS, defaultThemedIcons)
-        modelHelper.destroy()
-    }
-
-    @Test
-    fun checkThemedIconWithThemingOn_iconShouldBeThemed() {
-        get(context).put(THEMED_ICONS, true)
-        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
-
-        previewItemManager.setDrawable(drawingParams, folderItems[0])
-
-        assert((drawingParams.drawable as FastBitmapDrawable).isThemed)
-    }
-
-    @Test
-    fun checkThemedIconWithThemingOff_iconShouldNotBeThemed() {
-        get(context).put(THEMED_ICONS, false)
-        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
-
-        previewItemManager.setDrawable(drawingParams, folderItems[0])
-
-        assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed)
-    }
-
-    @Test
-    fun checkUnthemedIconWithThemingOn_iconShouldNotBeThemed() {
-        get(context).put(THEMED_ICONS, true)
-        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
-
-        previewItemManager.setDrawable(drawingParams, folderItems[1])
-
-        assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed)
-    }
-
-    @Test
-    fun checkUnthemedIconWithThemingOff_iconShouldNotBeThemed() {
-        get(context).put(THEMED_ICONS, false)
-        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
-
-        previewItemManager.setDrawable(drawingParams, folderItems[1])
-
-        assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed)
-    }
-
-    @Test
-    fun checkThemedIconWithBadgeWithThemingOn_iconAndBadgeShouldBeThemed() {
-        get(context).put(THEMED_ICONS, true)
-        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
-
-        previewItemManager.setDrawable(drawingParams, folderItems[2])
-
-        assert((drawingParams.drawable as FastBitmapDrawable).isThemed)
-        assert(
-            ((drawingParams.drawable as FastBitmapDrawable).badge as UserBadgeDrawable).mIsThemed
-        )
-    }
-
-    @Test
-    fun checkUnthemedIconWithBadgeWithThemingOn_badgeShouldBeThemed() {
-        get(context).put(THEMED_ICONS, true)
-        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
-
-        previewItemManager.setDrawable(drawingParams, folderItems[3])
-
-        assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed)
-        assert(
-            ((drawingParams.drawable as FastBitmapDrawable).badge as UserBadgeDrawable).mIsThemed
-        )
-    }
-
-    @Test
-    fun checkUnthemedIconWithBadgeWithThemingOff_iconAndBadgeShouldNotBeThemed() {
-        get(context).put(THEMED_ICONS, false)
-        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
-
-        previewItemManager.setDrawable(drawingParams, folderItems[3])
-
-        assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed)
-        assert(
-            !((drawingParams.drawable as FastBitmapDrawable).badge as UserBadgeDrawable).mIsThemed
-        )
-    }
-
-    @Test
-    fun `Inactive archived app previews are not drawn as preload icon`() {
-        // Given
-        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
-        val archivedApp =
-            WorkspaceItemInfo().apply {
-                runtimeStatusFlags = runtimeStatusFlags or FLAG_ARCHIVED
-                runtimeStatusFlags = runtimeStatusFlags and FLAG_INSTALL_SESSION_ACTIVE.inv()
-            }
-        // When
-        previewItemManager.setDrawable(drawingParams, archivedApp)
-        // Then
-        assertThat(drawingParams.drawable).isNotInstanceOf(PreloadIconDrawable::class.java)
-    }
-
-    @Test
-    fun `Actively installing archived app previews are drawn as preload icon`() {
-        // Given
-        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
-        val archivedApp =
-            WorkspaceItemInfo().apply {
-                runtimeStatusFlags = runtimeStatusFlags or FLAG_ARCHIVED
-                runtimeStatusFlags = runtimeStatusFlags or FLAG_INSTALL_SESSION_ACTIVE
-            }
-        // When
-        TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) {
-            // Run on main thread because preload drawable triggers animator
-            previewItemManager.setDrawable(drawingParams, archivedApp)
-        }
-        // Then
-        assertThat(drawingParams.drawable).isInstanceOf(PreloadIconDrawable::class.java)
-    }
-
-    private fun profileFlagOp(type: Int) =
-        UserIconInfo(Process.myUserHandle(), type).applyBitmapInfoFlags(FlagOp.NO_OP)
-}
diff --git a/tests/multivalentTests/src/com/android/launcher3/graphics/ShapeDelegateTest.kt b/tests/multivalentTests/src/com/android/launcher3/graphics/ShapeDelegateTest.kt
new file mode 100644
index 0000000..7e38f0e
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/graphics/ShapeDelegateTest.kt
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.graphics
+
+import android.graphics.Matrix
+import android.graphics.Matrix.ScaleToFit.FILL
+import android.graphics.Path
+import android.graphics.Path.Direction
+import android.graphics.Rect
+import android.graphics.RectF
+import android.graphics.Region
+import android.platform.uiautomatorhelpers.DeviceHelpers.context
+import android.view.View
+import androidx.core.graphics.PathParser
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.graphics.ShapeDelegate.Circle
+import com.android.launcher3.graphics.ShapeDelegate.Companion.AREA_CALC_SIZE
+import com.android.launcher3.graphics.ShapeDelegate.Companion.AREA_DIFF_THRESHOLD
+import com.android.launcher3.graphics.ShapeDelegate.Companion.areaDiffCalculator
+import com.android.launcher3.graphics.ShapeDelegate.Companion.pickBestShape
+import com.android.launcher3.graphics.ShapeDelegate.GenericPathShape
+import com.android.launcher3.graphics.ShapeDelegate.RoundedSquare
+import com.android.launcher3.icons.GraphicsUtils
+import com.android.launcher3.views.ClipPathView
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShapeDelegateTest {
+
+    @Test
+    fun `areaDiffCalculator increases with outwards shape`() {
+        val diffCalculator =
+            areaDiffCalculator(
+                Path().apply {
+                    addCircle(
+                        AREA_CALC_SIZE / 2f,
+                        AREA_CALC_SIZE / 2f,
+                        AREA_CALC_SIZE / 2f,
+                        Direction.CW,
+                    )
+                }
+            )
+        assertThat(diffCalculator(Circle())).isLessThan(AREA_DIFF_THRESHOLD)
+        assertThat(diffCalculator(Circle())).isLessThan(diffCalculator(RoundedSquare(.9f)))
+        assertThat(diffCalculator(RoundedSquare(.9f)))
+            .isLessThan(diffCalculator(RoundedSquare(.8f)))
+        assertThat(diffCalculator(RoundedSquare(.8f)))
+            .isLessThan(diffCalculator(RoundedSquare(.7f)))
+        assertThat(diffCalculator(RoundedSquare(.7f)))
+            .isLessThan(diffCalculator(RoundedSquare(.6f)))
+        assertThat(diffCalculator(RoundedSquare(.6f)))
+            .isLessThan(diffCalculator(RoundedSquare(.5f)))
+    }
+
+    @Test
+    fun `areaDiffCalculator increases with inwards shape`() {
+        val diffCalculator = areaDiffCalculator(roundedRectPath(0.5f))
+        assertThat(diffCalculator(RoundedSquare(.5f))).isLessThan(AREA_DIFF_THRESHOLD)
+        assertThat(diffCalculator(RoundedSquare(.5f)))
+            .isLessThan(diffCalculator(RoundedSquare(.6f)))
+        assertThat(diffCalculator(RoundedSquare(.5f)))
+            .isLessThan(diffCalculator(RoundedSquare(.4f)))
+    }
+
+    @Test
+    fun `pickBestShape picks circle`() {
+        val r = AREA_CALC_SIZE / 2
+        val pathStr = "M 50 0 a 50 50 0 0 1 0 100 a 50 50 0 0 1 0 -100"
+        val path = Path().apply { addCircle(r.toFloat(), r.toFloat(), r.toFloat(), Direction.CW) }
+        assertThat(pickBestShape(path, pathStr)).isInstanceOf(Circle::class.java)
+    }
+
+    @Test
+    fun `pickBestShape picks rounded rect`() {
+        val factor = 0.5f
+        var shape = pickBestShape(roundedRectPath(factor), roundedRectString(factor))
+        assertThat(shape).isInstanceOf(RoundedSquare::class.java)
+        assertThat((shape as RoundedSquare).radiusRatio).isEqualTo(factor)
+
+        val factor2 = 0.2f
+        shape = pickBestShape(roundedRectPath(factor2), roundedRectString(factor2))
+        assertThat(shape).isInstanceOf(RoundedSquare::class.java)
+        assertThat((shape as RoundedSquare).radiusRatio).isEqualTo(factor2)
+    }
+
+    @Test
+    fun `pickBestShape picks generic shape`() {
+        val path = cookiePath(Rect(0, 0, AREA_CALC_SIZE, AREA_CALC_SIZE))
+        val pathStr = FOUR_SIDED_COOKIE
+        val shape = pickBestShape(path, pathStr)
+        assertThat(shape).isInstanceOf(GenericPathShape::class.java)
+
+        val diffCalculator = areaDiffCalculator(path)
+        assertThat(diffCalculator(shape)).isLessThan(AREA_DIFF_THRESHOLD)
+    }
+
+    @Test
+    fun `generic shape creates smooth animation`() {
+        val shape = GenericPathShape(FOUR_SIDED_COOKIE)
+        val target = TestClipView()
+        val anim =
+            shape.createRevealAnimator(
+                target,
+                Rect(0, 0, AREA_CALC_SIZE, AREA_CALC_SIZE),
+                Rect(0, 0, AREA_CALC_SIZE, AREA_CALC_SIZE),
+                AREA_CALC_SIZE * .25f,
+                false,
+            )
+
+        // Verify that the start rect is similar to initial path
+        anim.setCurrentFraction(0f)
+        assertThat(
+                getAreaDiff(
+                    target.currentClip!!,
+                    cookiePath(Rect(0, 0, AREA_CALC_SIZE, AREA_CALC_SIZE)),
+                )
+            )
+            .isLessThan(AREA_CALC_SIZE)
+
+        // Verify that end rect is similar to end path
+        anim.setCurrentFraction(1f)
+        assertThat(getAreaDiff(target.currentClip!!, roundedRectPath(0.5f)))
+            .isLessThan(AREA_CALC_SIZE)
+
+        // Ensure that when running animation, area increases smoothly. We run the animation over
+        // [steps] and verify increase of max 5 times the linear diff increase
+        val steps = 1000
+        val incrementalDiff =
+            getAreaDiff(
+                cookiePath(Rect(0, 0, AREA_CALC_SIZE, AREA_CALC_SIZE)),
+                roundedRectPath(0.5f),
+            ) * 5 / steps
+        var lastPath = cookiePath(Rect(0, 0, AREA_CALC_SIZE, AREA_CALC_SIZE))
+        for (progress in 1..steps) {
+            anim.setCurrentFraction(progress / 1000f)
+            val currentPath = Path(target.currentClip!!)
+            assertThat(getAreaDiff(lastPath, currentPath)).isLessThan(incrementalDiff)
+            lastPath = currentPath
+        }
+        assertThat(getAreaDiff(lastPath, roundedRectPath(0.5f))).isLessThan(AREA_CALC_SIZE)
+    }
+
+    private fun roundedRectPath(factor: Float) =
+        Path().apply {
+            val r = factor * AREA_CALC_SIZE / 2
+            addRoundRect(
+                0f,
+                0f,
+                AREA_CALC_SIZE.toFloat(),
+                AREA_CALC_SIZE.toFloat(),
+                r,
+                r,
+                Direction.CW,
+            )
+        }
+
+    private fun roundedRectString(factor: Float): String {
+        val s = 100f
+        val r = (factor * s / 2)
+        val t = s - r
+        return "M $r 0 " +
+            "L $t 0 " +
+            "A $r $r 0 0 1 $s $r " +
+            "L $s $t " +
+            "A $r $r 0 0 1 $t $s " +
+            "L $r $s " +
+            "A $r $r 0 0 1 0 $t " +
+            "L 0 $r " +
+            "A $r $r 0 0 1 $r 0 Z"
+    }
+
+    private fun getAreaDiff(p1: Path, p2: Path): Int {
+        val fullRegion = Region(0, 0, AREA_CALC_SIZE, AREA_CALC_SIZE)
+        val iconRegion = Region().apply { setPath(p1, fullRegion) }
+        val shapeRegion = Region().apply { setPath(p2, fullRegion) }
+        shapeRegion.op(iconRegion, Region.Op.XOR)
+        return GraphicsUtils.getArea(shapeRegion)
+    }
+
+    class TestClipView : View(context), ClipPathView {
+
+        var currentClip: Path? = null
+
+        override fun setClipPath(clipPath: Path?) {
+            currentClip = clipPath
+        }
+    }
+
+    companion object {
+        const val FOUR_SIDED_COOKIE =
+            "M63.605 3C84.733 -6.176 106.176 15.268 97 36.395L95.483 39.888C92.681 46.338 92.681 53.662 95.483 60.112L97 63.605C106.176 84.732 84.733 106.176 63.605 97L60.112 95.483C53.662 92.681 46.338 92.681 39.888 95.483L36.395 97C15.267 106.176 -6.176 84.732 3 63.605L4.517 60.112C7.319 53.662 7.319 46.338 4.517 39.888L3 36.395C -6.176 15.268 15.267 -6.176 36.395 3L39.888 4.517C46.338 7.319 53.662 7.319 60.112 4.517L63.605 3Z"
+
+        private fun cookiePath(bounds: Rect) =
+            PathParser.createPathFromPathData(FOUR_SIDED_COOKIE).apply {
+                transform(
+                    Matrix().apply { setRectToRect(RectF(0f, 0f, 100f, 100f), RectF(bounds), FILL) }
+                )
+            }
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/graphics/ThemeManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/graphics/ThemeManagerTest.kt
new file mode 100644
index 0000000..85c1156
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/graphics/ThemeManagerTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.graphics
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.FakeLauncherPrefs
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.icons.mono.MonoIconThemeController
+import com.android.launcher3.util.AllModulesForTest
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.FakePrefsModule
+import com.android.launcher3.util.SandboxApplication
+import com.android.launcher3.util.TestUtil
+import com.google.common.truth.Truth.assertThat
+import dagger.Component
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ThemeManagerTest {
+
+    @get:Rule val context = SandboxApplication()
+
+    lateinit var themeManager: ThemeManager
+
+    @Before
+    fun setUp() {
+        context.initDaggerComponent(DaggerThemeManagerComponent.builder())
+        themeManager = ThemeManager.INSTANCE[context]
+    }
+
+    @Test
+    fun `isMonoThemeEnabled get and set`() {
+        themeManager.isMonoThemeEnabled = true
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+        assertTrue(themeManager.isMonoThemeEnabled)
+        assertThat(themeManager.iconState.themeController)
+            .isInstanceOf(MonoIconThemeController::class.java)
+
+        themeManager.isMonoThemeEnabled = false
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+        assertFalse(themeManager.isMonoThemeEnabled)
+        assertThat(themeManager.iconState.themeController).isNull()
+    }
+
+    @Test
+    fun `callback called on theme change`() {
+        themeManager.isMonoThemeEnabled = false
+
+        var callbackCalled = false
+        themeManager.addChangeListener { callbackCalled = true }
+        themeManager.isMonoThemeEnabled = true
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+
+        assertTrue(callbackCalled)
+    }
+
+    @Test
+    fun `iconState changes with theme`() {
+        themeManager.isMonoThemeEnabled = false
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+        val disabledIconState = themeManager.iconState
+
+        themeManager.isMonoThemeEnabled = true
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+        assertNotEquals(disabledIconState, themeManager.iconState)
+
+        themeManager.isMonoThemeEnabled = false
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {}
+        assertEquals(disabledIconState, themeManager.iconState)
+    }
+}
+
+@LauncherAppSingleton
+@Component(modules = [AllModulesForTest::class, FakePrefsModule::class])
+interface ThemeManagerComponent : LauncherAppComponent {
+
+    override fun getLauncherPrefs(): FakeLauncherPrefs
+
+    @Component.Builder
+    interface Builder : LauncherAppComponent.Builder {
+
+        override fun build(): ThemeManagerComponent
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
index 9b4bd71..0aaf4d7 100644
--- a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheTest.java
@@ -21,6 +21,7 @@
 
 import static com.android.launcher3.icons.IconCache.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE;
 import static com.android.launcher3.icons.IconCacheUpdateHandlerTestKt.waitForUpdateHandlerToFinish;
+import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
 import static com.android.launcher3.model.data.AppInfo.makeLaunchIntent;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
@@ -55,7 +56,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.icons.cache.CachingLogic;
 import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
 import com.android.launcher3.icons.cache.LauncherActivityCachingLogic;
@@ -68,11 +69,13 @@
 import com.android.launcher3.util.ApplicationInfoWrapper;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.SandboxApplication;
 
 import com.google.common.truth.Truth;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -84,20 +87,18 @@
 @RunWith(AndroidJUnit4.class)
 public class IconCacheTest {
 
-    private Context mContext;
+    @Rule public SandboxApplication mContext = new SandboxApplication();
+
     private IconCache mIconCache;
 
     private ComponentName mMyComponent;
 
     @Before
     public void setup() {
-        mContext = getInstrumentation().getTargetContext();
         mMyComponent = new ComponentName(mContext, SettingsActivity.class);
 
         // In memory icon cache
-        mIconCache = new IconCache(mContext,
-                InvariantDeviceProfile.INSTANCE.get(mContext), null,
-                new LauncherIconProvider(mContext));
+        mIconCache = LauncherAppState.getInstance(mContext).getIconCache();
     }
 
     @After
@@ -164,7 +165,7 @@
         WorkspaceItemInfo info = new WorkspaceItemInfo();
         info.intent = makeLaunchIntent(cn);
         runOnExecutorSync(MODEL_EXECUTOR,
-                () -> mIconCache.getTitleAndIcon(info, lai, false));
+                () -> mIconCache.getTitleAndIcon(info, lai, DEFAULT_LOOKUP_FLAG));
         assertNotNull(info.bitmap);
         assertFalse(info.bitmap.isLowRes());
 
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheUpdateHandlerTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheUpdateHandlerTest.kt
index bae74c8..9dbed74 100644
--- a/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheUpdateHandlerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/IconCacheUpdateHandlerTest.kt
@@ -62,7 +62,11 @@
 
     private var cursor =
         MatrixCursor(
-            arrayOf(IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT, IconDB.COLUMN_FRESHNESS_ID)
+            arrayOf(
+                BaseIconCache.COLUMN_ROWID,
+                BaseIconCache.COLUMN_COMPONENT,
+                BaseIconCache.COLUMN_FRESHNESS_ID,
+            )
         )
 
     private lateinit var updateHandlerUnderTest: IconCacheUpdateHandler
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/UserBadgeDrawableTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/UserBadgeDrawableTest.kt
index d611ae8..91ba628 100644
--- a/tests/multivalentTests/src/com/android/launcher3/icons/UserBadgeDrawableTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/UserBadgeDrawableTest.kt
@@ -34,19 +34,20 @@
     private val context = InstrumentationRegistry.getInstrumentation().targetContext
     private val canvas = mock<Canvas>()
     private val systemUnderTest =
-        UserBadgeDrawable(context, R.drawable.ic_work_app_badge, R.color.badge_tint_work, false)
+        UserBadgeDrawable(
+            context,
+            R.drawable.ic_work_app_badge,
+            R.color.badge_tint_work,
+            false /* isThemed */,
+            null, /* shape */
+        )
 
     @Test
     fun draw_opaque() {
         val colorList = mutableListOf<Int>()
-        whenever(
-            canvas.drawCircle(
-                any(),
-                any(),
-                any(),
-                any()
-            )
-        ).then { colorList.add(it.getArgument<Paint>(3).color) }
+        whenever(canvas.drawCircle(any(), any(), any(), any())).then {
+            colorList.add(it.getArgument<Paint>(3).color)
+        }
 
         systemUnderTest.alpha = 255
         systemUnderTest.draw(canvas)
@@ -57,14 +58,9 @@
     @Test
     fun draw_transparent() {
         val colorList = mutableListOf<Int>()
-        whenever(
-            canvas.drawCircle(
-                any(),
-                any(),
-                any(),
-                any()
-            )
-        ).then { colorList.add(it.getArgument<Paint>(3).color) }
+        whenever(canvas.drawCircle(any(), any(), any(), any())).then {
+            colorList.add(it.getArgument<Paint>(3).color)
+        }
 
         systemUnderTest.alpha = 0
         systemUnderTest.draw(canvas)
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/cache/CacheLookupFlagTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/cache/CacheLookupFlagTest.kt
new file mode 100644
index 0000000..8218181
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/cache/CacheLookupFlagTest.kt
@@ -0,0 +1,102 @@
+/*
+ * 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.icons.cache
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.icons.cache.CacheLookupFlag.Companion.DEFAULT_LOOKUP_FLAG
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CacheLookupFlagTest {
+
+    @Test
+    fun `useLowRes preserves lowRes values`() {
+        assertFalse(DEFAULT_LOOKUP_FLAG.useLowRes())
+        assertTrue(DEFAULT_LOOKUP_FLAG.withUseLowRes().useLowRes())
+        assertFalse(DEFAULT_LOOKUP_FLAG.withUseLowRes().withUseLowRes(false).useLowRes())
+        assertTrue(
+            DEFAULT_LOOKUP_FLAG.withUseLowRes().withUseLowRes(false).withUseLowRes().useLowRes()
+        )
+    }
+
+    @Test
+    fun `usePackageIcon preserves lowRes values`() {
+        assertFalse(DEFAULT_LOOKUP_FLAG.usePackageIcon())
+        assertTrue(DEFAULT_LOOKUP_FLAG.withUsePackageIcon().usePackageIcon())
+        assertFalse(
+            DEFAULT_LOOKUP_FLAG.withUsePackageIcon().withUsePackageIcon(false).usePackageIcon()
+        )
+        assertTrue(
+            DEFAULT_LOOKUP_FLAG.withUsePackageIcon()
+                .withUsePackageIcon(false)
+                .withUsePackageIcon()
+                .usePackageIcon()
+        )
+    }
+
+    @Test
+    fun `skipAddToMemCache preserves lowRes values`() {
+        assertFalse(DEFAULT_LOOKUP_FLAG.skipAddToMemCache())
+        assertTrue(DEFAULT_LOOKUP_FLAG.withSkipAddToMemCache().skipAddToMemCache())
+        assertFalse(
+            DEFAULT_LOOKUP_FLAG.withSkipAddToMemCache()
+                .withSkipAddToMemCache(false)
+                .skipAddToMemCache()
+        )
+        assertTrue(
+            DEFAULT_LOOKUP_FLAG.withSkipAddToMemCache()
+                .withSkipAddToMemCache(false)
+                .withSkipAddToMemCache()
+                .skipAddToMemCache()
+        )
+    }
+
+    @Test
+    fun `preserves multiple flags`() {
+        val flag = DEFAULT_LOOKUP_FLAG.withSkipAddToMemCache().withUseLowRes()
+
+        assertTrue(flag.skipAddToMemCache())
+        assertTrue(flag.useLowRes())
+        assertFalse(flag.usePackageIcon())
+    }
+
+    @Test
+    fun `isVisuallyLessThan does not depend on package icon`() {
+        assertFalse(DEFAULT_LOOKUP_FLAG.isVisuallyLessThan(DEFAULT_LOOKUP_FLAG))
+        assertFalse(
+            DEFAULT_LOOKUP_FLAG.withUsePackageIcon().isVisuallyLessThan(DEFAULT_LOOKUP_FLAG)
+        )
+        assertFalse(
+            DEFAULT_LOOKUP_FLAG.isVisuallyLessThan(DEFAULT_LOOKUP_FLAG.withUsePackageIcon())
+        )
+    }
+
+    @Test
+    fun `isVisuallyLessThan depends on low res`() {
+        assertTrue(DEFAULT_LOOKUP_FLAG.withUseLowRes().isVisuallyLessThan(DEFAULT_LOOKUP_FLAG))
+        assertFalse(DEFAULT_LOOKUP_FLAG.isVisuallyLessThan(DEFAULT_LOOKUP_FLAG.withUseLowRes()))
+        assertTrue(
+            DEFAULT_LOOKUP_FLAG.withUseLowRes()
+                .isVisuallyLessThan(DEFAULT_LOOKUP_FLAG.withUsePackageIcon())
+        )
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
index 4af564e..2c9cb2f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt
@@ -16,9 +16,11 @@
 
 package com.android.launcher3.icons.mono
 
+import android.content.ComponentName
 import android.graphics.Color
 import android.graphics.drawable.AdaptiveIconDrawable
 import android.graphics.drawable.ColorDrawable
+import android.os.Process
 import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
@@ -29,10 +31,14 @@
 import com.android.launcher3.Flags
 import com.android.launcher3.icons.BaseIconFactory
 import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.SourceHint
+import com.android.launcher3.icons.cache.LauncherActivityCachingLogic
+import com.android.launcher3.util.ComponentKey
 import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertNull
 import org.junit.Assume.assumeFalse
+import org.junit.Assume.assumeTrue
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -45,6 +51,12 @@
 
     private val iconFactory = BaseIconFactory(context, DisplayMetrics.DENSITY_MEDIUM, 30)
 
+    private val sourceHint =
+        SourceHint(
+            key = ComponentKey(ComponentName("a", "a"), Process.myUserHandle()),
+            logic = LauncherActivityCachingLogic,
+        )
+
     @Test
     fun `createThemedBitmap when mono drawable is present`() {
         val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
@@ -66,6 +78,8 @@
     @EnableFlags(Flags.FLAG_FORCE_MONOCHROME_APP_ICONS)
     fun `createThemedBitmap when mono generation is enabled`() {
         ensureBitmapSerializationSupported()
+        // Make sure forced theme icon is enabled in BaseIconFactory
+        assumeTrue(iconFactory.shouldForceThemeIcon())
         val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null)
         assertNotNull(
             MonoIconThemeController().createThemedBitmap(icon, BitmapInfo.LOW_RES_INFO, iconFactory)
@@ -81,7 +95,8 @@
         val themeBitmap =
             MonoIconThemeController().createThemedBitmap(icon, iconInfo, iconFactory)!!
         assertNotNull(
-            MonoIconThemeController().decode(themeBitmap.serialize(), iconInfo, iconFactory)
+            MonoIconThemeController()
+                .decode(themeBitmap.serialize(), iconInfo, iconFactory, sourceHint)
         )
     }
 
@@ -90,7 +105,10 @@
         ensureBitmapSerializationSupported()
         val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED))
         val iconInfo = iconFactory.createBadgedIconBitmap(icon)
-        assertNull(MonoIconThemeController().decode(byteArrayOf(1, 1, 1, 1), iconInfo, iconFactory))
+        assertNull(
+            MonoIconThemeController()
+                .decode(byteArrayOf(1, 1, 1, 1), iconInfo, iconFactory, sourceHint)
+        )
     }
 
     @Test
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
index ce04682..08b8f81 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/AddWorkspaceItemsTaskTest.kt
@@ -155,7 +155,7 @@
     private fun verifyItemSpaceFinderCall(nonEmptyScreenIds: List<Int>, numberOfExpectedCall: Int) {
         verify(mWorkspaceItemSpaceFinder, times(numberOfExpectedCall))
             .findSpaceForItem(
-                same(mAppState),
+                eq(mAppState),
                 same(mModelHelper.bgDataModel),
                 eq(IntArray.wrap(*nonEmptyScreenIds.toIntArray())),
                 eq(IntArray()),
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
index 1e2431f..0ae4d00 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/DefaultLayoutProviderTest.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
 import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
 import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
 
@@ -42,6 +44,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+
 /**
  * Tests for layout parser for remote layout
  */
@@ -63,14 +67,23 @@
         mModelHelper.destroy();
     }
 
+    private List<ItemInfo> getWorkspaceItems() {
+        return mModelHelper
+                .getBgDataModel()
+                .itemsIdMap
+                .stream()
+                .filter(i -> i.container == CONTAINER_DESKTOP || i.container == CONTAINER_HOTSEAT)
+                .toList();
+    }
+
     @Test
     public void testCustomProfileLoaded_with_icon_on_hotseat() throws Exception {
         writeLayoutAndLoad(new LauncherLayoutBuilder().atHotseat(0)
                 .putApp(TEST_PACKAGE, TEST_ACTIVITY));
 
         // Verify one item in hotseat
-        assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
-        ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+        assertEquals(1, getWorkspaceItems().size());
+        ItemInfo info = getWorkspaceItems().get(0);
         assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container);
         assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPLICATION, info.itemType);
     }
@@ -84,8 +97,8 @@
                 .build());
 
         // Verify folder
-        assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
-        ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+        assertEquals(1, getWorkspaceItems().size());
+        ItemInfo info = getWorkspaceItems().get(0);
         assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
         assertEquals(3, ((FolderInfo) info).getContents().size());
     }
@@ -99,8 +112,8 @@
                 .build());
 
         // Verify folder
-        assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
-        ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+        assertEquals(1, getWorkspaceItems().size());
+        ItemInfo info = getWorkspaceItems().get(0);
         assertEquals(LauncherSettings.Favorites.ITEM_TYPE_FOLDER, info.itemType);
         assertEquals(3, ((FolderInfo) info).getContents().size());
         assertEquals("CustomFolder", info.title.toString());
@@ -124,8 +137,8 @@
                 .putWidget(pendingAppPkg, "PlaceholderWidget", 2, 2));
 
         // Verify widget
-        assertEquals(1, mModelHelper.getBgDataModel().appWidgets.size());
-        ItemInfo info = mModelHelper.getBgDataModel().appWidgets.get(0);
+        assertEquals(1, getWorkspaceItems().size());
+        ItemInfo info = getWorkspaceItems().get(0);
         assertEquals(LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET, info.itemType);
         assertEquals(2, info.spanX);
         assertEquals(2, info.spanY);
@@ -138,8 +151,8 @@
                 .putShortcut(TEST_PACKAGE, "shortcut2"));
 
         // Verify one item in hotseat
-        assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
-        ItemInfo info = mModelHelper.getBgDataModel().workspaceItems.get(0);
+        assertEquals(1, getWorkspaceItems().size());
+        ItemInfo info = getWorkspaceItems().get(0);
         assertEquals(LauncherSettings.Favorites.CONTAINER_HOTSEAT, info.container);
         assertEquals(LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT, info.itemType);
     }
@@ -154,8 +167,8 @@
                 .build());
 
         // Verify folder
-        assertEquals(1, mModelHelper.getBgDataModel().workspaceItems.size());
-        FolderInfo info = (FolderInfo) mModelHelper.getBgDataModel().workspaceItems.get(0);
+        assertEquals(1, getWorkspaceItems().size());
+        FolderInfo info = (FolderInfo) getWorkspaceItems().get(0);
         assertEquals(3, info.getContents().size());
 
         // Verify last icon
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
index e8f778f..f357487 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/FolderIconLoadTest.kt
@@ -18,8 +18,10 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
 import com.android.launcher3.icons.BitmapInfo
 import com.android.launcher3.icons.waitForUpdateHandlerToFinish
+import com.android.launcher3.model.data.FolderInfo
 import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.util.Executors
 import com.android.launcher3.util.LauncherLayoutBuilder
@@ -149,11 +151,13 @@
         // Reload again with correct icon state
         app.model.forceReload()
         modelHelper.loadModelSync()
-        val collections = modelHelper.getBgDataModel().collections
-
-        assertThat(collections.size()).isEqualTo(1)
-        assertThat(collections.valueAt(0).getAppContents().size).isEqualTo(itemCount)
-        return collections.valueAt(0).getAppContents()
+        val collections =
+            modelHelper.bgDataModel.itemsIdMap
+                .filter { it.itemType == ITEM_TYPE_FOLDER }
+                .map { it as FolderInfo }
+        assertThat(collections.size).isEqualTo(1)
+        assertThat(collections[0].getAppContents().size).isEqualTo(itemCount)
+        return collections[0].getAppContents()
     }
 
     private fun verifyHighRes(items: ArrayList<WorkspaceItemInfo>, vararg indices: Int) {
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationTest.kt
index eee6191..5607bb4 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/GridSizeMigrationTest.kt
@@ -134,6 +134,7 @@
             var gridSizeMigrationLogic = GridSizeMigrationLogic()
             val idsInUse = mutableListOf<Int>()
             gridSizeMigrationLogic.migrateHotseat(
+                5,
                 idp.numDatabaseHotseatIcons,
                 srcReader,
                 destReader,
@@ -152,6 +153,7 @@
                 dbHelper,
                 srcReader,
                 destReader,
+                5,
                 idp.numDatabaseHotseatIcons,
                 Point(idp.numColumns, idp.numRows),
                 DeviceGridState(context),
@@ -273,34 +275,15 @@
         val readerGridA = DbReader(db, TMP_TABLE, context)
         val readerGridB = DbReader(db, TABLE_NAME, context)
         // migrate from A -> B
-        if (Flags.gridMigrationRefactor()) {
-            var gridSizeMigrationLogic = GridSizeMigrationLogic()
-            val idsInUse = mutableListOf<Int>()
-            gridSizeMigrationLogic.migrateHotseat(
-                idp.numDatabaseHotseatIcons,
-                readerGridA,
-                readerGridB,
-                dbHelper,
-                idsInUse,
-            )
-            gridSizeMigrationLogic.migrateWorkspace(
-                readerGridA,
-                readerGridB,
-                dbHelper,
-                Point(idp.numColumns, idp.numRows),
-                idsInUse,
-            )
-        } else {
-            GridSizeMigrationDBController.migrate(
-                dbHelper,
-                readerGridA,
-                readerGridB,
-                idp.numDatabaseHotseatIcons,
-                Point(idp.numColumns, idp.numRows),
-                DeviceGridState(context),
-                DeviceGridState(idp),
-            )
-        }
+        migrateGrid(
+            dbHelper,
+            readerGridA,
+            readerGridB,
+            5,
+            idp.numDatabaseHotseatIcons,
+            idp.numColumns,
+            idp.numRows,
+        )
 
         // Check hotseat items in grid B
         var c =
@@ -317,8 +300,8 @@
         // 2 1 3 4
         verifyHotseat(
             c,
-            idp,
             mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList(),
+            4,
         )
 
         // Check workspace items in grid B
@@ -348,7 +331,7 @@
         addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 0, 2, testPackage9)
 
         // migrate from B -> A
-        migrateGrid(dbHelper, readerGridB, readerGridA, 5, 5, 5)
+        migrateGrid(dbHelper, readerGridB, readerGridA, 4, 5, 5, 5)
 
         // Check hotseat items in grid A
         c =
@@ -362,11 +345,12 @@
                 null,
             ) ?: throw IllegalStateException()
         // Expected hotseat items in grid A
-        // 1 2 _ 3 4
+        // 1 2 4 3 4
         verifyHotseat(
             c,
-            idp,
-            mutableListOf(testPackage1, testPackage2, null, testPackage3, testPackage4).toList(),
+            mutableListOf(testPackage1, testPackage2, testPackage4, testPackage3, testPackage4)
+                .toList(),
+            5,
         )
 
         // Check workspace items in grid A
@@ -383,8 +367,8 @@
         locMap = parseLocMap(c)
         // Expected workspace items in grid A
         // _ _ _ _ _
-        // _ _ _ _ 5
-        // 9 _ 6 _ 7
+        // 9 _ _ _ 5
+        // _ _ 6 _ 7
         // _ _ 8 _ _
         // _ _ _ _ _
         assertThat(locMap.size.toLong()).isEqualTo(5)
@@ -394,7 +378,7 @@
         assertThat(locMap[testPackage7]).isEqualTo(Triple(0, 4, 2))
         assertThat(locMap[testPackage8]).isEqualTo(Triple(0, 2, 3))
         // Verify items that didn't exist in grid A are added in new screen
-        assertThat(locMap[testPackage9]).isEqualTo(Triple(0, 0, 2))
+        assertThat(locMap[testPackage9]).isEqualTo(Triple(0, 0, 1))
 
         // remove item from B
         db.delete(TMP_TABLE, "$_ID=7", null)
@@ -404,6 +388,7 @@
             dbHelper,
             readerGridA,
             readerGridB,
+            5,
             idp.numDatabaseHotseatIcons,
             idp.numColumns,
             idp.numRows,
@@ -424,8 +409,8 @@
         // 2 1 3 4
         verifyHotseat(
             c,
-            idp,
             mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList(),
+            4,
         )
 
         // Check workspace items in grid B
@@ -452,10 +437,234 @@
         assertThat(locMap[testPackage9]).isEqualTo(Triple(0, 0, 2))
     }
 
+    @Test
+    @Throws(Exception::class)
+    @EnableFlags(Flags.FLAG_GRID_MIGRATION_REFACTOR)
+    fun testHotseatMigrationToSmallerGridBackAndForthFlagOn() {
+        testHotseatMigrationToSmallerGridBackAndForth()
+    }
+
+    @Test
+    @Throws(Exception::class)
+    @DisableFlags(Flags.FLAG_GRID_MIGRATION_REFACTOR)
+    fun testHotseatMigrationToSmallerGridBackAndForthFlagOff() {
+        testHotseatMigrationToSmallerGridBackAndForth()
+    }
+
+    /** Old migration logic, should be modified once is not needed anymore */
+    @Throws(Exception::class)
+    fun testHotseatMigrationToSmallerGridBackAndForth() {
+        // Hotseat items in grid A
+        // 1 2 3 4 5
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
+        addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
+        addItem(ITEM_TYPE_DEEP_SHORTCUT, 2, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 3, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage5, 5, TMP_TABLE)
+
+        // Hotseat items in grid B
+        // 2 _ _ _
+        addItem(ITEM_TYPE_DEEP_SHORTCUT, 0, CONTAINER_HOTSEAT, 0, 0, testPackage2)
+
+        idp.numDatabaseHotseatIcons = 4
+        idp.numColumns = 4
+        idp.numRows = 4
+        val readerGridA = DbReader(db, TMP_TABLE, context)
+        val readerGridB = DbReader(db, TABLE_NAME, context)
+        // migrate from A -> B
+        migrateGrid(
+            dbHelper,
+            readerGridA,
+            readerGridB,
+            5,
+            idp.numDatabaseHotseatIcons,
+            idp.numColumns,
+            idp.numRows,
+        )
+        // Check hotseat items in grid B
+        var c =
+            db.query(
+                TABLE_NAME,
+                arrayOf(SCREEN, INTENT),
+                "container=$CONTAINER_HOTSEAT",
+                null,
+                SCREEN,
+                null,
+                null,
+            ) ?: throw IllegalStateException()
+        // Expected hotseat items in grid B
+        // 2 1 3 4
+        verifyHotseat(
+            c,
+            mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList(),
+            4,
+        )
+
+        // migrate from B -> A
+        migrateGrid(dbHelper, readerGridB, readerGridA, idp.numDatabaseHotseatIcons, 5, 5, 5)
+
+        // Check hotseat items in grid A
+        c =
+            db.query(
+                TMP_TABLE,
+                arrayOf(SCREEN, INTENT),
+                "container=$CONTAINER_HOTSEAT",
+                null,
+                SCREEN,
+                null,
+                null,
+            ) ?: throw IllegalStateException()
+        // Expected hotseat items in grid A
+        // 1 2 3 4 5
+        verifyHotseat(
+            c,
+            mutableListOf(testPackage1, testPackage2, testPackage3, testPackage4, testPackage5)
+                .toList(),
+            5,
+        )
+
+        // migrate from A -> B
+        migrateGrid(
+            dbHelper,
+            readerGridA,
+            readerGridB,
+            5,
+            idp.numDatabaseHotseatIcons,
+            idp.numColumns,
+            idp.numRows,
+        )
+
+        // Check hotseat items in grid B
+        c =
+            db.query(
+                TABLE_NAME,
+                arrayOf(SCREEN, INTENT),
+                "container=$CONTAINER_HOTSEAT",
+                null,
+                SCREEN,
+                null,
+                null,
+            ) ?: throw IllegalStateException()
+        // Expected hotseat items in grid B
+        // 2 1 3 4
+        verifyHotseat(
+            c,
+            mutableListOf(testPackage2, testPackage1, testPackage3, testPackage4).toList(),
+            4,
+        )
+    }
+
+    @Test
+    @Throws(Exception::class)
+    @EnableFlags(Flags.FLAG_GRID_MIGRATION_REFACTOR)
+    fun testMigrationToFullGridFlagOn() {
+        testMigrationToFullGrid()
+    }
+
+    @Test
+    @Throws(Exception::class)
+    @DisableFlags(Flags.FLAG_GRID_MIGRATION_REFACTOR)
+    fun testHotseatMigrationToFullGridFlagOff() {
+        testMigrationToFullGrid()
+    }
+
+    @Throws(Exception::class)
+    fun testMigrationToFullGrid() {
+        // Hotseat items in grid A
+        // 1 2 3 4 5
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_HOTSEAT, 0, 0, testPackage1, 1, TMP_TABLE)
+        addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage2, 2, TMP_TABLE)
+        addItem(ITEM_TYPE_DEEP_SHORTCUT, 2, CONTAINER_HOTSEAT, 0, 0, testPackage3, 3, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 3, CONTAINER_HOTSEAT, 0, 0, testPackage4, 4, TMP_TABLE)
+        addItem(ITEM_TYPE_APPLICATION, 4, CONTAINER_HOTSEAT, 0, 0, testPackage5, 5, TMP_TABLE)
+
+        // Hotseat items in grid B
+        // 6 7 8 9
+        addItem(ITEM_TYPE_DEEP_SHORTCUT, 0, CONTAINER_HOTSEAT, 0, 0, testPackage6)
+        addItem(ITEM_TYPE_DEEP_SHORTCUT, 1, CONTAINER_HOTSEAT, 0, 0, testPackage7)
+        addItem(ITEM_TYPE_APPLICATION, 2, CONTAINER_HOTSEAT, 0, 0, testPackage8)
+        addItem(ITEM_TYPE_APPLICATION, 3, CONTAINER_HOTSEAT, 0, 0, testPackage9)
+
+        // Workspace items in grid A
+        // _ _ _ _ _
+        // 6 _ _ _ _
+        // _ _ _ _ _
+        // _ _ _ _ _
+        // _ _ _ _ _
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 0, 1, testPackage6, 6, TMP_TABLE)
+
+        // Workspace items in grid B
+        // _ _ _ _
+        // 1 2 3 4
+        // _ _ _ _
+        // _ _ _ _
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 0, 1, testPackage1)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 1, 1, testPackage2)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 2, 1, testPackage3)
+        addItem(ITEM_TYPE_APPLICATION, 0, CONTAINER_DESKTOP, 3, 1, testPackage4)
+
+        idp.numDatabaseHotseatIcons = 4
+        idp.numColumns = 4
+        idp.numRows = 4
+        val readerGridA = DbReader(db, TMP_TABLE, context)
+        val readerGridB = DbReader(db, TABLE_NAME, context)
+
+        // migrate from A -> B
+        migrateGrid(
+            dbHelper,
+            readerGridA,
+            readerGridB,
+            5,
+            idp.numDatabaseHotseatIcons,
+            idp.numColumns,
+            idp.numRows,
+        )
+
+        // Check hotseat items in grid B
+        var c =
+            db.query(
+                TABLE_NAME,
+                arrayOf(SCREEN, INTENT),
+                "container=$CONTAINER_HOTSEAT",
+                null,
+                SCREEN,
+                null,
+                null,
+            ) ?: throw IllegalStateException()
+        // Expected hotseat items in grid B
+        // 1 2 3 4
+        verifyHotseat(
+            c,
+            mutableListOf(testPackage1, testPackage2, testPackage3, testPackage4).toList(),
+            4,
+        )
+
+        // Check workspace items in grid B
+        c =
+            db.query(
+                TABLE_NAME,
+                arrayOf(SCREEN, CELLX, CELLY, INTENT),
+                "container=$CONTAINER_DESKTOP",
+                null,
+                null,
+                null,
+                null,
+            ) ?: throw IllegalStateException()
+        val locMap = parseLocMap(c)
+        // Expected workspace items in grid B
+        // _ _ _ _
+        // 6 _ _ _
+        // _ _ _ _
+        // _ _ _ _
+        assertThat(locMap.size.toLong()).isEqualTo(1)
+        assertThat(locMap[testPackage6]).isEqualTo(Triple(0, 0, 1))
+    }
+
     private fun migrateGrid(
         dbHelper: DatabaseHelper,
         srcReader: DbReader,
         destReader: DbReader,
+        srcHotseatSize: Int,
         destHotseatSize: Int,
         pointX: Int,
         pointY: Int,
@@ -464,7 +673,8 @@
             var gridSizeMigrationLogic = GridSizeMigrationLogic()
             val idsInUse = mutableListOf<Int>()
             gridSizeMigrationLogic.migrateHotseat(
-                idp.numDatabaseHotseatIcons,
+                srcHotseatSize,
+                destHotseatSize,
                 srcReader,
                 destReader,
                 dbHelper,
@@ -474,7 +684,7 @@
                 srcReader,
                 destReader,
                 dbHelper,
-                Point(idp.numColumns, idp.numRows),
+                Point(pointX, pointY),
                 idsInUse,
             )
         } else {
@@ -482,6 +692,7 @@
                 dbHelper,
                 srcReader,
                 destReader,
+                srcHotseatSize,
                 destHotseatSize,
                 Point(pointX, pointY),
                 DeviceGridState(idp),
@@ -490,8 +701,8 @@
         }
     }
 
-    private fun verifyHotseat(c: Cursor, idp: InvariantDeviceProfile, expected: List<String?>) {
-        assertThat(c.count).isEqualTo(idp.numDatabaseHotseatIcons)
+    private fun verifyHotseat(c: Cursor, expected: List<String?>, expectedCount: Int) {
+        assertThat(c.count).isEqualTo(expectedCount)
         val screenIndex = c.getColumnIndex(SCREEN)
         val intentIndex = c.getColumnIndex(INTENT)
         expected.forEachIndexed { idx, pkg ->
@@ -584,6 +795,7 @@
             dbHelper,
             srcReader,
             destReader,
+            4,
             idp.numDatabaseHotseatIcons,
             idp.numColumns,
             idp.numRows,
@@ -651,6 +863,7 @@
             dbHelper,
             srcReader,
             destReader,
+            6,
             idp.numDatabaseHotseatIcons,
             idp.numColumns,
             idp.numRows,
@@ -729,6 +942,7 @@
             dbHelper,
             srcReader,
             destReader,
+            2,
             idp.numDatabaseHotseatIcons,
             idp.numColumns,
             idp.numRows,
@@ -801,6 +1015,7 @@
             dbHelper,
             srcReader,
             destReader,
+            5,
             idp.numDatabaseHotseatIcons,
             idp.numColumns,
             idp.numRows,
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
index 63359ec..ad40818 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/model/LoaderCursorTest.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3.model;
 
+import static android.graphics.BitmapFactory.decodeByteArray;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
+
 import static androidx.test.InstrumentationRegistry.getContext;
 
 import static com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_ID;
@@ -40,8 +43,11 @@
 import static com.android.launcher3.LauncherSettings.Favorites.SPANY;
 import static com.android.launcher3.LauncherSettings.Favorites.TITLE;
 import static com.android.launcher3.LauncherSettings.Favorites._ID;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
 import static com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
@@ -52,13 +58,20 @@
 import android.content.Context;
 import android.content.Intent;
 import android.database.MatrixCursor;
+import android.graphics.Bitmap;
 import android.os.Process;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.LauncherIcons;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.util.Executors;
@@ -67,6 +80,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -77,6 +91,9 @@
 @RunWith(AndroidJUnit4.class)
 public class LoaderCursorTest {
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+
     private LauncherModelHelper mModelHelper;
     private LauncherAppState mApp;
     private PackageManagerHelper mPmHelper;
@@ -87,6 +104,12 @@
 
     private LoaderCursor mLoaderCursor;
 
+    private static byte[] sTestBlob = new byte[] {
+            -119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, 0, 0, 0, 1,
+            8, 4, 0, 0, 0, -75, 28, 12, 2, 0, 0, 0, 11, 73, 68, 65, 84, 120, -38, 99, 100, 96, 0,
+            0, 0, 6, 0, 2, 48, -127, -48, 47, 0, 0, 0, 0, 73, 69, 78, 68, -82, 66, 96, -126
+    };
+
     @Before
     public void setup() {
         mModelHelper = new LauncherModelHelper();
@@ -119,7 +142,8 @@
                 .add(PROFILE_ID, 0)
                 .add(ITEM_TYPE, itemType)
                 .add(TITLE, title)
-                .add(CONTAINER, CONTAINER_DESKTOP);
+                .add(CONTAINER, CONTAINER_DESKTOP)
+                .add(ICON, sTestBlob);
     }
 
     @Test
@@ -161,7 +185,12 @@
 
     @Test
     public void loadSimpleShortcut() {
-        initCursor(ITEM_TYPE_DEEP_SHORTCUT, "my-shortcut");
+        mCursor.newRow()
+                .add(_ID, 1)
+                .add(PROFILE_ID, 0)
+                .add(ITEM_TYPE, ITEM_TYPE_DEEP_SHORTCUT)
+                .add(TITLE, "my-shortcut")
+                .add(CONTAINER, CONTAINER_DESKTOP);
         assertTrue(mLoaderCursor.moveToNext());
 
         WorkspaceItemInfo info = mLoaderCursor.loadSimpleWorkspaceItem();
@@ -223,6 +252,67 @@
                 newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3), true));
     }
 
+    @Test
+    @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+    public void ifArchivedWithFlag_whenloadWorkspaceTitleAndIcon_thenLoadIconFromDb() {
+        // Given
+        initCursor(ITEM_TYPE_APPLICATION, "title");
+        assertTrue(mLoaderCursor.moveToNext());
+        WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
+        itemInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
+        Bitmap expectedBitmap = LauncherIcons.obtain(mContext)
+                .createIconBitmap(decodeByteArray(sTestBlob, 0, sTestBlob.length))
+                .icon;
+        // When
+        mLoaderCursor.loadWorkspaceTitleAndIcon(false, true, itemInfo);
+        // Then
+        assertThat(itemInfo.bitmap.icon).isNotNull();
+        assertThat(itemInfo.bitmap.icon.sameAs(expectedBitmap)).isTrue();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+    public void ifArchivedWithFlag_whenLoadIconFromDb_thenLoadIconFromBlob() {
+        // Given
+        initCursor(ITEM_TYPE_APPLICATION, "title");
+        assertTrue(mLoaderCursor.moveToNext());
+        WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
+        itemInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
+        // Then
+        assertTrue(mLoaderCursor.loadIconFromDb(itemInfo));
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+    public void ifArchivedWithoutFlag_whenLoadWorkspaceTitleAndIcon_thenDoNotLoadFromDb() {
+        // Given
+        initCursor(ITEM_TYPE_APPLICATION, "title");
+        assertTrue(mLoaderCursor.moveToNext());
+        WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
+        BitmapInfo original = itemInfo.bitmap;
+        itemInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName("package", "class"));
+        itemInfo.intent = intent;
+        // When
+        mLoaderCursor.loadWorkspaceTitleAndIcon(false, false, itemInfo);
+        // Then
+        assertThat(itemInfo.bitmap).isEqualTo(original);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+    public void ifArchivedWithoutFlag_whenLoadIconFromDb_thenDoNotLoadFromBlob() {
+        // Given
+        initCursor(ITEM_TYPE_APPLICATION, "title");
+        assertTrue(mLoaderCursor.moveToNext());
+        WorkspaceItemInfo itemInfo = new WorkspaceItemInfo();
+        itemInfo.runtimeStatusFlags |= FLAG_ARCHIVED;
+        // Then
+        assertFalse(mLoaderCursor.loadIconFromDb(itemInfo));
+    }
+
+
     private ItemInfo newItemInfo(int cellX, int cellY, int spanX, int spanY,
             int container, int screenId) {
         ItemInfo info = new ItemInfo();
diff --git a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
similarity index 98%
rename from tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
index d9af07a..eec6eed 100644
--- a/tests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/PackageUpdatedTaskTest.kt
@@ -21,7 +21,7 @@
 import android.content.pm.ApplicationInfo
 import android.content.pm.LauncherActivityInfo
 import android.content.pm.LauncherApps
-import android.os.UserHandle
+import android.os.Process.myUserHandle
 import android.platform.test.annotations.EnableFlags
 import android.platform.test.flag.junit.SetFlagsRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -60,7 +60,7 @@
 
     @get:Rule val setFlagsRule = SetFlagsRule()
 
-    private val mUser = UserHandle(0)
+    private val mUser = myUserHandle()
     private val mDataModel: BgDataModel = BgDataModel()
     private val mLauncherModelHelper = LauncherModelHelper()
     private val mContext: SandboxModelContext = spy(mLauncherModelHelper.sandboxContext)
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt
new file mode 100644
index 0000000..c6863f4
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/model/ShortcutsChangedTaskTest.kt
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.model
+
+import android.content.ComponentName
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.ApplicationInfo.FLAG_INSTALLED
+import android.content.pm.LauncherApps
+import android.content.pm.ShortcutInfo
+import android.os.Process.myUserHandle
+import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.Flags
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.CacheableShortcutInfo
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.shortcuts.ShortcutKey
+import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.util.IntSparseArrayMap
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
+import com.google.common.truth.Truth.assertThat
+import java.util.function.Predicate
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShortcutsChangedTaskTest {
+    @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
+    private lateinit var shortcutsChangedTask: ShortcutsChangedTask
+    private lateinit var modelHelper: LauncherModelHelper
+    private lateinit var context: SandboxModelContext
+    private lateinit var launcherApps: LauncherApps
+    private var shortcuts: List<ShortcutInfo> = emptyList()
+
+    private val expectedPackage: String = "expected"
+    private val expectedShortcutId: String = "shortcut_id"
+    private val user: UserHandle = myUserHandle()
+    private val mockTaskController: ModelTaskController = mock()
+    private val mockAllApps: AllAppsList = mock()
+    private val mockAppState: LauncherAppState = mock()
+    private val mockIconCache: IconCache = mock()
+
+    private val expectedWai =
+        WorkspaceItemInfo().apply {
+            id = 1
+            itemType = ITEM_TYPE_DEEP_SHORTCUT
+            intent =
+                Intent().apply {
+                    `package` = expectedPackage
+                    putExtra(ShortcutKey.EXTRA_SHORTCUT_ID, expectedShortcutId)
+                }
+        }
+
+    @Before
+    fun setup() {
+        modelHelper = LauncherModelHelper()
+        modelHelper.loadModelSync()
+        context = modelHelper.sandboxContext
+        launcherApps = context.spyService(LauncherApps::class.java)
+        whenever(mockTaskController.app).thenReturn(mockAppState)
+        whenever(mockAppState.context).thenReturn(context)
+        whenever(mockAppState.iconCache).thenReturn(mockIconCache)
+        whenever(mockIconCache.getShortcutIcon(eq(expectedWai), any<CacheableShortcutInfo>()))
+            .then { _ -> { expectedWai.bitmap = BitmapInfo.LOW_RES_INFO } }
+        shortcuts = emptyList()
+        shortcutsChangedTask = ShortcutsChangedTask(expectedPackage, shortcuts, user, false)
+    }
+
+    @After
+    fun teardown() {
+        modelHelper.destroy()
+    }
+
+    @Test
+    fun `When installed pinned shortcut is found then keep in workspace`() {
+        // Given
+        shortcuts =
+            listOf(
+                mock<ShortcutInfo>().apply {
+                    whenever(isPinned).thenReturn(true)
+                    whenever(id).thenReturn(expectedShortcutId)
+                }
+            )
+        val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+        items.put(expectedWai.id, expectedWai)
+        doReturn(
+                ApplicationInfo().apply {
+                    enabled = true
+                    flags = flags or FLAG_INSTALLED
+                    isArchived = false
+                }
+            )
+            .whenever(launcherApps)
+            .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+        doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user))
+        // When
+        shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+        // Then
+        verify(mockAppState.iconCache)
+            .getShortcutIcon(eq(expectedWai), any<CacheableShortcutInfo>())
+        verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWai))
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+    fun `When installed unpinned shortcut is found with Flag off then remove from workspace`() {
+        // Given
+        shortcuts =
+            listOf(
+                mock<ShortcutInfo>().apply {
+                    whenever(isPinned).thenReturn(false)
+                    whenever(id).thenReturn(expectedShortcutId)
+                }
+            )
+        val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+        items.put(expectedWai.id, expectedWai)
+        doReturn(
+                ApplicationInfo().apply {
+                    enabled = true
+                    flags = flags or FLAG_INSTALLED
+                    isArchived = false
+                }
+            )
+            .whenever(launcherApps)
+            .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+        doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user))
+        // When
+        shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+        // Then
+        verify(mockTaskController)
+            .deleteAndBindComponentsRemoved(
+                any<Predicate<ItemInfo?>>(),
+                eq("removed because the shortcut is no longer available in shortcut service"),
+            )
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+    fun `When installed unpinned shortcut is found with Flag on then keep in workspace`() {
+        // Given
+        shortcuts =
+            listOf(
+                mock<ShortcutInfo>().apply {
+                    whenever(isPinned).thenReturn(false)
+                    whenever(id).thenReturn(expectedShortcutId)
+                }
+            )
+        val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+        items.put(expectedWai.id, expectedWai)
+        doReturn(
+                ApplicationInfo().apply {
+                    enabled = true
+                    flags = flags or FLAG_INSTALLED
+                    isArchived = false
+                }
+            )
+            .whenever(launcherApps)
+            .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+        doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user))
+        // When
+        shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+        // Then
+        verify(mockAppState.iconCache)
+            .getShortcutIcon(eq(expectedWai), any<CacheableShortcutInfo>())
+        verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWai))
+    }
+
+    @Test
+    fun `When shortcut app is uninstalled then skip handling`() {
+        // Given
+        shortcuts =
+            listOf(
+                mock<ShortcutInfo>().apply {
+                    whenever(isPinned).thenReturn(true)
+                    whenever(id).thenReturn(expectedShortcutId)
+                }
+            )
+        val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+        items.put(expectedWai.id, expectedWai)
+        doReturn(
+                ApplicationInfo().apply {
+                    enabled = true
+                    flags = flags and FLAG_INSTALLED.inv()
+                    isArchived = false
+                }
+            )
+            .whenever(launcherApps)
+            .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+        doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user))
+        // When
+        shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+        // Then
+        verify(mockTaskController, times(0)).deleteAndBindComponentsRemoved(any(), any())
+        verify(mockTaskController, times(0)).bindUpdatedWorkspaceItems(any())
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+    fun `When archived pinned shortcut is found with flag off then keep in workspace`() {
+        // Given
+        shortcuts =
+            listOf(
+                mock<ShortcutInfo>().apply {
+                    whenever(isPinned).thenReturn(true)
+                    whenever(id).thenReturn(expectedShortcutId)
+                }
+            )
+        val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+        items.put(expectedWai.id, expectedWai)
+        doReturn(
+                ApplicationInfo().apply {
+                    enabled = true
+                    flags = flags or FLAG_INSTALLED
+                    isArchived = true
+                }
+            )
+            .whenever(launcherApps)
+            .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+        doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user))
+        // When
+        shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+        // Then
+        verify(mockAppState.iconCache)
+            .getShortcutIcon(eq(expectedWai), any<CacheableShortcutInfo>())
+        verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWai))
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+    fun `When archived unpinned shortcut is found with flag off then keep in workspace`() {
+        // Given
+        shortcuts =
+            listOf(
+                mock<ShortcutInfo>().apply {
+                    whenever(isPinned).thenReturn(true)
+                    whenever(id).thenReturn(expectedShortcutId)
+                }
+            )
+        val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+        items.put(expectedWai.id, expectedWai)
+        doReturn(
+                ApplicationInfo().apply {
+                    enabled = true
+                    flags = flags or FLAG_INSTALLED
+                    isArchived = true
+                }
+            )
+            .whenever(launcherApps)
+            .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+        doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user))
+        // When
+        shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+        // Then
+        verify(mockAppState.iconCache)
+            .getShortcutIcon(eq(expectedWai), any<CacheableShortcutInfo>())
+        verify(mockTaskController).bindUpdatedWorkspaceItems(listOf(expectedWai))
+    }
+
+    @Test
+    fun `When updateIdMap true then trigger deep shortcut binding`() {
+        // Given
+        val expectedShortcut =
+            mock<ShortcutInfo>().apply {
+                whenever(isEnabled).thenReturn(true)
+                whenever(isDeclaredInManifest).thenReturn(true)
+                whenever(activity).thenReturn(ComponentName(expectedPackage, "expectedClass"))
+                whenever(id).thenReturn(expectedShortcutId)
+                whenever(userHandle).thenReturn(user)
+            }
+        shortcuts = listOf(expectedShortcut)
+        val expectedKey = ComponentKey(expectedShortcut.activity, expectedShortcut.userHandle)
+        doReturn(ApplicationInfo())
+            .whenever(launcherApps)
+            .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+        shortcutsChangedTask =
+            ShortcutsChangedTask(
+                packageName = expectedPackage,
+                shortcuts = shortcuts,
+                user = user,
+                shouldUpdateIdMap = true,
+            )
+        // When
+        shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+        // Then
+        assertThat(modelHelper.bgDataModel.deepShortcutMap).containsEntry(expectedKey, 1)
+        verify(mockTaskController).bindDeepShortcuts(eq(modelHelper.bgDataModel))
+    }
+
+    @Test
+    fun `When updateIdMap false then do not trigger deep shortcut binding`() {
+        // Given
+        val expectedShortcut =
+            mock<ShortcutInfo>().apply {
+                whenever(isEnabled).thenReturn(true)
+                whenever(isDeclaredInManifest).thenReturn(true)
+                whenever(activity).thenReturn(ComponentName(expectedPackage, "expectedClass"))
+                whenever(id).thenReturn(expectedShortcutId)
+                whenever(userHandle).thenReturn(user)
+            }
+        shortcuts = listOf(expectedShortcut)
+        val expectedKey = ComponentKey(expectedShortcut.activity, expectedShortcut.userHandle)
+        doReturn(ApplicationInfo())
+            .whenever(launcherApps)
+            .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+        shortcutsChangedTask =
+            ShortcutsChangedTask(
+                packageName = expectedPackage,
+                shortcuts = shortcuts,
+                user = user,
+                shouldUpdateIdMap = false,
+            )
+        // When
+        shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+        // Then
+        assertThat(modelHelper.bgDataModel.deepShortcutMap).doesNotContainKey(expectedKey)
+        verify(mockTaskController, times(0)).bindDeepShortcuts(eq(modelHelper.bgDataModel))
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+    fun `When restoring archived shortcut with flag on then skip handling`() {
+        // Given
+        shortcuts =
+            listOf(
+                mock<ShortcutInfo>().apply {
+                    whenever(isPinned).thenReturn(true)
+                    whenever(id).thenReturn(expectedShortcutId)
+                }
+            )
+        val items: IntSparseArrayMap<ItemInfo> = modelHelper.bgDataModel.itemsIdMap
+        items.put(expectedWai.id, expectedWai)
+        doReturn(
+                ApplicationInfo().apply {
+                    enabled = true
+                    flags = flags or FLAG_INSTALLED
+                    isArchived = true
+                }
+            )
+            .whenever(launcherApps)
+            .getApplicationInfo(eq(expectedPackage), any(), eq(user))
+        doReturn(shortcuts).whenever(launcherApps).getShortcuts(any(), eq(user))
+        // When
+        shortcutsChangedTask.execute(mockTaskController, modelHelper.bgDataModel, mockAllApps)
+        // Then
+        verify(mockTaskController, times(0)).deleteAndBindComponentsRemoved(any(), any())
+        verify(mockTaskController, times(0)).bindUpdatedWorkspaceItems(any())
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
index ae4ff04..777d81b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
@@ -43,7 +43,6 @@
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
-import java.util.function.Predicate
 import org.junit.Assert.fail
 import org.junit.Before
 import org.junit.Rule
@@ -65,7 +64,6 @@
     @Mock private lateinit var appWidgetManager: AppWidgetManager
     @Mock private lateinit var app: LauncherAppState
     @Mock private lateinit var iconCacheMock: IconCache
-    @Mock private lateinit var widgetsFilterDataProvider: WidgetsFilterDataProvider
 
     private lateinit var context: Context
     private lateinit var idp: InvariantDeviceProfile
@@ -119,6 +117,11 @@
                     // A widget in different package (none of that app's widgets are in widget
                     // sections xml)
                     createAppWidgetProviderInfo(AppBTestWidgetComponent),
+                    // A widget in different app that is meant to be hidden from picker
+                    createAppWidgetProviderInfo(
+                        AppCPinOnlyTestWidgetComponent,
+                        /*hideFromPicker=*/ true,
+                    ),
                 )
             )
 
@@ -129,12 +132,13 @@
     }
 
     @Test
-    fun widgetsByPackage_treatsWidgetSectionsAsSeparatePackageItems() {
+    fun widgetsByPackageForPicker_treatsWidgetSectionsAsSeparatePackageItems() {
         loadWidgets()
 
-        val packages: Map<PackageItemInfo, List<WidgetItem>> = underTest.widgetsByPackageItem
+        val packages: Map<PackageItemInfo, List<WidgetItem>> =
+            underTest.widgetsByPackageItemForPicker
 
-        // expect 3 package items
+        // expect 3 package items (no app C as its widget is hidden from picker)
         // one for the custom section with widget from appA
         // one for package section for second widget from appA (that wasn't listed in xml)
         // and one for package section for appB
@@ -167,6 +171,13 @@
         assertThat(appBPackageSection).hasSize(1)
         val widgetsInAppBSection = appBPackageSection.entries.first().value
         assertThat(widgetsInAppBSection).hasSize(1)
+
+        // No App C's package section - as the only widget hosted by it is hidden in picker
+        val appCPackageSection =
+            packageSections.filter {
+                it.key.packageName == AppCPinOnlyTestWidgetComponent.packageName
+            }
+        assertThat(appCPackageSection).isEmpty()
     }
 
     @Test
@@ -175,7 +186,29 @@
 
         val widgetsByComponentKey: Map<ComponentKey, WidgetItem> = underTest.widgetsByComponentKey
 
+        // Has all widgets including ones not visible in picker
+        assertThat(widgetsByComponentKey).hasSize(4)
+        widgetsByComponentKey.forEach { entry ->
+            assertThat(entry.key).isEqualTo(entry.value as ComponentKey)
+        }
+    }
+
+    @Test
+    fun widgetComponentMapForPicker_excludesWidgetsHiddenInPicker() {
+        loadWidgets()
+
+        val widgetsByComponentKey: Map<ComponentKey, WidgetItem> =
+            underTest.widgetsByComponentKeyForPicker
+
+        // Has all widgets excluding the appC's widget.
         assertThat(widgetsByComponentKey).hasSize(3)
+        assertThat(
+                widgetsByComponentKey.filter {
+                    it.key.componentName == AppCPinOnlyTestWidgetComponent
+                }
+            )
+            .isEmpty()
+        // widgets mapped correctly
         widgetsByComponentKey.forEach { entry ->
             assertThat(entry.key).isEqualTo(entry.value as ComponentKey)
         }
@@ -189,7 +222,7 @@
     }
 
     @Test
-    fun getWidgetsByPackageItem_returnsACopyOfMap() {
+    fun getWidgetsByPackageItemForPicker_returnsACopyOfMap() {
         loadWidgets()
 
         val latch = CountDownLatch(1)
@@ -198,8 +231,8 @@
 
             // each "widgetsByPackageItem" read returns a different copy of the map held internally.
             // Modifying one shouldn't impact another.
-            for ((_, _) in underTest.widgetsByPackageItem.entries) {
-                underTest.widgetsByPackageItem.clear()
+            for ((_, _) in underTest.widgetsByPackageItemForPicker.entries) {
+                underTest.widgetsByPackageItemForPicker.clear()
                 if (update) { // trigger update
                     update = false
                     // Similarly, model could update its code independently while a client is
@@ -217,27 +250,6 @@
         // No exception
     }
 
-    @Test
-    fun updateWidgetFilters_setsFiltersCorrectly() {
-        val testDefaultWidgetFilter = Predicate<WidgetItem> { w -> w.widgetInfo != null }
-        whenever(widgetsFilterDataProvider.getDefaultWidgetsFilter())
-            .thenReturn(testDefaultWidgetFilter)
-        val testPredicatedWidgetFilter = Predicate<WidgetItem> { w -> w.widgetInfo != null }
-        whenever(widgetsFilterDataProvider.getPredictedWidgetsFilter())
-            .thenReturn(testPredicatedWidgetFilter)
-
-        underTest.updateWidgetFilters(widgetsFilterDataProvider)
-
-        assertThat(underTest.defaultWidgetsFilter).isEqualTo(testDefaultWidgetFilter)
-        assertThat(underTest.predictedWidgetsFilter).isEqualTo(testPredicatedWidgetFilter)
-    }
-
-    @Test
-    fun widgetFilters_nullInitially() {
-        assertThat(underTest.defaultWidgetsFilter).isNull()
-        assertThat(underTest.predictedWidgetsFilter).isNull()
-    }
-
     private fun loadWidgets() {
         val latch = CountDownLatch(1)
         Executors.MODEL_EXECUTOR.execute {
@@ -256,6 +268,9 @@
         private val AppBTestWidgetComponent: ComponentName =
             ComponentName.createRelative("com.test.package", "TestProvider")
 
+        private val AppCPinOnlyTestWidgetComponent: ComponentName =
+            ComponentName.createRelative("com.testC.package", "PinOnlyTestProvider")
+
         private const val LOAD_WIDGETS_TIMEOUT_SECONDS = 2L
     }
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
index 7099d38..a55d64b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/WorkspaceItemProcessorTest.kt
@@ -18,16 +18,19 @@
 
 import android.appwidget.AppWidgetProviderInfo
 import android.content.ComponentName
-import android.content.Context
 import android.content.Intent
+import android.content.pm.ApplicationInfo
 import android.content.pm.LauncherApps
 import android.content.pm.PackageInstaller
 import android.content.pm.ShortcutInfo
 import android.os.Process
 import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
 import android.util.LongSparseArray
-import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.launcher3.Flags
 import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherSettings.Favorites
 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
@@ -44,9 +47,14 @@
 import com.android.launcher3.model.data.LauncherAppWidgetInfo
 import com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_UI_NOT_READY
 import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_ICON
+import com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORE_STARTED
 import com.android.launcher3.pm.UserCache
 import com.android.launcher3.shortcuts.ShortcutKey
 import com.android.launcher3.util.ComponentKey
+import com.android.launcher3.util.ContentWriter
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
 import com.android.launcher3.util.PackageManagerHelper
 import com.android.launcher3.util.PackageUserKey
 import com.android.launcher3.util.UserIconInfo
@@ -55,6 +63,7 @@
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
@@ -66,6 +75,7 @@
 import org.mockito.kotlin.any
 import org.mockito.kotlin.anyOrNull
 import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.whenever
@@ -73,20 +83,23 @@
 @RunWith(AndroidJUnit4::class)
 class WorkspaceItemProcessorTest {
 
+    @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
     @Mock private lateinit var mockIconRequestInfo: IconRequestInfo<WorkspaceItemInfo>
     @Mock private lateinit var mockWorkspaceInfo: WorkspaceItemInfo
     @Mock private lateinit var mockBgDataModel: BgDataModel
-    @Mock private lateinit var mockContext: Context
     @Mock private lateinit var mockAppState: LauncherAppState
     @Mock private lateinit var mockPmHelper: PackageManagerHelper
-    @Mock private lateinit var mockLauncherApps: LauncherApps
     @Mock private lateinit var mockCursor: LoaderCursor
     @Mock private lateinit var mockUserCache: UserCache
     @Mock private lateinit var mockUserManagerState: UserManagerState
     @Mock private lateinit var mockWidgetInflater: WidgetInflater
 
-    private var intent: Intent = Intent()
-    private var mUserHandle: UserHandle = UserHandle(0)
+    lateinit var mModelHelper: LauncherModelHelper
+    lateinit var mContext: SandboxModelContext
+    lateinit var mLauncherApps: LauncherApps
+    private var mIntent: Intent = Intent()
+    private var mUserHandle: UserHandle = Process.myUserHandle()
     private var mIconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>> = mutableListOf()
     private var mComponentName: ComponentName = ComponentName("package", "class")
     private var mUnlockedUsersArray: LongSparseArray<Boolean> = LongSparseArray()
@@ -101,40 +114,35 @@
 
     @Before
     fun setup() {
-        mUserHandle = UserHandle(0)
+        mModelHelper = LauncherModelHelper()
+        mContext = mModelHelper.sandboxContext
+        mLauncherApps =
+            mContext.spyService(LauncherApps::class.java).apply {
+                doReturn(true).whenever(this).isPackageEnabled("package", mUserHandle)
+                doReturn(true).whenever(this).isActivityEnabled(mComponentName, mUserHandle)
+            }
+        mUserHandle = Process.myUserHandle()
         mockIconRequestInfo = mock<IconRequestInfo<WorkspaceItemInfo>>()
         mockWorkspaceInfo = mock<WorkspaceItemInfo>()
         mockBgDataModel = mock<BgDataModel>()
         mComponentName = ComponentName("package", "class")
         mUnlockedUsersArray = LongSparseArray<Boolean>(1).apply { put(101, true) }
-        intent =
+        mIntent =
             Intent().apply {
                 component = mComponentName
                 `package` = "pkg"
                 putExtra(ShortcutKey.EXTRA_SHORTCUT_ID, "")
             }
-        mockLauncherApps =
-            mock<LauncherApps>().apply {
-                whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
-                whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(true)
-            }
-        mockContext =
-            mock<Context>().apply {
-                whenever(packageManager).thenReturn(mock())
-                whenever(packageManager.getUserBadgedLabel(any(), any())).thenReturn("")
-                whenever(applicationContext).thenReturn(ApplicationProvider.getApplicationContext())
-                whenever(getSystemService(LauncherApps::class.java)).thenReturn(mockLauncherApps)
-            }
         mockAppState =
             mock<LauncherAppState>().apply {
-                whenever(context).thenReturn(mockContext)
+                whenever(context).thenReturn(mContext)
                 whenever(iconCache).thenReturn(mock())
                 whenever(iconCache.getShortcutIcon(any(), any(), any())).then {}
             }
         mockPmHelper =
             mock<PackageManagerHelper>().apply {
                 whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
-                    .thenReturn(intent)
+                    .thenReturn(mIntent)
             }
         mockCursor =
             mock(LoaderCursor::class.java, RETURNS_DEEP_STUBS).apply {
@@ -143,9 +151,9 @@
                 id = 1
                 restoreFlag = 1
                 serialNumber = 101
-                whenever(parseIntent()).thenReturn(intent)
+                whenever(parseIntent()).thenReturn(mIntent)
                 whenever(markRestored()).doAnswer { restoreFlag = 0 }
-                whenever(updater().put(Favorites.INTENT, intent.toUri(0)).commit()).thenReturn(1)
+                whenever(updater().put(Favorites.INTENT, mIntent.toUri(0)).commit()).thenReturn(1)
                 whenever(getAppShortcutInfo(any(), any(), any(), any()))
                     .thenReturn(mockWorkspaceInfo)
                 whenever(createIconRequestInfo(any(), any())).thenReturn(mockIconRequestInfo)
@@ -177,7 +185,7 @@
         memoryLogger: LoaderMemoryLogger? = null,
         userCache: UserCache = mockUserCache,
         userManagerState: UserManagerState = mockUserManagerState,
-        launcherApps: LauncherApps = mockLauncherApps,
+        launcherApps: LauncherApps = mLauncherApps,
         shortcutKeyToPinnedShortcuts: Map<ShortcutKey, ShortcutInfo> = mKeyToPinnedShortcutsMap,
         app: LauncherAppState = mockAppState,
         bgDataModel: BgDataModel = mockBgDataModel,
@@ -244,7 +252,7 @@
     fun `When app has null target package then mark deleted`() {
 
         // Given
-        intent.apply {
+        mIntent.apply {
             component = null
             `package` = null
         }
@@ -264,8 +272,8 @@
 
         // Given
         mComponentName = ComponentName("", "")
-        intent.component = mComponentName
-        intent.`package` = ""
+        mIntent.component = mComponentName
+        mIntent.`package` = ""
 
         // When
         itemProcessorUnderTest = createWorkspaceItemProcessorUnderTest()
@@ -298,15 +306,14 @@
     fun `When fallback Activity found for app then mark restored`() {
 
         // Given
-        mockLauncherApps =
-            mock<LauncherApps>().apply {
-                whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
-                whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false)
-            }
+        mLauncherApps.apply {
+            whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
+            whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false)
+        }
         mockPmHelper =
             mock<PackageManagerHelper>().apply {
                 whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
-                    .thenReturn(intent)
+                    .thenReturn(mIntent)
             }
 
         // When
@@ -317,7 +324,7 @@
         assertWithMessage("item restoreFlag should be set to 0")
             .that(mockCursor.restoreFlag)
             .isEqualTo(0)
-        verify(mockCursor.updater().put(Favorites.INTENT, intent.toUri(0))).commit()
+        verify(mockCursor.updater().put(Favorites.INTENT, mIntent.toUri(0))).commit()
         assertThat(mIconRequestInfos).containsExactly(mockIconRequestInfo)
         verify(mockCursor).checkAndAddItem(mockWorkspaceInfo, mockBgDataModel, null)
     }
@@ -326,11 +333,10 @@
     fun `When app with disabled activity and no fallback found then mark deleted`() {
 
         // Given
-        mockLauncherApps =
-            mock<LauncherApps>().apply {
-                whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
-                whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false)
-            }
+        mLauncherApps.apply {
+            whenever(isPackageEnabled("package", mUserHandle)).thenReturn(true)
+            whenever(isActivityEnabled(mComponentName, mUserHandle)).thenReturn(false)
+        }
         mockPmHelper =
             mock<PackageManagerHelper>().apply {
                 whenever(getAppLaunchIntent(mComponentName.packageName, mUserHandle))
@@ -358,11 +364,11 @@
 
     @Test
     fun `When valid Pinned Deep Shortcut then mark restored`() {
-
         // Given
         mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT
         val expectedShortcutInfo =
             mock<ShortcutInfo>().apply {
+                whenever(userHandle).thenReturn(mUserHandle)
                 whenever(id).thenReturn("")
                 whenever(`package`).thenReturn("")
                 whenever(activity).thenReturn(mock())
@@ -372,7 +378,7 @@
                 whenever(disabledReason).thenReturn(0)
                 whenever(persons).thenReturn(EMPTY_PERSON_ARRAY)
             }
-        val shortcutKey = ShortcutKey.fromIntent(intent, mockCursor.user)
+        val shortcutKey = ShortcutKey.fromIntent(mIntent, mockCursor.user)
         mKeyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo
         mIconRequestInfos = mutableListOf()
 
@@ -393,6 +399,101 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+    fun `When Archived Deep Shortcut with flag on then mark restored`() {
+        // Given
+        val mockContentWriter: ContentWriter = mock()
+        val mockAppInfo: ApplicationInfo =
+            mock<ApplicationInfo>().apply {
+                isArchived = true
+                enabled = true
+            }
+        val expectedRestoreFlag = FLAG_RESTORED_ICON or FLAG_RESTORE_STARTED
+        doReturn(mockAppInfo).whenever(mLauncherApps).getApplicationInfo(any(), any(), any())
+        whenever(mockContentWriter.put(Favorites.RESTORED, expectedRestoreFlag))
+            .thenReturn(mockContentWriter)
+        whenever(mockContentWriter.commit()).thenReturn(1)
+        mockCursor.apply {
+            itemType = ITEM_TYPE_DEEP_SHORTCUT
+            restoreFlag = restoreFlag or FLAG_RESTORED_ICON
+            whenever(updater()).thenReturn(mockContentWriter)
+        }
+        mIconRequestInfos = mutableListOf()
+
+        // When
+        itemProcessorUnderTest =
+            createWorkspaceItemProcessorUnderTest(allDeepShortcuts = mAllDeepShortcuts)
+        itemProcessorUnderTest.processItem()
+
+        // Then
+        assertThat(mockCursor.restoreFlag and FLAG_RESTORED_ICON).isEqualTo(FLAG_RESTORED_ICON)
+        assertThat(mockCursor.restoreFlag and FLAG_RESTORE_STARTED).isEqualTo(FLAG_RESTORE_STARTED)
+        assertThat(mIconRequestInfos).isNotEmpty()
+        assertThat(mAllDeepShortcuts).isEmpty()
+        verify(mockContentWriter).put(Favorites.RESTORED, expectedRestoreFlag)
+        verify(mockCursor).checkAndAddItem(any(), eq(mockBgDataModel), eq(null))
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_SHORTCUTS)
+    fun `When Archived Deep Shortcut with flag off then remove`() {
+        // Given
+        mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT
+        mIconRequestInfos = mutableListOf()
+
+        // When
+        itemProcessorUnderTest =
+            createWorkspaceItemProcessorUnderTest(allDeepShortcuts = mAllDeepShortcuts)
+        itemProcessorUnderTest.processItem()
+
+        // Then
+        assertWithMessage("item restoreFlag should be set to 0")
+            .that(mockCursor.restoreFlag)
+            .isEqualTo(0)
+        assertThat(mIconRequestInfos).isEmpty()
+        assertThat(mAllDeepShortcuts).isEmpty()
+        verify(mockCursor)
+            .markDeleted(
+                "Pinned shortcut not found from request. package=pkg, user=$mUserHandle",
+                "shortcut_not_found",
+            )
+    }
+
+    @Test
+    fun `When Pinned Deep Shortcut is not stored in ShortcutManager re-query by Shortcut ID`() {
+        // Given
+        mockCursor.itemType = ITEM_TYPE_DEEP_SHORTCUT
+        val si =
+            mock<ShortcutInfo>().apply {
+                whenever(id).thenReturn("")
+                whenever(`package`).thenReturn("")
+                whenever(activity).thenReturn(mock())
+                whenever(longLabel).thenReturn("")
+                whenever(isEnabled).thenReturn(true)
+                whenever(disabledMessage).thenReturn("")
+                whenever(disabledReason).thenReturn(0)
+                whenever(persons).thenReturn(EMPTY_PERSON_ARRAY)
+                whenever(userHandle).thenReturn(mUserHandle)
+            }
+        doReturn(listOf(si)).whenever(mLauncherApps).getShortcuts(any(), any())
+        mKeyToPinnedShortcutsMap.clear()
+        mIconRequestInfos = mutableListOf()
+
+        // When
+        itemProcessorUnderTest =
+            createWorkspaceItemProcessorUnderTest(allDeepShortcuts = mAllDeepShortcuts)
+        itemProcessorUnderTest.processItem()
+
+        // Then
+        verify(mLauncherApps).getShortcuts(any(), any())
+        assertWithMessage("item restoreFlag should be set to 0")
+            .that(mockCursor.restoreFlag)
+            .isEqualTo(0)
+        verify(mockCursor).markRestored()
+        verify(mockCursor).checkAndAddItem(any(), any(), eq(null))
+    }
+
+    @Test
     fun `When Pinned Deep Shortcut not found then mark deleted`() {
 
         // Given
@@ -412,7 +513,7 @@
         verify(mockCursor, times(0)).checkAndAddItem(any(), any(), anyOrNull())
         verify(mockCursor)
             .markDeleted(
-                "Pinned shortcut not found from request. package=pkg, user=UserHandle{0}",
+                "Pinned shortcut not found from request. package=pkg, user=$mUserHandle",
                 "shortcut_not_found",
             )
     }
@@ -432,15 +533,15 @@
                 whenever(disabledMessage).thenReturn("")
                 whenever(disabledReason).thenReturn(0)
                 whenever(persons).thenReturn(EMPTY_PERSON_ARRAY)
-                whenever(userHandle).thenReturn(Process.myUserHandle())
+                whenever(userHandle).thenReturn(mUserHandle)
             }
         mIconRequestInfos = mutableListOf()
         // Make sure shortcuts map has expected key from expected package
-        intent.`package` = mComponentName.packageName
-        val shortcutKey = ShortcutKey.fromIntent(intent, mockCursor.user)
+        mIntent.`package` = mComponentName.packageName
+        val shortcutKey = ShortcutKey.fromIntent(mIntent, mockCursor.user)
         mKeyToPinnedShortcutsMap[shortcutKey] = expectedShortcutInfo
         // set intent package back to null to test scenario
-        intent.`package` = null
+        mIntent.`package` = null
 
         // When
         itemProcessorUnderTest =
@@ -461,11 +562,10 @@
     @Test
     fun `When processing Folder then create FolderInfo and mark restored`() {
         val actualFolderInfo = FolderInfo()
-        mockBgDataModel =
-            mock<BgDataModel>().apply { whenever(findOrMakeFolder(1)).thenReturn(actualFolderInfo) }
+        mockBgDataModel = mock<BgDataModel>()
         mockCursor =
             mock<LoaderCursor>().apply {
-                user = UserHandle(0)
+                user = mUserHandle
                 itemType = ITEM_TYPE_FOLDER
                 id = 1
                 container = 100
@@ -476,6 +576,7 @@
                 whenever(getColumnIndex(Favorites.TITLE)).thenReturn(4)
                 whenever(getString(4)).thenReturn("title")
                 whenever(options).thenReturn(5)
+                whenever(findOrMakeFolder(eq(1), any())).thenReturn(actualFolderInfo)
             }
         val expectedFolderInfo =
             FolderInfo().apply {
@@ -567,7 +668,8 @@
 
         // Then
         val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java)
-        verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel))
+        verify(mockCursor)
+            .checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel), anyOrNull())
         val actualWidgetInfo = widgetInfoCaptor.value
         with(actualWidgetInfo) {
             assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName)
@@ -622,7 +724,7 @@
         itemProcessorUnderTest.processItem()
 
         // Then
-        verify(mockCursor).checkAndAddItem(any(), any())
+        verify(mockCursor).checkAndAddItem(any(), eq(mockBgDataModel), eq(null))
     }
 
     @Test
diff --git a/tests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt b/tests/multivalentTests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt
similarity index 98%
rename from tests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt
index cc8e61d..174d716 100644
--- a/tests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/gridmigration/GridMigrationUtils.kt
@@ -24,8 +24,11 @@
 
 class MockSet(override val size: Int) : Set<String> {
     override fun contains(element: String): Boolean = true
+
     override fun containsAll(elements: Collection<String>): Boolean = true
+
     override fun isEmpty(): Boolean = false
+
     override fun iterator(): Iterator<String> = listOf<String>().iterator()
 }
 
@@ -91,7 +94,7 @@
                 appWidgetProvider = cursor.getString(indexWidgetProvider),
                 intent = cursor.getString(indexIntent),
                 type = cursor.getInt(indexItemType),
-                container = cursor.getInt(container)
+                container = cursor.getInt(container),
             )
         )
     }
diff --git a/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt b/tests/multivalentTests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
similarity index 99%
rename from tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
rename to tests/multivalentTests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
index b96dbcd..1d1e7eb 100644
--- a/tests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/model/gridmigration/ValidGridMigrationUnitTest.kt
@@ -138,6 +138,7 @@
                 val gridSizeMigrationLogic = GridSizeMigrationLogic()
                 val idsInUse = mutableListOf<Int>()
                 gridSizeMigrationLogic.migrateHotseat(
+                    srcGrid.size.x,
                     dstGrid.size.x,
                     GridSizeMigrationDBController.DbReader(it.db, srcGrid.tableName, context),
                     GridSizeMigrationDBController.DbReader(it.db, dstGrid.tableName, context),
@@ -156,6 +157,7 @@
                     dbHelper,
                     GridSizeMigrationDBController.DbReader(it.db, srcGrid.tableName, context),
                     GridSizeMigrationDBController.DbReader(it.db, dstGrid.tableName, context),
+                    srcGrid.size.x,
                     dstGrid.size.x,
                     dstGrid.size,
                     srcGrid.toGridState(),
diff --git a/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt b/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
index 15a9964..23c1da9 100644
--- a/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/pm/InstallSessionTrackerTest.kt
@@ -219,7 +219,7 @@
             .whenever(launcherApps)
             .unregisterPackageInstallerSessionCallback(installSessionTracker)
         // When
-        installSessionTracker.unregister()
+        installSessionTracker.close()
         // Then
         verify(launcherApps).unregisterPackageInstallerSessionCallback(installSessionTracker)
     }
diff --git a/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
new file mode 100644
index 0000000..66b8be0
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/shapes/ShapesProviderTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.shapes
+
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.core.graphics.PathParser
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.Flags.FLAG_ENABLE_LAUNCHER_ICON_SHAPES
+import com.android.launcher3.graphics.ShapeDelegate.GenericPathShape
+import com.android.launcher3.shapes.ShapesProvider.ARCH_KEY
+import com.android.launcher3.shapes.ShapesProvider.CIRCLE_KEY
+import com.android.launcher3.shapes.ShapesProvider.FOUR_SIDED_COOKIE_KEY
+import com.android.launcher3.shapes.ShapesProvider.SEVEN_SIDED_COOKIE_KEY
+import com.android.launcher3.shapes.ShapesProvider.SQUARE_KEY
+import com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShapesProviderTest {
+
+    @get:Rule val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    fun `verify valid path arch`() {
+        ShapesProvider.iconShapes
+            .find { it.key == ARCH_KEY }!!
+            .run {
+                GenericPathShape(pathString)
+                PathParser.createPathFromPathData(pathString)
+            }
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    fun `verify valid path 4_sided_cookie`() {
+        ShapesProvider.iconShapes
+            .find { it.key == FOUR_SIDED_COOKIE_KEY }!!
+            .run {
+                GenericPathShape(pathString)
+                PathParser.createPathFromPathData(pathString)
+            }
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    fun `verify valid path seven_sided_cookie`() {
+        ShapesProvider.iconShapes
+            .find { it.key == SEVEN_SIDED_COOKIE_KEY }!!
+            .run {
+                GenericPathShape(pathString)
+                PathParser.createPathFromPathData(pathString)
+            }
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    fun `verify valid path circle`() {
+        ShapesProvider.iconShapes
+            .find { it.key == CIRCLE_KEY }!!
+            .run {
+                GenericPathShape(pathString)
+                PathParser.createPathFromPathData(pathString)
+            }
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    fun `verify valid path square`() {
+        ShapesProvider.iconShapes
+            .find { it.key == ARCH_KEY }!!
+            .run {
+                GenericPathShape(pathString)
+                PathParser.createPathFromPathData(pathString)
+            }
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    fun `verify valid folder path clover`() {
+        ShapesProvider.iconShapes
+            .find { it.key == CIRCLE_KEY }!!
+            .run {
+                GenericPathShape(folderPathString)
+                PathParser.createPathFromPathData(folderPathString)
+            }
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    fun `verify valid folder path complexClover`() {
+        ShapesProvider.iconShapes
+            .find { it.key == FOUR_SIDED_COOKIE_KEY }!!
+            .run {
+                GenericPathShape(folderPathString)
+                PathParser.createPathFromPathData(folderPathString)
+            }
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    fun `verify valid folder path arch`() {
+        ShapesProvider.iconShapes
+            .find { it.key == ARCH_KEY }!!
+            .run {
+                GenericPathShape(folderPathString)
+                PathParser.createPathFromPathData(folderPathString)
+            }
+    }
+
+    @Test
+    @EnableFlags(FLAG_ENABLE_LAUNCHER_ICON_SHAPES, FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+    fun `verify valid folder path square`() {
+        ShapesProvider.iconShapes
+            .find { it.key == SQUARE_KEY }!!
+            .run {
+                GenericPathShape(folderPathString)
+                PathParser.createPathFromPathData(folderPathString)
+            }
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
index b933ed2..5c326f9 100644
--- a/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/ui/BubbleTextViewTest.java
@@ -20,8 +20,6 @@
 import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL;
 import static android.text.style.DynamicDrawableSpan.ALIGN_CENTER;
 
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
 import static com.android.launcher3.BubbleTextView.DISPLAY_ALL_APPS;
 import static com.android.launcher3.BubbleTextView.DISPLAY_PREDICTION_ROW;
 import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT;
@@ -30,6 +28,7 @@
 import static com.android.launcher3.Flags.FLAG_USE_NEW_ICON_FOR_ARCHIVED_APPS;
 import static com.android.launcher3.LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE;
 import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -41,9 +40,12 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
 import android.graphics.Typeface;
 import android.os.Build;
 import android.os.UserHandle;
+import android.platform.test.annotations.DisableFlags;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 import android.text.SpannedString;
@@ -58,14 +60,20 @@
 import com.android.launcher3.Flags;
 import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.model.data.ItemInfoWithIcon;
+import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.search.StringMatcherUtility;
 import com.android.launcher3.util.ActivityContextWrapper;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext;
+import com.android.launcher3.util.TestUtil;
 import com.android.launcher3.views.BaseDragLayer;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -111,19 +119,22 @@
     private static final float SPACE_MULTIPLIER = 1;
     private static final float SPACE_EXTRA = 0;
 
+    private SandboxModelContext mModelContext;
+
     private BubbleTextView mBubbleTextView;
     private ItemInfoWithIcon mItemInfoWithIcon;
     private Context mContext;
     private int mLimitedWidth;
     private AppInfo mGmailAppInfo;
-    private LauncherPrefs mLauncherPrefs;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         Utilities.enableRunningInTestHarnessForTests();
-        mContext = new ActivityContextWrapper(getApplicationContext());
-        mLauncherPrefs = LauncherPrefs.get(mContext);
+        mModelContext = new SandboxModelContext();
+        LauncherPrefs.get(mModelContext).put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
+
+        mContext = new ActivityContextWrapper(mModelContext);
         mBubbleTextView = new BubbleTextView(mContext);
         mBubbleTextView.reset();
 
@@ -149,10 +160,14 @@
         mGmailAppInfo = new AppInfo(componentName, "Gmail", WORK_HANDLE, new Intent());
     }
 
+    @After
+    public void tearDown() {
+        mModelContext.onDestroy();
+    }
+
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
     public void testEmptyString_flagOn() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
-        mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
         mItemInfoWithIcon.title = EMPTY_STRING;
         mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
         mBubbleTextView.applyLabel(mItemInfoWithIcon);
@@ -165,8 +180,8 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
     public void testEmptyString_flagOff() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
         mItemInfoWithIcon.title = EMPTY_STRING;
         mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
         mBubbleTextView.applyLabel(mItemInfoWithIcon);
@@ -179,9 +194,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
     public void testStringWithSpaceLongerThanCharLimit_flagOn() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
-        mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
         // test string: "Battery Stats"
         mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
         mBubbleTextView.applyLabel(mItemInfoWithIcon);
@@ -195,8 +209,8 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
     public void testStringWithSpaceLongerThanCharLimit_flagOff() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
         // test string: "Battery Stats"
         mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
         mBubbleTextView.applyLabel(mItemInfoWithIcon);
@@ -210,9 +224,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
     public void testLongStringNoSpaceLongerThanCharLimit_flagOn() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
-        mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
         // test string: "flutterappflorafy"
         mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
         mBubbleTextView.applyLabel(mItemInfoWithIcon);
@@ -226,8 +239,8 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
     public void testLongStringNoSpaceLongerThanCharLimit_flagOff() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
         // test string: "flutterappflorafy"
         mItemInfoWithIcon.title = TEST_LONG_STRING_NO_SPACE_LONGER_THAN_CHAR_LIMIT;
         mBubbleTextView.applyLabel(mItemInfoWithIcon);
@@ -241,9 +254,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
     public void testLongStringWithSpaceLongerThanCharLimit_flagOn() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
-        mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
         // test string: "System UWB Field Test"
         mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
         mBubbleTextView.applyLabel(mItemInfoWithIcon);
@@ -257,8 +269,8 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
     public void testLongStringWithSpaceLongerThanCharLimit_flagOff() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
         // test string: "System UWB Field Test"
         mItemInfoWithIcon.title = TEST_LONG_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
         mBubbleTextView.applyLabel(mItemInfoWithIcon);
@@ -272,9 +284,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
     public void testLongStringSymbolLongerThanCharLimit_flagOn() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
-        mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
         // test string: "LEGO®Builder"
         mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
         mBubbleTextView.applyLabel(mItemInfoWithIcon);
@@ -288,8 +299,8 @@
     }
 
     @Test
+    @DisableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
     public void testLongStringSymbolLongerThanCharLimit_flagOff() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
         // test string: "LEGO®Builder"
         mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
         mBubbleTextView.applyLabel(mItemInfoWithIcon);
@@ -359,9 +370,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
     public void testEnsurePredictionRowIsTwoLine() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
-        mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
         // test string: "Battery Stats"
         mItemInfoWithIcon.title = TEST_STRING_WITH_SPACE_LONGER_THAN_CHAR_LIMIT;
         mBubbleTextView.setDisplay(DISPLAY_PREDICTION_ROW);
@@ -375,9 +385,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
     public void modifyTitleToSupportMultiLine_whenLimitedHeight_shouldBeOneLine() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
-        mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
         // test string: "LEGO®Builder"
         mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
         mBubbleTextView.applyLabel(mItemInfoWithIcon);
@@ -390,9 +399,8 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE)
     public void modifyTitleToSupportMultiLine_whenUnlimitedHeight_shouldBeTwoLine() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TWOLINE_TOGGLE);
-        mLauncherPrefs.put(ENABLE_TWOLINE_ALLAPPS_TOGGLE, true);
         // test string: "LEGO®Builder"
         mItemInfoWithIcon.title = TEST_LONG_STRING_SYMBOL_LONGER_THAN_CHAR_LIMIT;
         mBubbleTextView.setDisplay(DISPLAY_ALL_APPS);
@@ -484,4 +492,38 @@
 
         assertThat(mBubbleTextView.getIcon().hasBadge()).isEqualTo(true);
     }
+
+    @Test
+    public void applyingPendingIcon_preserves_last_icon() throws Exception {
+        mItemInfoWithIcon.bitmap =
+                BitmapInfo.fromBitmap(Bitmap.createBitmap(100, 100, Config.ARGB_8888));
+        mItemInfoWithIcon.setProgressLevel(30, PackageInstallInfo.STATUS_INSTALLING);
+
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR,
+                () -> mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon));
+        assertThat(mBubbleTextView.getIcon()).isInstanceOf(PreloadIconDrawable.class);
+        assertThat(mBubbleTextView.getIcon().getLevel()).isEqualTo(30);
+        PreloadIconDrawable oldIcon = (PreloadIconDrawable) mBubbleTextView.getIcon();
+
+        // Same icon is used when progress changes
+        mItemInfoWithIcon.setProgressLevel(50, PackageInstallInfo.STATUS_INSTALLING);
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR,
+                () -> mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon));
+        assertThat(mBubbleTextView.getIcon()).isSameInstanceAs(oldIcon);
+        assertThat(mBubbleTextView.getIcon().getLevel()).isEqualTo(50);
+
+        // Icon is replaced with a non pending icon when download finishes
+        mItemInfoWithIcon.setProgressLevel(100, PackageInstallInfo.STATUS_INSTALLED);
+
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR, () -> {
+            mBubbleTextView.applyIconAndLabel(mItemInfoWithIcon);
+            assertThat(mBubbleTextView.getIcon()).isSameInstanceAs(oldIcon);
+            assertThat(oldIcon.getActiveAnimation()).isNotNull();
+            oldIcon.getActiveAnimation().end();
+        });
+
+        // Assert that the icon is replaced with a non-pending icon
+        assertThat(mBubbleTextView.getIcon()).isNotInstanceOf(PreloadIconDrawable.class);
+    }
+
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java b/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java
index 4217d22..f4dc88d 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ActivityContextWrapper.java
@@ -18,23 +18,15 @@
 import android.R;
 import android.content.Context;
 import android.content.ContextWrapper;
-import android.view.ContextThemeWrapper;
 
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.views.ActivityContext;
 import com.android.launcher3.views.BaseDragLayer;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * {@link ContextWrapper} with internal Launcher interface for testing
  */
-public class ActivityContextWrapper extends ContextThemeWrapper implements ActivityContext {
-
-    private final List<OnDeviceProfileChangeListener> mDpChangeListeners = new ArrayList<>();
+public class ActivityContextWrapper extends BaseContext {
 
     private final DeviceProfile mProfile;
     private final MyDragLayer mMyDragLayer;
@@ -47,20 +39,15 @@
         super(base, theme);
         mProfile = InvariantDeviceProfile.INSTANCE.get(base).getDeviceProfile(base).copy(base);
         mMyDragLayer = new MyDragLayer(this);
+        Executors.MAIN_EXECUTOR.execute(this::onViewCreated);
     }
 
-
     @Override
     public BaseDragLayer getDragLayer() {
         return mMyDragLayer;
     }
 
     @Override
-    public List<OnDeviceProfileChangeListener> getOnDeviceProfileChangeListeners() {
-        return mDpChangeListeners;
-    }
-
-    @Override
     public DeviceProfile getDeviceProfile() {
         return mProfile;
     }
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt b/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt
new file mode 100644
index 0000000..b66a9d3
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/DaggerGraphs.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import com.android.launcher3.FakeLauncherPrefs
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.dagger.ApiWrapperModule
+import com.android.launcher3.dagger.AppModule
+import com.android.launcher3.dagger.StaticObjectModule
+import com.android.launcher3.dagger.WindowManagerProxyModule
+import dagger.Binds
+import dagger.Module
+
+private class DaggerGraphs {}
+
+@Module
+abstract class FakePrefsModule {
+    @Binds abstract fun bindLauncherPrefs(prefs: FakeLauncherPrefs): LauncherPrefs
+}
+
+/** All modules. We also exclude the plugin module from tests */
+@Module(
+    includes =
+        [
+            ApiWrapperModule::class,
+            WindowManagerProxyModule::class,
+            StaticObjectModule::class,
+            AppModule::class,
+        ]
+)
+class AllModulesForTest
+
+/** All modules except the WMProxy */
+@Module(includes = [ApiWrapperModule::class, StaticObjectModule::class, AppModule::class])
+class AllModulesMinusWMProxy
+
+/** All modules except the ApiWrapper */
+@Module(includes = [WindowManagerProxyModule::class, StaticObjectModule::class, AppModule::class])
+class AllModulesMinusApiWrapper
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
index a3a680e..0ecb38e 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/DisplayControllerTest.kt
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.util
 
-import android.content.Context
 import android.content.res.Configuration
 import android.content.res.Resources
 import android.graphics.Point
@@ -26,18 +25,23 @@
 import android.view.Display
 import android.view.Surface
 import androidx.test.annotation.UiThreadTest
-import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.SmallTest
 import com.android.launcher3.LauncherPrefs
 import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
 import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.util.DisplayController.CHANGE_DENSITY
+import com.android.launcher3.util.DisplayController.CHANGE_DESKTOP_MODE
 import com.android.launcher3.util.DisplayController.CHANGE_ROTATION
+import com.android.launcher3.util.DisplayController.CHANGE_SHOW_LOCKED_TASKBAR
 import com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING
 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
 import com.android.launcher3.util.window.CachedDisplayInfo
 import com.android.launcher3.util.window.WindowManagerProxy
+import dagger.BindsInstance
+import dagger.Component
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import kotlin.math.min
@@ -51,6 +55,7 @@
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 import org.mockito.stubbing.Answer
@@ -60,12 +65,10 @@
 @RunWith(LauncherMultivalentJUnit::class)
 class DisplayControllerTest {
 
-    private val appContext: Context = ApplicationProvider.getApplicationContext()
-
-    private val context: SandboxContext = mock()
-    private val windowManagerProxy: WindowManagerProxy = mock()
+    private val context = spy(SandboxModelContext())
+    private val windowManagerProxy: MyWmProxy = mock()
     private val launcherPrefs: LauncherPrefs = mock()
-    private val displayManager: DisplayManager = mock()
+    private lateinit var displayManager: DisplayManager
     private val display: Display = mock()
     private val resources: Resources = mock()
     private val displayInfoChangeListener: DisplayInfoChangeListener = mock()
@@ -85,7 +88,7 @@
             WindowBounds(Rect(0, 0, height, width), Rect(0, inset, 0, 0), Surface.ROTATION_270),
         )
     private val configuration =
-        Configuration(appContext.resources.configuration).apply {
+        Configuration(context.resources.configuration).apply {
             densityDpi = this@DisplayControllerTest.densityDpi
             screenWidthDp = (bounds[0].bounds.width() / density).toInt()
             screenHeightDp = (bounds[0].bounds.height() / density).toInt()
@@ -94,8 +97,13 @@
 
     @Before
     fun setUp() {
-        whenever(context.getObject(eq(WindowManagerProxy.INSTANCE))).thenReturn(windowManagerProxy)
-        whenever(context.getObject(eq(LauncherPrefs.INSTANCE))).thenReturn(launcherPrefs)
+        context.initDaggerComponent(
+            DaggerDisplayControllerTestComponent.builder()
+                .bindWMProxy(windowManagerProxy)
+                .bindLauncherPrefs(launcherPrefs)
+        )
+        displayManager = context.spyService(DisplayManager::class.java)
+
         whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
         whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(true)
 
@@ -118,15 +126,13 @@
 
         whenever(windowManagerProxy.getNavigationMode(any())).thenReturn(NavigationMode.NO_BUTTON)
         // Mock context
-        whenever(context.createWindowContext(any(), any(), anyOrNull())).thenReturn(context)
-        whenever(context.getSystemService(eq(DisplayManager::class.java)))
-            .thenReturn(displayManager)
+        doReturn(context).whenever(context).createWindowContext(any(), any(), anyOrNull())
         doNothing().whenever(context).registerComponentCallbacks(any())
 
         // Mock display
         whenever(display.rotation).thenReturn(displayInfo.rotation)
-        whenever(context.display).thenReturn(display)
-        whenever(displayManager.getDisplay(any())).thenReturn(display)
+        doReturn(display).whenever(context).display
+        doReturn(display).whenever(displayManager).getDisplay(any())
 
         // Mock resources
         doReturn(context).whenever(context).applicationContext
@@ -134,7 +140,7 @@
         whenever(context.resources).thenReturn(resources)
 
         // Initialize DisplayController
-        displayController = DisplayController(context)
+        displayController = DisplayController.INSTANCE.get(context)
         displayController.addChangeListener(displayInfoChangeListener)
     }
 
@@ -143,6 +149,7 @@
         // We need to reset the taskbar mode preference override even if a test throws an exception.
         // Otherwise, it may break the following tests' assumptions.
         DisplayController.enableTaskbarModePreferenceForTests(false)
+        context.onDestroy()
     }
 
     @Test
@@ -197,14 +204,19 @@
     fun testTaskbarPinningChangeInLockedTaskbarChange() {
         whenever(windowManagerProxy.showLockedTaskbarOnHome(any())).thenReturn(true)
         whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(true)
-        whenever(windowManagerProxy.isInDesktopMode()).thenReturn(false)
+        whenever(windowManagerProxy.isInDesktopMode(any())).thenReturn(false)
         whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
         DisplayController.enableTaskbarModePreferenceForTests(true)
 
         assertTrue(displayController.getInfo().isTransientTaskbar())
         displayController.notifyConfigChange()
+
         verify(displayInfoChangeListener)
-            .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
+            .onDisplayInfoChanged(
+                any(),
+                any(),
+                eq(CHANGE_TASKBAR_PINNING or CHANGE_SHOW_LOCKED_TASKBAR),
+            )
         assertFalse(displayController.getInfo().isTransientTaskbar())
     }
 
@@ -213,7 +225,7 @@
     fun testLockedTaskbarChangeOnConfigurationChanged() {
         whenever(windowManagerProxy.showLockedTaskbarOnHome(any())).thenReturn(true)
         whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(true)
-        whenever(windowManagerProxy.isInDesktopMode()).thenReturn(false)
+        whenever(windowManagerProxy.isInDesktopMode(any())).thenReturn(false)
         whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
         DisplayController.enableTaskbarModePreferenceForTests(true)
         assertTrue(displayController.getInfo().isTransientTaskbar())
@@ -221,7 +233,84 @@
         displayController.onConfigurationChanged(configuration)
 
         verify(displayInfoChangeListener)
+            .onDisplayInfoChanged(
+                any(),
+                any(),
+                eq(CHANGE_TASKBAR_PINNING or CHANGE_SHOW_LOCKED_TASKBAR),
+            )
+        assertFalse(displayController.getInfo().isTransientTaskbar())
+    }
+
+    @Test
+    @UiThreadTest
+    fun testTaskbarPinnedForDesktopTaskbar_inDesktopMode() {
+        whenever(windowManagerProxy.showDesktopTaskbarForFreeformDisplay(any())).thenReturn(true)
+        whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
+        whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(false)
+        whenever(windowManagerProxy.isInDesktopMode(any())).thenReturn(true)
+        whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(false)
+        DisplayController.enableTaskbarModePreferenceForTests(true)
+
+        assertTrue(displayController.getInfo().isTransientTaskbar())
+
+        displayController.onConfigurationChanged(configuration)
+
+        verify(displayInfoChangeListener)
+            .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING or CHANGE_DESKTOP_MODE))
+        assertFalse(displayController.getInfo().isTransientTaskbar())
+    }
+
+    @Test
+    @UiThreadTest
+    fun testTaskbarPinnedForDesktopTaskbar_notInDesktopMode() {
+        whenever(windowManagerProxy.showDesktopTaskbarForFreeformDisplay(any())).thenReturn(true)
+        whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
+        whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(false)
+        whenever(windowManagerProxy.isInDesktopMode(any())).thenReturn(false)
+        whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(false)
+        DisplayController.enableTaskbarModePreferenceForTests(true)
+
+        assertTrue(displayController.getInfo().isTransientTaskbar())
+
+        displayController.onConfigurationChanged(configuration)
+
+        verify(displayInfoChangeListener)
             .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
         assertFalse(displayController.getInfo().isTransientTaskbar())
     }
+
+    @Test
+    @UiThreadTest
+    fun testTaskbarPinnedForDesktopTaskbar_onHome() {
+        whenever(windowManagerProxy.showDesktopTaskbarForFreeformDisplay(any())).thenReturn(true)
+        whenever(launcherPrefs.get(TASKBAR_PINNING)).thenReturn(false)
+        whenever(launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)).thenReturn(false)
+        whenever(windowManagerProxy.isInDesktopMode(any())).thenReturn(false)
+        whenever(windowManagerProxy.isHomeVisible(any())).thenReturn(true)
+        DisplayController.enableTaskbarModePreferenceForTests(true)
+
+        assertTrue(displayController.getInfo().isTransientTaskbar())
+
+        displayController.onConfigurationChanged(configuration)
+
+        verify(displayInfoChangeListener)
+            .onDisplayInfoChanged(any(), any(), eq(CHANGE_TASKBAR_PINNING))
+        assertFalse(displayController.getInfo().isTransientTaskbar())
+    }
+}
+
+class MyWmProxy : WindowManagerProxy()
+
+@LauncherAppSingleton
+@Component(modules = [AllModulesMinusWMProxy::class])
+interface DisplayControllerTestComponent : LauncherAppComponent {
+
+    @Component.Builder
+    interface Builder : LauncherAppComponent.Builder {
+        @BindsInstance fun bindWMProxy(proxy: WindowManagerProxy): Builder
+
+        @BindsInstance fun bindLauncherPrefs(prefs: LauncherPrefs): Builder
+
+        override fun build(): DisplayControllerTestComponent
+    }
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/GlobalTestRunListener.java b/tests/multivalentTests/src/com/android/launcher3/util/GlobalTestRunListener.java
new file mode 100644
index 0000000..667f540
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/GlobalTestRunListener.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util;
+
+import org.junit.runner.Description;
+import org.junit.runner.notification.RunListener;
+import org.mockito.Mockito;
+
+public class GlobalTestRunListener extends RunListener {
+    /**
+     * See {@link RunListener#testFinished} which executes per atomic test.
+     * {@link RunListener#testSuiteFinished} which executes per test suite. Test suite = test class
+     * in this context.
+     * {@link RunListener#testRunFinished} which executes after everything (all test suites) is
+     * completed.
+     */
+    @Override
+    public void testSuiteFinished(Description description) throws Exception {
+        // This method runs after every test class and will clear mocks after every test class
+        // execution is completed.
+        Mockito.framework().clearInlineMocks();
+        super.testSuiteFinished(description);
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherBindableItemsContainerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/LauncherBindableItemsContainerTest.kt
new file mode 100644
index 0000000..054c90b
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherBindableItemsContainerTest.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.content.ComponentName
+import android.content.pm.LauncherApps
+import android.graphics.Bitmap
+import android.graphics.Bitmap.Config.ARGB_8888
+import android.os.Process.myUserHandle
+import android.platform.uiautomatorhelpers.DeviceHelpers.context
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.BubbleTextView
+import com.android.launcher3.graphics.PreloadIconDrawable
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.FastBitmapDrawable
+import com.android.launcher3.icons.PlaceHolderIconDrawable
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.AppInfo.makeLaunchIntent
+import com.android.launcher3.model.data.ItemInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.pm.PackageInstallInfo
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator
+import com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY
+import com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY2
+import com.android.launcher3.util.LauncherModelHelper.TEST_ACTIVITY3
+import com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LauncherBindableItemsContainerTest {
+
+    private val icon1 by lazy { getLAI(TEST_ACTIVITY) }
+    private val icon2 by lazy { getLAI(TEST_ACTIVITY2) }
+    private val icon3 by lazy { getLAI(TEST_ACTIVITY3) }
+
+    private val container = TestContainer()
+
+    @Test
+    fun `icon bitmap is updated`() {
+        container.addIcon(icon1)
+        container.addIcon(icon2)
+        container.addIcon(icon3)
+
+        assertThat(container.getAppIcon(icon1).icon)
+            .isInstanceOf(PlaceHolderIconDrawable::class.java)
+        assertThat(container.getAppIcon(icon2).icon)
+            .isInstanceOf(PlaceHolderIconDrawable::class.java)
+        assertThat(container.getAppIcon(icon3).icon)
+            .isInstanceOf(PlaceHolderIconDrawable::class.java)
+
+        icon2.bitmap = BitmapInfo.fromBitmap(Bitmap.createBitmap(200, 200, ARGB_8888))
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {
+            container.updateContainerItems(setOf(icon2), container)
+        }
+
+        assertThat(container.getAppIcon(icon1).icon)
+            .isInstanceOf(PlaceHolderIconDrawable::class.java)
+        assertThat(container.getAppIcon(icon3).icon)
+            .isInstanceOf(PlaceHolderIconDrawable::class.java)
+        assertThat(container.getAppIcon(icon2).icon)
+            .isNotInstanceOf(PlaceHolderIconDrawable::class.java)
+        assertThat(container.getAppIcon(icon2).icon).isInstanceOf(FastBitmapDrawable::class.java)
+    }
+
+    @Test
+    fun `icon download progress updated`() {
+        container.addIcon(icon1)
+        container.addIcon(icon2)
+        assertThat(container.getAppIcon(icon1).icon)
+            .isInstanceOf(PlaceHolderIconDrawable::class.java)
+        assertThat(container.getAppIcon(icon2).icon)
+            .isInstanceOf(PlaceHolderIconDrawable::class.java)
+
+        icon1.status = WorkspaceItemInfo.FLAG_RESTORED_ICON
+        icon1.bitmap = BitmapInfo.fromBitmap(Bitmap.createBitmap(200, 200, ARGB_8888))
+        icon1.setProgressLevel(30, PackageInstallInfo.STATUS_INSTALLING)
+        TestUtil.runOnExecutorSync(MAIN_EXECUTOR) {
+            container.updateContainerItems(setOf(icon1), container)
+        }
+
+        assertThat(container.getAppIcon(icon2).icon)
+            .isInstanceOf(PlaceHolderIconDrawable::class.java)
+        assertThat(container.getAppIcon(icon1).icon).isInstanceOf(PreloadIconDrawable::class.java)
+        val oldIcon = container.getAppIcon(icon1).icon as PreloadIconDrawable
+        assertThat(oldIcon.level).isEqualTo(30)
+    }
+
+    private fun getLAI(className: String): WorkspaceItemInfo =
+        AppInfo(
+                context,
+                context
+                    .getSystemService(LauncherApps::class.java)!!
+                    .resolveActivity(
+                        makeLaunchIntent(ComponentName(TEST_PACKAGE, className)),
+                        myUserHandle(),
+                    )!!,
+                myUserHandle(),
+            )
+            .makeWorkspaceItem(context)
+
+    class TestContainer : ActivityContextWrapper(context), LauncherBindableItemsContainer {
+
+        val items = mutableMapOf<ItemInfo, View>()
+
+        override fun mapOverItems(op: ItemOperator): View? =
+            items.firstNotNullOfOrNull { (item, view) ->
+                if (op.evaluate(item, view)) view else null
+            }
+
+        fun addIcon(info: WorkspaceItemInfo) {
+            val btv = BubbleTextView(this)
+            btv.applyFromWorkspaceItem(info)
+            items[info] = btv
+        }
+
+        fun getAppIcon(info: WorkspaceItemInfo) = items[info] as BubbleTextView
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
index 09b9a3b..4458e8f 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -21,8 +21,8 @@
 
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
-import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
 import static com.android.launcher3.util.TestUtil.grantWriteSecurePermission;
+import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
 
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -51,11 +51,11 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel;
+import com.android.launcher3.dagger.LauncherBaseAppComponent;
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.ModelDbController;
 import com.android.launcher3.testing.TestInformationProvider;
-import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
@@ -267,14 +267,6 @@
         }
 
         @Override
-        public <T extends SafeCloseable> T createObject(MainThreadInitializedObject<T> object) {
-            if (object == LauncherAppState.INSTANCE) {
-                return (T) new LauncherAppState(this, null /* iconCacheFileName */);
-            }
-            return super.createObject(object);
-        }
-
-        @Override
         public File getDatabasePath(String name) {
             if (!mDbDir.exists()) {
                 mDbDir.mkdirs();
@@ -342,5 +334,10 @@
             }
             return success;
         }
+
+        @Override
+        public void initDaggerComponent(LauncherBaseAppComponent.Builder componentBuilder) {
+            super.initDaggerComponent(componentBuilder.iconsDbName(null));
+        }
     }
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt
index 2711d7a..99f5a5b 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/LockedUserStateTest.kt
@@ -22,7 +22,10 @@
 import android.os.UserManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
 import com.google.common.truth.Truth.assertThat
+import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -38,17 +41,24 @@
 
     private val userManager: UserManager = mock()
     private val context: Context = mock()
+    private val lifeCycle: DaggerSingletonTracker = mock()
 
     @Before
     fun setup() {
         whenever(context.getSystemService(UserManager::class.java)).thenReturn(userManager)
     }
 
+    @After
+    fun tearDown() {
+        UI_HELPER_EXECUTOR.submit {}.get()
+        MAIN_EXECUTOR.submit {}.get()
+    }
+
     @Test
     fun runOnUserUnlocked_runs_action_immediately_if_already_unlocked() {
         whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
         val action: Runnable = mock()
-        LockedUserState(context).runOnUserUnlocked(action)
+        LockedUserState(context, lifeCycle).runOnUserUnlocked(action)
         verify(action).run()
     }
 
@@ -56,23 +66,23 @@
     fun runOnUserUnlocked_waits_to_run_action_until_user_is_unlocked() {
         whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
         val action: Runnable = mock()
-        val state = LockedUserState(context)
+        val state = LockedUserState(context, lifeCycle)
         state.runOnUserUnlocked(action)
         // b/343530737
         verifyNoMoreInteractions(action)
-        state.mUserUnlockedReceiver.onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED))
+        state.userUnlockedReceiver.onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED))
         verify(action).run()
     }
 
     @Test
     fun isUserUnlocked_returns_true_when_user_is_unlocked() {
         whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
-        assertThat(LockedUserState(context).isUserUnlocked).isTrue()
+        assertThat(LockedUserState(context, lifeCycle).isUserUnlocked).isTrue()
     }
 
     @Test
     fun isUserUnlocked_returns_false_when_user_is_locked() {
         whenever(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
-        assertThat(LockedUserState(context).isUserUnlocked).isFalse()
+        assertThat(LockedUserState(context, lifeCycle).isUserUnlocked).isFalse()
     }
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt b/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
index 8d072d8..ceefb0d 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ModelTestExtensions.kt
@@ -1,6 +1,7 @@
 package com.android.launcher3.util
 
 import android.content.ContentValues
+import android.os.Process
 import com.android.launcher3.Flags
 import com.android.launcher3.LauncherModel
 import com.android.launcher3.LauncherSettings.Favorites
@@ -31,8 +32,9 @@
         loadModelSync()
         TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
             modelDbController.run {
-                if (Flags.gridMigrationRefactor()) attemptMigrateDb(null /* restoreEventLogger */)
-                else tryMigrateDB(null /* restoreEventLogger */)
+                if (Flags.gridMigrationRefactor())
+                    attemptMigrateDb(null /* restoreEventLogger */, modelDelegate)
+                else tryMigrateDB(null /* restoreEventLogger */, modelDelegate)
                 createEmptyDB()
                 clearEmptyDbFlag()
             }
@@ -65,7 +67,7 @@
         spanX: Int = 1,
         spanY: Int = 1,
         id: Int = 0,
-        profileId: Int = 0,
+        profileId: Int = Process.myUserHandle().identifier,
         tableName: String = Favorites.TABLE_NAME,
         appWidgetId: Int = -1,
         appWidgetSource: Int = -1,
@@ -74,7 +76,7 @@
         loadModelSync()
         TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
             val controller: ModelDbController = modelDbController
-            controller.attemptMigrateDb(null /* restoreEventLogger */)
+            controller.attemptMigrateDb(null /* restoreEventLogger */, modelDelegate)
             modelDbController.newTransaction().use { transaction ->
                 val values =
                     ContentValues().apply {
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
index efe7637..2fa4cad 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplication.kt
@@ -27,7 +27,6 @@
 import android.view.Display
 import androidx.test.core.app.ApplicationProvider
 import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
-import com.android.launcher3.util.MainThreadInitializedObject.ObjectSandbox
 import org.junit.Rule
 import org.junit.rules.ExternalResource
 import org.junit.rules.TestRule
@@ -48,6 +47,7 @@
 class SandboxApplication private constructor(private val base: SandboxApplicationWrapper) :
     SandboxModelContext(base), TestRule {
 
+    @JvmOverloads
     constructor(
         base: Context = ApplicationProvider.getApplicationContext()
     ) : this(SandboxApplicationWrapper(base))
@@ -68,7 +68,7 @@
         // Defer to the true application to decide whether to clean up. For instance, we do not want
         // to cleanup under Robolectric.
         val app = ApplicationProvider.getApplicationContext<Context>()
-        return if (app is ObjectSandbox) app.shouldCleanUpOnDestroy() else true
+        return (app as? SandboxContext)?.shouldCleanUpOnDestroy() ?: true
     }
 
     override fun apply(statement: Statement, description: Description): Statement {
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt
index d87a406..8a21cff 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SandboxApplicationTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.util
 
-import android.content.Context
 import android.hardware.display.DisplayManager
 import android.view.Display
 import android.view.Display.DEFAULT_DISPLAY
@@ -63,16 +62,4 @@
             onDestroy()
         }
     }
-
-    @Test
-    fun testGetObject_objectCreatesDisplayContext_isSandboxed() {
-        class TestSingleton(context: Context) : SafeCloseable {
-            override fun close() = Unit
-
-            val displayContext = context.createDisplayContext(display)
-        }
-
-        val displayContext = MainThreadInitializedObject { TestSingleton(it) }[app].displayContext
-        assertThat(displayContext.applicationContext).isEqualTo(app)
-    }
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt
index 45cc19c..9c3f223 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/ScreenOnTrackerTest.kt
@@ -51,7 +51,7 @@
 
     @Test
     fun test_default_state() {
-        verify(receiver).register(context, ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT)
+        verify(receiver).register(ACTION_SCREEN_ON, ACTION_SCREEN_OFF, ACTION_USER_PRESENT)
         assertThat(underTest.isScreenOn).isTrue()
     }
 
@@ -59,7 +59,7 @@
     fun close_unregister_receiver() {
         underTest.close()
 
-        verify(receiver).unregisterReceiverSafely(context)
+        verify(receiver).unregisterReceiverSafely()
     }
 
     @Test
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt b/tests/multivalentTests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt
index d3e27b6..17933f2 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/util/SimpleBroadcastReceiverTest.kt
@@ -52,7 +52,7 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        underTest = SimpleBroadcastReceiver(UI_HELPER_EXECUTOR, intentConsumer)
+        underTest = SimpleBroadcastReceiver(context, UI_HELPER_EXECUTOR, intentConsumer)
         if (Looper.getMainLooper() == null) {
             Looper.prepareMainLooper()
         }
@@ -60,7 +60,7 @@
 
     @Test
     fun async_register() {
-        underTest.register(context, "test_action_1", "test_action_2")
+        underTest.register("test_action_1", "test_action_2")
         awaitTasksCompleted()
 
         verify(context).registerReceiver(same(underTest), intentFilterCaptor.capture())
@@ -72,7 +72,7 @@
 
     @Test
     fun async_register_withCompletionRunnable() {
-        underTest.register(context, completionRunnable, "test_action_1", "test_action_2")
+        underTest.register(completionRunnable, "test_action_1", "test_action_2")
         awaitTasksCompleted()
 
         verify(context).registerReceiver(same(underTest), intentFilterCaptor.capture())
@@ -85,7 +85,7 @@
 
     @Test
     fun async_register_withCompletionRunnable_and_flag() {
-        underTest.register(context, completionRunnable, 1, "test_action_1", "test_action_2")
+        underTest.register(completionRunnable, 1, "test_action_1", "test_action_2")
         awaitTasksCompleted()
 
         verify(context).registerReceiver(same(underTest), intentFilterCaptor.capture(), eq(1))
@@ -98,7 +98,7 @@
 
     @Test
     fun async_register_with_package() {
-        underTest.registerPkgActions(context, "pkg", "test_action_1", "test_action_2")
+        underTest.registerPkgActions("pkg", "test_action_1", "test_action_2")
 
         awaitTasksCompleted()
         verify(context).registerReceiver(same(underTest), intentFilterCaptor.capture())
@@ -112,9 +112,10 @@
 
     @Test
     fun sync_register_withCompletionRunnable_and_flag() {
-        underTest = SimpleBroadcastReceiver(Handler(Looper.getMainLooper()), intentConsumer)
+        underTest =
+            SimpleBroadcastReceiver(context, Handler(Looper.getMainLooper()), intentConsumer)
 
-        underTest.register(context, completionRunnable, 1, "test_action_1", "test_action_2")
+        underTest.register(completionRunnable, 1, "test_action_1", "test_action_2")
         getInstrumentation().waitForIdleSync()
 
         verify(context).registerReceiver(same(underTest), intentFilterCaptor.capture(), eq(1))
@@ -127,7 +128,7 @@
 
     @Test
     fun async_unregister() {
-        underTest.unregisterReceiverSafely(context)
+        underTest.unregisterReceiverSafely()
 
         awaitTasksCompleted()
         verify(context).unregisterReceiver(same(underTest))
@@ -135,9 +136,10 @@
 
     @Test
     fun sync_unregister() {
-        underTest = SimpleBroadcastReceiver(Handler(Looper.getMainLooper()), intentConsumer)
+        underTest =
+            SimpleBroadcastReceiver(context, Handler(Looper.getMainLooper()), intentConsumer)
 
-        underTest.unregisterReceiverSafely(context)
+        underTest.unregisterReceiverSafely()
         getInstrumentation().waitForIdleSync()
 
         verify(context).unregisterReceiver(same(underTest))
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java b/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
index 71637f1..acd17d1 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/TestSandboxModelContextWrapper.java
@@ -18,10 +18,9 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
-
 import android.content.ContextWrapper;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -29,6 +28,7 @@
 import com.android.launcher3.allapps.AllAppsStore;
 import com.android.launcher3.allapps.AlphabeticalAppsList;
 import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.model.WidgetsFilterDataProvider;
 import com.android.launcher3.model.data.AppInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.popup.PopupDataProvider;
@@ -44,7 +44,7 @@
  * There are 2 constructors in this class. The base context can be {@link SandboxContext} or
  * Instrumentation target context.
  * Using {@link SandboxContext} as base context allows custom implementations for
- * MainThreadInitializedObject providers.
+ * providing objects in Dagger components.
  */
 
 public class TestSandboxModelContextWrapper extends ActivityContextWrapper implements
@@ -57,14 +57,14 @@
 
     protected ActivityAllAppsContainerView<ActivityContextWrapper> mAppsView;
 
-    private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
+    private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(this);
     private final WidgetPickerDataProvider mWidgetPickerDataProvider =
-            new WidgetPickerDataProvider();
+            new WidgetPickerDataProvider(new WidgetsFilterDataProvider());
     protected final UserCache mUserCache;
 
     public TestSandboxModelContextWrapper(SandboxContext base) {
         super(base);
-        mUserCache = base.getObject(UserCache.INSTANCE);
+        mUserCache = UserCache.getInstance(base);
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() ->
                 mAppsView = new ActivityAllAppsContainerView<>(this));
         mAppsList = mAppsView.getPersonalAppList();
@@ -80,7 +80,7 @@
         mAllAppsStore = mAppsView.getAppsStore();
     }
 
-    @Nullable
+    @NonNull
     @Override
     public PopupDataProvider getPopupDataProvider() {
         return mPopupDataProvider;
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
index deb0ef3..9fbd7ff 100644
--- a/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
+++ b/tests/multivalentTests/src/com/android/launcher3/util/WidgetUtils.java
@@ -15,12 +15,15 @@
  */
 package com.android.launcher3.util;
 
+import static android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_HIDE_FROM_PICKER;
+
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.os.Bundle;
+import android.os.Process;
 
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -82,13 +85,30 @@
 
     /**
      * Creates a {@link AppWidgetProviderInfo} for the provided component name
+     *
+     * @param cn component name of the appwidget provider
+     * @param hideFromPicker indicates if the widget should appear in widget picker
      */
-    public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
+    public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn,
+            boolean hideFromPicker) {
         ActivityInfo activityInfo = new ActivityInfo();
         activityInfo.applicationInfo = new ApplicationInfo();
+        activityInfo.applicationInfo.uid = Process.myUid();
         AppWidgetProviderInfo info = new AppWidgetProviderInfo();
+        if (hideFromPicker) {
+            info.widgetFeatures = WIDGET_FEATURE_HIDE_FROM_PICKER;
+        }
         info.providerInfo = activityInfo;
         info.provider = cn;
         return info;
     }
+
+    /**
+     * Creates a {@link AppWidgetProviderInfo} for the provided component name
+     *
+     * @param cn component name of the appwidget provider
+     */
+    public static AppWidgetProviderInfo createAppWidgetProviderInfo(ComponentName cn) {
+        return createAppWidgetProviderInfo(cn, /*hideFromPicker=*/ false);
+    }
 }
diff --git a/tests/multivalentTests/src/com/android/launcher3/util/rule/ZipFilesRule.kt b/tests/multivalentTests/src/com/android/launcher3/util/rule/ZipFilesRule.kt
new file mode 100644
index 0000000..d12de76
--- /dev/null
+++ b/tests/multivalentTests/src/com/android/launcher3/util/rule/ZipFilesRule.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util.rule
+
+import android.content.Context
+import android.os.FileUtils
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+class ZipFilesRule(val context: Context, val name: String) : TestRule {
+
+    var resultsZip: ZipOutputStream? = null
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return object : Statement() {
+            @Throws(Throwable::class)
+            override fun evaluate() {
+                resultsZip =
+                    ZipOutputStream(
+                        FileOutputStream(
+                            File(context.filesDir, "${description.testClass.simpleName}-$name.zip")
+                        )
+                    )
+                try {
+                    base.evaluate() // This will run the test.
+                } finally {
+                    resultsZip?.close()
+                }
+            }
+        }
+    }
+
+    fun write(file: File) {
+        if (resultsZip !is ZipOutputStream) {
+            throw RuntimeException(
+                "Cannot save files before the test rule starts! We need the rule to start to get the name of the test"
+            )
+        }
+        resultsZip!!.let {
+            it.putNextEntry(ZipEntry(file.name))
+            FileUtils.copy(FileInputStream(file), it)
+            it.closeEntry()
+        }
+    }
+}
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
index b92582c..6af0950 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/GeneratedPreviewTest.kt
@@ -17,7 +17,6 @@
 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.R
 import com.android.launcher3.icons.IconCache
 import com.android.launcher3.model.WidgetItem
@@ -100,7 +99,7 @@
 
     private fun createWidgetItem() {
         Executors.MODEL_EXECUTOR.submit {
-                val idp = InvariantDeviceProfile()
+                val idp = context.appComponent.idp
                 widgetItem = WidgetItem(appWidgetProviderInfo, idp, iconCache, context)
             }
             .get()
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
index 48cf3df..b3fd0f7 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/LauncherAppWidgetProviderInfoTest.java
@@ -15,15 +15,12 @@
  */
 package com.android.launcher3.widget;
 
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doAnswer;
 
 import android.appwidget.AppWidgetHostView;
-import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
 
@@ -32,16 +29,14 @@
 
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.util.SandboxApplication;
 
-import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.List;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -52,12 +47,7 @@
     private static final int NUM_OF_COLS = 4;
     private static final int NUM_OF_ROWS = 5;
 
-    private Context mContext;
-
-    @Before
-    public void setUp() {
-        mContext = getApplicationContext();
-    }
+    @Rule public SandboxApplication mContext = new SandboxApplication();
 
     @Test
     public void initSpans_minWidthSmallerThanCellWidth_shouldInitializeSpansToOne() {
@@ -256,8 +246,9 @@
     }
 
     private InvariantDeviceProfile createIDP() {
-        DeviceProfile dp = LauncherAppState.getIDP(mContext)
-                .getDeviceProfile(mContext).copy(mContext);
+        InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
+
+        DeviceProfile dp = idp.getDeviceProfile(mContext).copy(mContext);
         DeviceProfile profile = Mockito.spy(dp);
         doAnswer(i -> {
             ((Point) i.getArgument(0)).set(CELL_SIZE, CELL_SIZE);
@@ -267,10 +258,7 @@
         profile.cellLayoutBorderSpacePx = new Point(SPACE_SIZE, SPACE_SIZE);
         profile.widgetPadding.setEmpty();
 
-        InvariantDeviceProfile idp = new InvariantDeviceProfile();
-        List<DeviceProfile> supportedProfiles = new ArrayList<>(idp.supportedProfiles);
-        supportedProfiles.add(profile);
-        idp.supportedProfiles = Collections.unmodifiableList(supportedProfiles);
+        idp.supportedProfiles = Collections.singletonList(profile);
         idp.numColumns = NUM_OF_COLS;
         idp.numRows = NUM_OF_ROWS;
         return idp;
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
index 8b6553f..2fbeaf1 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetRecommendationCategoryProviderTest.java
@@ -25,8 +25,6 @@
 import static android.content.pm.ApplicationInfo.CATEGORY_VIDEO;
 import static android.content.pm.ApplicationInfo.FLAG_INSTALLED;
 
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -35,11 +33,9 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
 
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.LauncherApps;
 import android.os.Process;
@@ -53,12 +49,14 @@
 import com.android.launcher3.icons.IconCache;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.SandboxApplication;
 import com.android.launcher3.util.WidgetUtils;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 
 import com.google.common.collect.ImmutableMap;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -92,22 +90,22 @@
 
     private final ApplicationInfo mTestAppInfo = ApplicationInfoBuilder.newBuilder().setPackageName(
             TEST_PACKAGE).setName(TEST_APP_NAME).build();
-    private Context mContext;
+
+    @Rule public SandboxApplication mContext = new SandboxApplication();
     @Mock
     private IconCache mIconCache;
 
     private WidgetItem mTestWidgetItem;
-    @Mock
+
     private LauncherApps mLauncherApps;
     private InvariantDeviceProfile mTestProfile;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mContext = spy(getInstrumentation().getTargetContext());
-        doReturn(mLauncherApps).when(mContext).getSystemService(LauncherApps.class);
+        mLauncherApps = mContext.spyService(LauncherApps.class);
         mTestAppInfo.flags = FLAG_INSTALLED;
-        mTestProfile = new InvariantDeviceProfile();
+        mTestProfile = InvariantDeviceProfile.INSTANCE.get(mContext);
         mTestProfile.numRows = 5;
         mTestProfile.numColumns = 5;
         createTestWidgetItem();
@@ -128,10 +126,10 @@
                 testCategories.entrySet()) {
 
             mTestAppInfo.category = testCategory.getKey();
-            when(mLauncherApps.getApplicationInfo(/*packageName=*/ eq(TEST_PACKAGE),
+            doReturn(mTestAppInfo).when(mLauncherApps).getApplicationInfo(
+                    /*packageName=*/ eq(TEST_PACKAGE),
                     /*flags=*/ anyInt(),
-                    /*user=*/ eq(Process.myUserHandle())))
-                    .thenReturn(mTestAppInfo);
+                    /*user=*/ eq(Process.myUserHandle()));
 
             WidgetRecommendationCategory category = Executors.MODEL_EXECUTOR.submit(() ->
                     new WidgetRecommendationCategoryProvider().getWidgetRecommendationCategory(
@@ -148,11 +146,12 @@
 
         doAnswer(invocation -> widgetLabel).when(mIconCache).getTitleNoCache(any());
 
-        AppWidgetProviderInfo providerInfo = WidgetUtils.createAppWidgetProviderInfo(ComponentName
-                .createRelative(TEST_PACKAGE, widgetClassName));
+        AppWidgetProviderInfo providerInfo = WidgetUtils.createAppWidgetProviderInfo(
+                ComponentName.createRelative(TEST_PACKAGE, widgetClassName));
 
         LauncherAppWidgetProviderInfo launcherAppWidgetProviderInfo =
-                LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, providerInfo);
+                spy(LauncherAppWidgetProviderInfo.fromProviderInfo(mContext, providerInfo));
+        doReturn(Process.myUserHandle()).when(launcherAppWidgetProviderInfo).getProfile();
         launcherAppWidgetProviderInfo.spanX = 2;
         launcherAppWidgetProviderInfo.spanY = 2;
         launcherAppWidgetProviderInfo.label = widgetLabel;
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
index c9b6d4f..767ab63 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.widget.picker;
 
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -48,11 +46,13 @@
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.util.ActivityContextWrapper;
 import com.android.launcher3.util.PackageUserKey;
+import com.android.launcher3.util.SandboxApplication;
 import com.android.launcher3.util.WidgetUtils;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -67,6 +67,7 @@
     private static final String TEST_PACKAGE = "com.google.test";
     private static final String APP_NAME = "Test app";
 
+    @Rule public SandboxApplication app = new SandboxApplication();
     private Context mContext;
     private WidgetsListHeaderViewHolderBinder mViewHolderBinder;
     private InvariantDeviceProfile mTestProfile;
@@ -80,9 +81,9 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mContext = new ActivityContextWrapper(new ContextThemeWrapper(getApplicationContext(),
-                R.style.WidgetContainerTheme));
-        mTestProfile = new InvariantDeviceProfile();
+        mContext = new ActivityContextWrapper(new ContextThemeWrapper(
+                app, R.style.WidgetContainerTheme));
+        mTestProfile = InvariantDeviceProfile.INSTANCE.get(app);
         mTestProfile.numRows = 5;
         mTestProfile.numColumns = 5;
 
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
index 86bbcc1..e6f13a6 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java
@@ -15,7 +15,6 @@
  */
 package com.android.launcher3.widget.picker;
 
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -50,6 +49,7 @@
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.util.ActivityContextWrapper;
+import com.android.launcher3.util.SandboxApplication;
 import com.android.launcher3.util.WidgetUtils;
 import com.android.launcher3.widget.DatabaseWidgetPreviewLoader;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
@@ -57,6 +57,7 @@
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -71,6 +72,7 @@
     private static final String TEST_PACKAGE = "com.google.test";
     private static final String APP_NAME = "Test app";
 
+    @Rule public SandboxApplication app = new SandboxApplication();
     private Context mContext;
     private WidgetsListTableViewHolderBinder mViewHolderBinder;
     private InvariantDeviceProfile mTestProfile;
@@ -85,8 +87,8 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mContext = new ActivityContextWrapper(getApplicationContext());
-        mTestProfile = new InvariantDeviceProfile();
+        mContext = new ActivityContextWrapper(app);
+        mTestProfile = InvariantDeviceProfile.INSTANCE.get(app);
         mTestProfile.numRows = 5;
         mTestProfile.numColumns = 5;
 
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProviderTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProviderTest.kt
index 1da74cb..c1827bc 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProviderTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetPickerDataProviderTest.kt
@@ -40,6 +40,7 @@
 import com.android.launcher3.widget.model.WidgetsListHeaderEntry
 import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider.WidgetPickerDataChangeListener
 import com.google.common.truth.Truth.assertThat
+import org.junit.After
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -72,14 +73,14 @@
 
     private lateinit var appWidgetItem: WidgetItem
 
-    private var underTest = WidgetPickerDataProvider()
+    private lateinit var underTest: WidgetPickerDataProvider
 
     @Before
     fun setUp() {
         userHandle = UserHandle.CURRENT
         context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
         testInvariantProfile = LauncherAppState.getIDP(context)
-
+        underTest = WidgetPickerDataProvider(context)
         doAnswer { invocation: InvocationOnMock ->
                 val componentWithLabel = invocation.getArgument<Any>(0) as CachedObject
                 componentWithLabel.getComponent().shortClassName
@@ -90,6 +91,11 @@
         appWidgetItem = createWidgetItem()
     }
 
+    @After
+    fun tearDown() {
+        underTest.destroy()
+    }
+
     @Test
     fun setWidgets_invokesTheListener_andUpdatedWidgetsAvailable() {
         assertThat(underTest.get().allWidgets).isEmpty()
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
index 6088c8e..bd34de6 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/model/WidgetsListContentEntryTest.java
@@ -37,10 +37,12 @@
 import com.android.launcher3.icons.cache.CachedObject;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.PackageItemInfo;
+import com.android.launcher3.util.SandboxApplication;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -64,6 +66,7 @@
     private final ComponentName mWidget3 = ComponentName.createRelative(PACKAGE_NAME, ".mWidget3");
     private final Map<ComponentName, String> mWidgetsToLabels = new HashMap();
 
+    @Rule public SandboxApplication app = new SandboxApplication();
     @Mock private IconCache mIconCache;
 
     private InvariantDeviceProfile mTestProfile;
@@ -76,7 +79,7 @@
         mWidgetsToLabels.put(mWidget2, "Dog");
         mWidgetsToLabels.put(mWidget3, "Bird");
 
-        mTestProfile = new InvariantDeviceProfile();
+        mTestProfile = InvariantDeviceProfile.INSTANCE.get(app);
         mTestProfile.numRows = 5;
         mTestProfile.numColumns = 5;
 
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
index 59f352b..0cdda3a 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/search/SimpleWidgetsSearchAlgorithmTest.java
@@ -46,6 +46,7 @@
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.model.data.PackageItemInfo;
 import com.android.launcher3.search.SearchCallback;
+import com.android.launcher3.util.SandboxApplication;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.model.WidgetsListBaseEntry;
 import com.android.launcher3.widget.model.WidgetsListContentEntry;
@@ -53,6 +54,7 @@
 import com.android.launcher3.widget.picker.search.WidgetsSearchBar.WidgetsSearchDataProvider;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -66,6 +68,7 @@
 @RunWith(AndroidJUnit4.class)
 public class SimpleWidgetsSearchAlgorithmTest {
 
+    @Rule public SandboxApplication app = new SandboxApplication();
     @Mock private IconCache mIconCache;
 
     private InvariantDeviceProfile mTestProfile;
@@ -90,7 +93,7 @@
             CachedObject componentWithLabel = invocation.getArgument(0);
             return componentWithLabel.getComponent().getShortClassName();
         }).when(mIconCache).getTitleNoCache(any());
-        mTestProfile = new InvariantDeviceProfile();
+        mTestProfile = InvariantDeviceProfile.INSTANCE.get(app);
         mTestProfile.numRows = 5;
         mTestProfile.numColumns = 5;
         mContext = getApplicationContext();
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt b/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt
index 7a858e4..2452a88 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetPreviewContainerSizesTest.kt
@@ -25,6 +25,7 @@
 import com.android.launcher3.DeviceProfile
 import com.android.launcher3.InvariantDeviceProfile
 import com.android.launcher3.LauncherAppState
+import com.android.launcher3.dagger.LauncherComponentProvider.appComponent
 import com.android.launcher3.icons.IconCache
 import com.android.launcher3.model.WidgetItem
 import com.android.launcher3.util.ActivityContextWrapper
@@ -53,7 +54,7 @@
         context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
         testInvariantProfile = LauncherAppState.getIDP(context)
         widgetItemInvariantProfile =
-            InvariantDeviceProfile().apply {
+            context.appComponent.idp.apply {
                 numRows = TEST_GRID_SIZE
                 numColumns = TEST_GRID_SIZE
             }
@@ -143,13 +144,13 @@
             widgetSize: Point,
             context: Context,
             invariantDeviceProfile: InvariantDeviceProfile,
-            iconCache: IconCache
+            iconCache: IconCache,
         ): WidgetItem {
             val providerInfo =
                 createAppWidgetProviderInfo(
                     ComponentName.createRelative(
                         TEST_PACKAGE,
-                        /*cls=*/ ".WidgetProvider_" + widgetSize.x + "x" + widgetSize.y
+                        /*cls=*/ ".WidgetProvider_" + widgetSize.x + "x" + widgetSize.y,
                     )
                 )
             val widgetInfo =
diff --git a/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java b/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
index 2f5fcfe..a17e472 100644
--- a/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
+++ b/tests/multivalentTests/src/com/android/launcher3/widget/picker/util/WidgetsTableUtilsTest.java
@@ -15,8 +15,6 @@
  */
 package com.android.launcher3.widget.picker.util;
 
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
 import static com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -44,10 +42,12 @@
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.pm.ShortcutConfigActivityInfo;
 import com.android.launcher3.util.ActivityContextWrapper;
+import com.android.launcher3.util.SandboxApplication;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.util.WidgetsTableUtils;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -68,6 +68,8 @@
     private static final int NUM_OF_COLS = 5;
     private static final int NUM_OF_ROWS = 5;
 
+    @Rule public SandboxApplication app = new SandboxApplication();
+
     @Mock
     private IconCache mIconCache;
 
@@ -89,9 +91,8 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mContext = new ActivityContextWrapper(getApplicationContext());
-
-        mTestInvariantProfile = new InvariantDeviceProfile();
+        mContext = new ActivityContextWrapper(app);
+        mTestInvariantProfile = InvariantDeviceProfile.INSTANCE.get(app);
         mTestInvariantProfile.numColumns = NUM_OF_COLS;
         mTestInvariantProfile.numRows = NUM_OF_ROWS;
 
diff --git a/tests/src/com/android/launcher3/allapps/KeyboardFocusTest.java b/tests/src/com/android/launcher3/allapps/KeyboardFocusTest.java
index 1e21ee5..44df5b8 100644
--- a/tests/src/com/android/launcher3/allapps/KeyboardFocusTest.java
+++ b/tests/src/com/android/launcher3/allapps/KeyboardFocusTest.java
@@ -23,7 +23,6 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.util.BaseLauncherActivityTest;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.views.ActivityContext;
 
 import org.junit.Test;
@@ -64,7 +63,6 @@
     }
 
     @Test
-    @ScreenRecord  //b/378167329
     public void testAllAppsExitSearchAndFocusSearchResults() {
         loadLauncherSync();
         goToState(LauncherState.ALL_APPS);
diff --git a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
index b4ee090..38fad6b 100644
--- a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
+++ b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
@@ -25,6 +25,7 @@
 import com.android.launcher3.Flags
 import com.android.launcher3.LauncherPrefs
 import com.android.launcher3.model.ModelDbController
+import com.android.launcher3.model.ModelDelegate
 import com.android.launcher3.provider.RestoreDbTask
 import com.android.launcher3.util.Executors.MODEL_EXECUTOR
 import com.android.launcher3.util.TestUtil
@@ -34,6 +35,7 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
 
 /**
  * Makes sure to test {@code RestoreDbTask#removeOldDBs}, we need to remove all the dbs that are not
@@ -49,6 +51,8 @@
     @Rule
     val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
 
+    val modelDelegate = mock<ModelDelegate>()
+
     @Before
     fun setUp() {
         setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_NARROW_GRID_RESTORE)
@@ -68,9 +72,9 @@
     fun oldDatabasesNotPresentAfterRestore() {
         val dbController = ModelDbController(getInstrumentation().targetContext)
         if (Flags.gridMigrationRefactor()) {
-            dbController.attemptMigrateDb(null)
+            dbController.attemptMigrateDb(null, modelDelegate)
         } else {
-            dbController.tryMigrateDB(null)
+            dbController.tryMigrateDB(null, modelDelegate)
         }
         TestUtil.runOnExecutorSync(MODEL_EXECUTOR) {
             assert(backAndRestoreRule.getDatabaseFiles().size == 1) {
diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventWaiter.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventWaiter.kt
new file mode 100644
index 0000000..20ad60f
--- /dev/null
+++ b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventWaiter.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.celllayout.integrationtest.events
+
+import com.android.launcher3.debug.TestEventEmitter.TestEvent
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeoutOrNull
+
+enum class EventStatus() {
+    SUCCESS,
+    FAILURE,
+    TIMEOUT,
+}
+
+class EventWaiter(val eventToWait: TestEvent) {
+    private val deferrable = CompletableDeferred<EventStatus>()
+
+    companion object {
+        private const val TAG = "EventWaiter"
+        private val SIGNAL_TIMEOUT = TimeUnit.SECONDS.toMillis(5)
+    }
+
+    fun waitForSignal(timeout: Long = SIGNAL_TIMEOUT) = runBlocking {
+        var status = withTimeoutOrNull(timeout) { deferrable.await() }
+        if (status == null) {
+            status = EventStatus.TIMEOUT
+        }
+        if (status != EventStatus.SUCCESS) {
+            throw Exception("Failure waiting for event $eventToWait, failure = $status")
+        }
+    }
+
+    fun terminate() {
+        deferrable.complete(EventStatus.SUCCESS)
+    }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt
index fb61ced..45eb5e1 100644
--- a/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt
+++ b/tests/src/com/android/launcher3/celllayout/integrationtest/events/EventsRule.kt
@@ -17,11 +17,15 @@
 package com.android.launcher3.celllayout.integrationtest.events
 
 import android.content.Context
-import com.android.launcher3.debug.TestEvent
+import android.util.Log
+import com.android.dx.mockito.inline.extended.ExtendedMockito.*
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.android.launcher3.debug.TestEventEmitter
+import com.android.launcher3.debug.TestEventEmitter.TestEvent
 import org.junit.rules.TestRule
 import org.junit.runner.Description
 import org.junit.runners.model.Statement
+import org.mockito.quality.Strictness
 
 /**
  * Rule to create EventWaiters to wait for events that happens on the Launcher. For reference look
@@ -30,35 +34,65 @@
  * Waiting for event should be used to prevent race conditions, it provides a more precise way of
  * waiting for events compared to [AbstractLauncherUiTest#waitForLauncherCondition].
  *
- * This class overrides the [TestEventEmitter] with [TestEventsEmitterImplementation] and makes sure
- * to return the [TestEventEmitter] to the previous value when finished.
+ * This class mocks the static method [TestEventEmitter.sendEvent]
  */
 class EventsRule(val context: Context) : TestRule {
 
-    private var prevEventEmitter: TestEventEmitter? = null
+    private val expectedEvents: ArrayDeque<EventWaiter> = ArrayDeque()
 
-    private val eventEmitter = TestEventsEmitterImplementation()
+    private lateinit var mockitoSession: StaticMockitoSession
 
     override fun apply(base: Statement, description: Description?): Statement {
         return object : Statement() {
             override fun evaluate() {
-                beforeTest()
-                base.evaluate()
-                afterTest()
+                try {
+                    beforeTest()
+                    base.evaluate()
+                } finally {
+                    afterTest()
+                }
             }
         }
     }
 
     fun createEventWaiter(expectedEvent: TestEvent): EventWaiter {
-        return eventEmitter.createEventWaiter(expectedEvent)
+        val eventWaiter = EventWaiter(expectedEvent)
+        expectedEvents.add(eventWaiter)
+        return eventWaiter
     }
 
     private fun beforeTest() {
-        prevEventEmitter = TestEventEmitter.INSTANCE.get(context)
-        TestEventEmitter.INSTANCE.initializeForTesting(eventEmitter)
+        mockitoSession =
+            mockitoSession()
+                .strictness(Strictness.LENIENT)
+                .spyStatic(TestEventEmitter::class.java)
+                .startMocking()
+
+        doAnswer { invocation ->
+                val event = (invocation.arguments[0] as TestEvent)
+                Log.d(TAG, "Signal received $event")
+                Log.d(TAG, "Total expected events ${expectedEvents.size}")
+                if (!expectedEvents.isEmpty()) {
+                    val eventWaiter = expectedEvents.last()
+                    if (eventWaiter.eventToWait == event) {
+                        Log.d(TAG, "Removing $event")
+                        expectedEvents.removeLast()
+                        eventWaiter.terminate()
+                    } else {
+                        Log.d(TAG, "Not matching $event")
+                    }
+                }
+                null
+            }
+            .`when` { TestEventEmitter.sendEvent(any()) }
     }
 
     private fun afterTest() {
-        TestEventEmitter.INSTANCE.initializeForTesting(prevEventEmitter)
+        mockitoSession.finishMocking()
+        expectedEvents.clear()
+    }
+
+    companion object {
+        private const val TAG = "TestEvents"
     }
 }
diff --git a/tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt b/tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt
deleted file mode 100644
index 5e062d0..0000000
--- a/tests/src/com/android/launcher3/celllayout/integrationtest/events/TestEventsEmitterImplementation.kt
+++ /dev/null
@@ -1,90 +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.celllayout.integrationtest.events
-
-import android.util.Log
-import com.android.launcher3.debug.TestEvent
-import com.android.launcher3.debug.TestEventEmitter
-import java.util.concurrent.TimeUnit
-import kotlinx.coroutines.CompletableDeferred
-import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.withTimeoutOrNull
-
-enum class EventStatus() {
-    SUCCESS,
-    FAILURE,
-    TIMEOUT,
-}
-
-class EventWaiter(val eventToWait: TestEvent) {
-    private val deferrable = CompletableDeferred<EventStatus>()
-
-    companion object {
-        private const val TAG = "EventWaiter"
-        private val SIGNAL_TIMEOUT = TimeUnit.SECONDS.toMillis(5)
-    }
-
-    fun waitForSignal(timeout: Long = SIGNAL_TIMEOUT) = runBlocking {
-        var status = withTimeoutOrNull(timeout) { deferrable.await() }
-        if (status == null) {
-            status = EventStatus.TIMEOUT
-        }
-        if (status != EventStatus.SUCCESS) {
-            throw Exception("Failure waiting for event $eventToWait, failure = $status")
-        }
-    }
-
-    fun terminate() {
-        deferrable.complete(EventStatus.SUCCESS)
-    }
-}
-
-class TestEventsEmitterImplementation() : TestEventEmitter {
-    companion object {
-        private const val TAG = "TestEvents"
-    }
-
-    private val expectedEvents: ArrayDeque<EventWaiter> = ArrayDeque()
-
-    fun createEventWaiter(expectedEvent: TestEvent): EventWaiter {
-        val eventWaiter = EventWaiter(expectedEvent)
-        expectedEvents.add(eventWaiter)
-        return eventWaiter
-    }
-
-    private fun clearQueue() {
-        expectedEvents.clear()
-    }
-
-    override fun sendEvent(event: TestEvent) {
-        Log.d(TAG, "Signal received $event")
-        Log.d(TAG, "Total expected events ${expectedEvents.size}")
-        if (expectedEvents.isEmpty()) return
-        val eventWaiter = expectedEvents.last()
-        if (eventWaiter.eventToWait == event) {
-            Log.d(TAG, "Removing $event")
-            expectedEvents.removeLast()
-            eventWaiter.terminate()
-        } else {
-            Log.d(TAG, "Not matching $event")
-        }
-    }
-
-    override fun close() {
-        clearQueue()
-    }
-}
diff --git a/tests/src/com/android/launcher3/dragging/TaplDragTest.java b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
index e2f9feb9a..2e02eb0 100644
--- a/tests/src/com/android/launcher3/dragging/TaplDragTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplDragTest.java
@@ -64,6 +64,7 @@
     @Test
     @PortraitLandscape
     @PlatinumTest(focusArea = "launcher")
+    @ScreenRecordRule.ScreenRecord // b/383917141
     public void testDragToFolder() {
         // TODO: add the use case to drag an icon to an existing folder. Currently it either fails
         // on tablets or phones due to difference in resolution.
diff --git a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
index 1816030..f490bd6 100644
--- a/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
+++ b/tests/src/com/android/launcher3/dragging/TaplUninstallRemoveTest.java
@@ -36,7 +36,6 @@
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
 import com.android.launcher3.util.TestUtil;
 import com.android.launcher3.util.Wait;
-import com.android.launcher3.util.rule.ScreenRecordRule;
 
 import org.junit.Test;
 
@@ -55,7 +54,6 @@
      */
     @Test
     @PortraitLandscape
-    @ScreenRecordRule.ScreenRecord // b/349439239
     public void testDeleteFromWorkspace() {
         for (String appName : new String[]{GMAIL_APP_NAME, STORE_APP_NAME, TEST_APP_NAME}) {
             final HomeAppIcon homeAppIcon = createShortcutInCenterIfNotExist(appName);
@@ -110,6 +108,9 @@
     @PortraitLandscape
     @PlatinumTest(focusArea = "launcher")
     public void testUninstallFromAllApps() throws Exception {
+        // Ensure no existing app icons on the workspace cause scroll to all apps interruptions
+        mLauncher.clearLauncherData();
+
         installDummyAppAndWaitForUIUpdate();
         try {
             Workspace workspace = mLauncher.getWorkspace();
@@ -126,7 +127,6 @@
      */
     @Test
     @PlatinumTest(focusArea = "launcher")
-    @ScreenRecordRule.ScreenRecord // b/319501259
     public void uninstallWorkspaceIcon() throws IOException {
         Point[] gridPositions = TestUtil.getCornersAndCenterPositions(mLauncher);
         StringBuilder sb = new StringBuilder();
diff --git a/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
new file mode 100644
index 0000000..c956395
--- /dev/null
+++ b/tests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.folder
+
+import android.R
+import android.os.Process
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
+import com.android.launcher3.LauncherAppState
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.graphics.PreloadIconDrawable
+import com.android.launcher3.graphics.ThemeManager
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.FastBitmapDrawable
+import com.android.launcher3.icons.IconCache
+import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver
+import com.android.launcher3.icons.PlaceHolderIconDrawable
+import com.android.launcher3.icons.UserBadgeDrawable
+import com.android.launcher3.model.data.FolderInfo
+import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED
+import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.ActivityContextWrapper
+import com.android.launcher3.util.AllModulesForTest
+import com.android.launcher3.util.Executors
+import com.android.launcher3.util.FakePrefsModule
+import com.android.launcher3.util.FlagOp
+import com.android.launcher3.util.LauncherLayoutBuilder
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
+import com.android.launcher3.util.TestUtil
+import com.android.launcher3.util.UserIconInfo
+import com.google.common.truth.Truth.assertThat
+import dagger.Component
+import kotlin.annotation.AnnotationRetention.RUNTIME
+import kotlin.annotation.AnnotationTarget.FUNCTION
+import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER
+import kotlin.annotation.AnnotationTarget.PROPERTY_SETTER
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runner.RunWith
+import org.junit.runners.model.Statement
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+/** Tests for [PreviewItemManager] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PreviewItemManagerTest {
+
+    @get:Rule val theseStateRule = ThemeStateRule()
+
+    private lateinit var previewItemManager: PreviewItemManager
+    private lateinit var context: SandboxModelContext
+    private lateinit var folderItems: ArrayList<WorkspaceItemInfo>
+    private lateinit var modelHelper: LauncherModelHelper
+    private lateinit var folderIcon: FolderIcon
+    private lateinit var iconCache: IconCache
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        modelHelper = LauncherModelHelper()
+        context = modelHelper.sandboxContext
+        context.initDaggerComponent(DaggerPreviewItemManagerTestComponent.builder())
+        theseStateRule.themeState?.let {
+            LauncherPrefs.get(context).putSync(ThemeManager.THEMED_ICONS.to(it))
+        }
+        folderIcon = FolderIcon(ActivityContextWrapper(context))
+
+        iconCache = LauncherAppState.INSTANCE[context].iconCache
+        spyOn(iconCache)
+        doReturn(null).whenever(iconCache).updateIconInBackground(any(), any())
+
+        previewItemManager = PreviewItemManager(folderIcon)
+        modelHelper
+            .setupDefaultLayoutProvider(
+                LauncherLayoutBuilder()
+                    .atWorkspace(0, 0, 1)
+                    .putFolder(R.string.copy)
+                    .addApp(LauncherModelHelper.TEST_PACKAGE, LauncherModelHelper.TEST_ACTIVITY)
+                    .addApp(LauncherModelHelper.TEST_PACKAGE, LauncherModelHelper.TEST_ACTIVITY2)
+                    .addApp(LauncherModelHelper.TEST_PACKAGE, LauncherModelHelper.TEST_ACTIVITY3)
+                    .addApp(LauncherModelHelper.TEST_PACKAGE, LauncherModelHelper.TEST_ACTIVITY4)
+                    .build()
+            )
+            .loadModelSync()
+
+        folderIcon.mInfo =
+            modelHelper.bgDataModel.itemsIdMap.find { it.itemType == ITEM_TYPE_FOLDER }
+                as FolderInfo
+        // Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps
+        folderItems = folderIcon.mInfo.getAppContents()
+
+        // Set second icon to be non-themed.
+        folderItems[1].bitmap.themedBitmap = null
+
+        // Set third icon to be themed with badge.
+        folderItems[2].bitmap =
+            folderItems[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
+
+        // Set fourth icon to be non-themed with badge.
+        folderItems[3].bitmap =
+            folderItems[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
+        folderItems[3].bitmap.themedBitmap = null
+    }
+
+    @After
+    @Throws(Exception::class)
+    fun tearDown() {
+        modelHelper.destroy()
+    }
+
+    @Test
+    @MonoThemeEnabled(true)
+    fun checkThemedIconWithThemingOn_iconShouldBeThemed() {
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+
+        previewItemManager.setDrawable(drawingParams, folderItems[0])
+
+        assert((drawingParams.drawable as FastBitmapDrawable).isThemed)
+    }
+
+    @Test
+    @MonoThemeEnabled(false)
+    fun checkThemedIconWithThemingOff_iconShouldNotBeThemed() {
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+
+        previewItemManager.setDrawable(drawingParams, folderItems[0])
+
+        assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed)
+    }
+
+    @Test
+    @MonoThemeEnabled(true)
+    fun checkUnthemedIconWithThemingOn_iconShouldNotBeThemed() {
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+
+        previewItemManager.setDrawable(drawingParams, folderItems[1])
+
+        assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed)
+    }
+
+    @Test
+    @MonoThemeEnabled(false)
+    fun checkUnthemedIconWithThemingOff_iconShouldNotBeThemed() {
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+
+        previewItemManager.setDrawable(drawingParams, folderItems[1])
+
+        assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed)
+    }
+
+    @Test
+    @MonoThemeEnabled(true)
+    fun checkThemedIconWithBadgeWithThemingOn_iconAndBadgeShouldBeThemed() {
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+
+        previewItemManager.setDrawable(drawingParams, folderItems[2])
+
+        assert((drawingParams.drawable as FastBitmapDrawable).isThemed)
+        assert(
+            ((drawingParams.drawable as FastBitmapDrawable).badge as UserBadgeDrawable).mIsThemed
+        )
+    }
+
+    @Test
+    @MonoThemeEnabled(true)
+    fun checkUnthemedIconWithBadgeWithThemingOn_badgeShouldBeThemed() {
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+
+        previewItemManager.setDrawable(drawingParams, folderItems[3])
+
+        assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed)
+        assert(
+            ((drawingParams.drawable as FastBitmapDrawable).badge as UserBadgeDrawable).mIsThemed
+        )
+    }
+
+    @Test
+    @MonoThemeEnabled(false)
+    fun checkUnthemedIconWithBadgeWithThemingOff_iconAndBadgeShouldNotBeThemed() {
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+
+        previewItemManager.setDrawable(drawingParams, folderItems[3])
+
+        assert(!(drawingParams.drawable as FastBitmapDrawable).isThemed)
+        assert(
+            !((drawingParams.drawable as FastBitmapDrawable).badge as UserBadgeDrawable).mIsThemed
+        )
+    }
+
+    @Test
+    fun `Inactive archived app previews are not drawn as preload icon`() {
+        // Given
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+        val archivedApp =
+            WorkspaceItemInfo().apply {
+                runtimeStatusFlags = runtimeStatusFlags or FLAG_ARCHIVED
+                runtimeStatusFlags = runtimeStatusFlags and FLAG_INSTALL_SESSION_ACTIVE.inv()
+            }
+        // When
+        previewItemManager.setDrawable(drawingParams, archivedApp)
+        // Then
+        assertThat(drawingParams.drawable).isNotInstanceOf(PreloadIconDrawable::class.java)
+    }
+
+    @Test
+    fun `Actively installing archived app previews are drawn as preload icon`() {
+        // Given
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+        val archivedApp =
+            WorkspaceItemInfo().apply {
+                runtimeStatusFlags = runtimeStatusFlags or FLAG_ARCHIVED
+                runtimeStatusFlags = runtimeStatusFlags or FLAG_INSTALL_SESSION_ACTIVE
+            }
+        // When
+        TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) {
+            // Run on main thread because preload drawable triggers animator
+            previewItemManager.setDrawable(drawingParams, archivedApp)
+        }
+        // Then
+        assertThat(drawingParams.drawable).isInstanceOf(PreloadIconDrawable::class.java)
+    }
+
+    @Test
+    fun `Preview item loads and apply high res icon`() {
+        val drawingParams = PreviewItemDrawingParams(0f, 0f, 0f)
+        val originalBitmap = folderItems[3].bitmap
+        folderItems[3].bitmap = BitmapInfo.LOW_RES_INFO
+
+        previewItemManager.setDrawable(drawingParams, folderItems[3])
+        assertThat(drawingParams.drawable).isInstanceOf(PlaceHolderIconDrawable::class.java)
+
+        val callbackCaptor = argumentCaptor<ItemInfoUpdateReceiver>()
+        verify(iconCache).updateIconInBackground(callbackCaptor.capture(), eq(folderItems[3]))
+
+        // Restore high-res icon
+        folderItems[3].bitmap = originalBitmap
+
+        // Calling with a different item info will ignore the update
+        callbackCaptor.firstValue.reapplyItemInfo(folderItems[2])
+        assertThat(drawingParams.drawable).isInstanceOf(PlaceHolderIconDrawable::class.java)
+
+        // Calling with correct value will update the drawable to high-res
+        callbackCaptor.firstValue.reapplyItemInfo(folderItems[3])
+        assertThat(drawingParams.drawable).isNotInstanceOf(PlaceHolderIconDrawable::class.java)
+        assertThat(drawingParams.drawable).isInstanceOf(FastBitmapDrawable::class.java)
+    }
+
+    private fun profileFlagOp(type: Int) =
+        UserIconInfo(Process.myUserHandle(), type).applyBitmapInfoFlags(FlagOp.NO_OP)
+}
+
+class ThemeStateRule : TestRule {
+
+    var themeState: Boolean? = null
+
+    override fun apply(base: Statement, description: Description): Statement {
+        themeState = description.getAnnotation(MonoThemeEnabled::class.java)?.value
+        return base
+    }
+}
+
+// Annotation for tests that need to be run with quickstep enabled and disabled.
+@Retention(RUNTIME)
+@Target(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
+annotation class MonoThemeEnabled(val value: Boolean = false)
+
+@LauncherAppSingleton
+@Component(modules = [AllModulesForTest::class, FakePrefsModule::class])
+interface PreviewItemManagerTestComponent : LauncherAppComponent {
+
+    @Component.Builder
+    interface Builder : LauncherAppComponent.Builder {
+        override fun build(): PreviewItemManagerTestComponent
+    }
+}
diff --git a/tests/src/com/android/launcher3/model/GridMigrationTest.kt b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
index b8ffe74..380c208 100644
--- a/tests/src/com/android/launcher3/model/GridMigrationTest.kt
+++ b/tests/src/com/android/launcher3/model/GridMigrationTest.kt
@@ -32,6 +32,8 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
 
 private val phoneContext = InstrumentationRegistry.getInstrumentation().targetContext
 
@@ -80,13 +82,13 @@
 @RunWith(AndroidJUnit4::class)
 class GridMigrationTest {
     private val DB_FILE = "test_launcher.db"
-    // This DB is used for testing the heuristic where we add an extra row at the bottom.
-    private val DB_FILE_NO_SHIFT = "test_launcher_2.db"
 
     @JvmField
     @Rule
     val setFlagsRule = SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT)
 
+    val modelDelegate = mock<ModelDelegate>()
+
     @Before
     fun setup() {
         setFlagsRule.setFlags(true, Flags.FLAG_ONE_GRID_SPECS)
@@ -102,6 +104,7 @@
                 dst.dbHelper,
                 src.dbHelper.readableDatabase,
                 true,
+                modelDelegate,
             )
         } else {
             GridSizeMigrationDBController.migrateGridIfNeeded(
@@ -111,6 +114,7 @@
                 dst.dbHelper,
                 src.dbHelper.readableDatabase,
                 true,
+                modelDelegate,
             )
         }
     }
@@ -149,11 +153,12 @@
     }
 
     /**
-     * Migrate src into dst and compare to target. This method validates 3 things:
+     * Migrate src into dst and compare to target. This method validates 4 things:
      * 1. dst has the same number of items as src after the migration, meaning, none of the items
      *    were removed during the migration.
      * 2. dst is valid, meaning that none of the items overlap with each other.
      * 3. dst is equal to target to ensure we don't unintentionally change the migration logic.
+     * 4. migration notifies the complete callback.
      */
     private fun runTest(src: GridMigrationData, dst: GridMigrationData, target: GridMigrationData) {
         migrate(src, dst)
@@ -162,6 +167,7 @@
         }
         validateDb(dst)
         compare(dst, target, src)
+        verify(modelDelegate).gridMigrationComplete(src.gridState, dst.gridState)
     }
 
     // Copying the src db for all tests.
@@ -222,42 +228,6 @@
 
     @JvmField
     @Rule
-    val result5x5to5x8WithShift =
-        TestToPhoneFileCopier(
-            src = "databases/GridMigrationTest/result5x5to5x8WithShift.db",
-            dest = "databases/result5x5to5x8WithShift.db",
-            removeOnFinish = true,
-        )
-
-    @Test
-    fun `5x5 to 5x8 with cells shifting down`() =
-        runTest(
-            src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)),
-            dst =
-                GridMigrationData(
-                    null, // in memory db, to download a new db change null
-                    // for
-                    // the filename of the db name to store it. Do not use existing names.
-                    DeviceGridState(5, 8, 5, TYPE_PHONE, ""),
-                ),
-            target =
-                GridMigrationData(
-                    "result5x5to5x8WithShift.db",
-                    DeviceGridState(5, 8, 5, TYPE_PHONE, ""),
-                ),
-        )
-
-    @JvmField
-    @Rule
-    val fileCopierNoShift =
-        TestToPhoneFileCopier(
-            src = "databases/GridMigrationTest/$DB_FILE_NO_SHIFT",
-            dest = "databases/$DB_FILE_NO_SHIFT",
-            removeOnFinish = true,
-        )
-
-    @JvmField
-    @Rule
     val result5x5to5x8 =
         TestToPhoneFileCopier(
             src = "databases/GridMigrationTest/result5x5to5x8.db",
@@ -266,16 +236,13 @@
         )
 
     @Test
-    fun `5x5 to 5x8 without cell shift`() =
+    fun `5x5 to 5x8`() =
         runTest(
-            src =
-                GridMigrationData(
-                    DB_FILE_NO_SHIFT,
-                    DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE_NO_SHIFT),
-                ),
+            src = GridMigrationData(DB_FILE, DeviceGridState(5, 5, 5, TYPE_PHONE, DB_FILE)),
             dst =
                 GridMigrationData(
-                    null, // in memory db, to download a new db change null for
+                    null, // in memory db, to download a new db change null
+                    // for
                     // the filename of the db name to store it. Do not use existing names.
                     DeviceGridState(5, 8, 5, TYPE_PHONE, ""),
                 ),
diff --git a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
index 34cbdee..246219f 100644
--- a/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
+++ b/tests/src/com/android/launcher3/model/LoaderTaskTest.kt
@@ -1,7 +1,10 @@
 package com.android.launcher3.model
 
 import android.appwidget.AppWidgetManager
+import android.content.ComponentName
 import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.LauncherActivityInfo
 import android.os.Process
 import android.os.UserHandle
 import android.platform.test.annotations.DisableFlags
@@ -11,25 +14,40 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
 import com.android.launcher3.Flags
-import com.android.launcher3.InvariantDeviceProfile
 import com.android.launcher3.LauncherAppState
 import com.android.launcher3.LauncherModel
 import com.android.launcher3.LauncherModel.LoaderTransaction
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.LauncherPrefs.Companion.IS_FIRST_LOAD_AFTER_RESTORE
+import com.android.launcher3.LauncherPrefs.Companion.RESTORE_DEVICE
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+import com.android.launcher3.dagger.LauncherAppComponent
+import com.android.launcher3.dagger.LauncherAppSingleton
 import com.android.launcher3.icons.IconCache
 import com.android.launcher3.icons.cache.CachingLogic
 import com.android.launcher3.icons.cache.IconCacheUpdateHandler
+import com.android.launcher3.model.data.AppInfo
+import com.android.launcher3.model.data.IconRequestInfo
+import com.android.launcher3.model.data.WorkspaceItemInfo
 import com.android.launcher3.pm.UserCache
 import com.android.launcher3.provider.RestoreDbTask
 import com.android.launcher3.ui.TestViewHelpers
+import com.android.launcher3.util.AllModulesForTest
 import com.android.launcher3.util.Executors.MODEL_EXECUTOR
 import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext
 import com.android.launcher3.util.LooperIdleLock
 import com.android.launcher3.util.TestUtil
 import com.android.launcher3.util.UserIconInfo
 import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertThat
+import dagger.BindsInstance
+import dagger.Component
 import java.util.concurrent.CountDownLatch
-import java.util.function.Predicate
 import junit.framework.Assert.assertEquals
 import org.junit.After
 import org.junit.Before
@@ -48,7 +66,7 @@
 import org.mockito.kotlin.any
 import org.mockito.kotlin.anyOrNull
 import org.mockito.kotlin.doReturn
-import org.mockito.kotlin.spy
+import org.mockito.kotlin.mock
 import org.mockito.kotlin.verify
 import org.mockito.kotlin.whenever
 import org.mockito.quality.Strictness
@@ -73,12 +91,10 @@
         )
     private lateinit var mockitoSession: MockitoSession
 
-    @Mock private lateinit var app: LauncherAppState
     @Mock private lateinit var bgAllAppsList: AllAppsList
     @Mock private lateinit var modelDelegate: ModelDelegate
     @Mock private lateinit var launcherBinder: BaseLauncherBinder
     private lateinit var launcherModel: LauncherModel
-    @Mock private lateinit var widgetsFilterDataProvider: WidgetsFilterDataProvider
     @Mock private lateinit var transaction: LoaderTransaction
     @Mock private lateinit var iconCache: IconCache
     @Mock private lateinit var idleLock: LooperIdleLock
@@ -89,6 +105,9 @@
 
     @get:Rule val setFlagsRule = SetFlagsRule()
 
+    private val app: LauncherAppState
+        get() = context.appComponent.launcherAppState
+
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
@@ -99,37 +118,34 @@
                 .strictness(Strictness.LENIENT)
                 .mockStatic(FirstScreenBroadcastHelper::class.java)
                 .startMocking()
-        val idp =
-            InvariantDeviceProfile().apply {
-                numRows = 5
-                numColumns = 6
-                numDatabaseHotseatIcons = 5
-            }
-        context.putObject(InvariantDeviceProfile.INSTANCE, idp)
-        context.putObject(LauncherAppState.INSTANCE, app)
-
         doReturn(TestViewHelpers.findWidgetProvider(false))
             .`when`(context.spyService(AppWidgetManager::class.java))
             .getAppWidgetInfo(any())
-        `when`(app.context).thenReturn(context)
-        `when`(app.model).thenReturn(launcherModel)
 
         `when`(launcherModel.beginLoader(any())).thenReturn(transaction)
-        `when`(app.iconCache).thenReturn(iconCache)
         `when`(launcherModel.modelDbController)
             .thenReturn(FactitiousDbController(context, INSERTION_STATEMENT_FILE))
-        `when`(app.invariantDeviceProfile).thenReturn(idp)
         `when`(launcherBinder.newIdleLock(any())).thenReturn(idleLock)
         `when`(idleLock.awaitLocked(1000)).thenReturn(false)
-        `when`(iconCache.updateHandler).thenReturn(iconCacheUpdateHandler)
-        `when`(widgetsFilterDataProvider.getDefaultWidgetsFilter()).thenReturn(Predicate { true })
-        context.putObject(UserCache.INSTANCE, userCache)
-
+        `when`(iconCache.getUpdateHandler()).thenReturn(iconCacheUpdateHandler)
+        context.initDaggerComponent(
+            DaggerLoaderTaskTest_TestComponent.builder()
+                .bindUserCache(userCache)
+                .bindIconCache(iconCache)
+                .bindLauncherModel(launcherModel)
+        )
+        context.appComponent.idp.apply {
+            numRows = 5
+            numColumns = 6
+            numDatabaseHotseatIcons = 5
+        }
         TestUtil.grantWriteSecurePermission()
     }
 
     @After
     fun tearDown() {
+        LauncherPrefs.get(context).removeSync(RESTORE_DEVICE)
+        LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(false))
         context.onDestroy()
         mockitoSession.finishMocking()
     }
@@ -141,32 +157,32 @@
             val mockUserHandles = arrayListOf<UserHandle>(MAIN_HANDLE)
             `when`(userCache.userProfiles).thenReturn(mockUserHandles)
             `when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 1))
-            LoaderTask(
-                    app,
-                    bgAllAppsList,
-                    this,
-                    modelDelegate,
-                    launcherBinder,
-                    widgetsFilterDataProvider,
-                )
+            LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder)
                 .runSyncOnBackgroundThread()
-            Truth.assertThat(workspaceItems.size).isAtLeast(25)
-            Truth.assertThat(appWidgets.size).isAtLeast(7)
-            Truth.assertThat(collections.size()).isAtLeast(8)
+            Truth.assertThat(
+                    itemsIdMap
+                        .filter {
+                            it.container == CONTAINER_DESKTOP || it.container == CONTAINER_HOTSEAT
+                        }
+                        .size
+                )
+                .isAtLeast(32)
+            Truth.assertThat(itemsIdMap.filter { ModelUtils.WIDGET_FILTER.test(it) }.size)
+                .isAtLeast(7)
+            Truth.assertThat(
+                    itemsIdMap
+                        .filter {
+                            it.itemType == ITEM_TYPE_FOLDER || it.itemType == ITEM_TYPE_APP_PAIR
+                        }
+                        .size
+                )
+                .isAtLeast(8)
             Truth.assertThat(itemsIdMap.size()).isAtLeast(40)
-            Truth.assertThat(widgetsModel.defaultWidgetsFilter).isNotNull()
         }
 
     @Test
     fun bindsLoadedDataCorrectly() {
-        LoaderTask(
-                app,
-                bgAllAppsList,
-                BgDataModel(),
-                modelDelegate,
-                launcherBinder,
-                widgetsFilterDataProvider,
-            )
+        LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
             .runSyncOnBackgroundThread()
 
         verify(launcherBinder).bindWorkspace(true, false)
@@ -175,7 +191,6 @@
         verify(launcherBinder).bindAllApps()
         verify(iconCacheUpdateHandler, times(4)).updateIcons(any(), any<CachingLogic<Any>>(), any())
         verify(launcherBinder).bindDeepShortcuts()
-        verify(widgetsFilterDataProvider).initPeriodicDataRefresh(any())
         verify(launcherBinder).bindWidgets()
         verify(modelDelegate).loadAndBindOtherItems(anyOrNull())
         verify(iconCacheUpdateHandler).finish()
@@ -193,15 +208,7 @@
             `when`(userManagerState?.isUserQuiet(MAIN_HANDLE)).thenReturn(true)
             `when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 1))
 
-            LoaderTask(
-                    app,
-                    bgAllAppsList,
-                    this,
-                    modelDelegate,
-                    launcherBinder,
-                    widgetsFilterDataProvider,
-                    userManagerState,
-                )
+            LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder, userManagerState)
                 .runSyncOnBackgroundThread()
 
             verify(bgAllAppsList)
@@ -222,15 +229,7 @@
             `when`(userManagerState?.isUserQuiet(MAIN_HANDLE)).thenReturn(true)
             `when`(userCache.getUserInfo(MAIN_HANDLE)).thenReturn(UserIconInfo(MAIN_HANDLE, 3))
 
-            LoaderTask(
-                    app,
-                    bgAllAppsList,
-                    this,
-                    modelDelegate,
-                    launcherBinder,
-                    widgetsFilterDataProvider,
-                    userManagerState,
-                )
+            LoaderTask(app, bgAllAppsList, this, modelDelegate, launcherBinder, userManagerState)
                 .runSyncOnBackgroundThread()
 
             verify(bgAllAppsList)
@@ -242,87 +241,11 @@
         }
 
     @Test
-    @DisableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
-    fun `When secure setting true and is restore then send installed item broadcast`() {
-        // Given
-        val spyContext = spy(context)
-        `when`(app.context).thenReturn(spyContext)
-        whenever(
-                FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
-                    any(),
-                    any(),
-                    any(),
-                    any(),
-                )
-            )
-            .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,
-                widgetsFilterDataProvider,
-            )
-            .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
     @EnableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
-    fun `When broadcast flag true and is restore then send installed item broadcast`() {
+    fun `When broadcast flag on and is restore and secure setting off then send new broadcast`() {
         // Given
-        val spyContext = spy(context)
-        `when`(app.context).thenReturn(spyContext)
+        spyOn(context)
+        val spyContext = context
         whenever(
                 FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
                     any(),
@@ -345,14 +268,7 @@
         RestoreDbTask.setPending(spyContext)
 
         // When
-        LoaderTask(
-                app,
-                bgAllAppsList,
-                BgDataModel(),
-                modelDelegate,
-                launcherBinder,
-                widgetsFilterDataProvider,
-            )
+        LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
             .runSyncOnBackgroundThread()
 
         // Then
@@ -397,49 +313,8 @@
     @EnableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
     fun `When not a restore then installed item broadcast not sent`() {
         // Given
-        val spyContext = spy(context)
-        `when`(app.context).thenReturn(spyContext)
-        whenever(
-                FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
-                    any(),
-                    any(),
-                    any(),
-                    any(),
-                )
-            )
-            .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,
-                widgetsFilterDataProvider,
-            )
-            .runSyncOnBackgroundThread()
-
-        // Then
-        verify(spyContext, times(0)).sendBroadcast(any())
-    }
-
-    @Test
-    @DisableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
-    fun `When broadcast flag and secure setting false then installed item broadcast not sent`() {
-        // Given
-        val spyContext = spy(context)
-        `when`(app.context).thenReturn(spyContext)
+        spyOn(context)
+        val spyContext = context
         whenever(
                 FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
                     any(),
@@ -459,22 +334,226 @@
             .thenCallRealMethod()
 
         Settings.Secure.putInt(spyContext.contentResolver, "launcher_broadcast_installed_apps", 0)
-        RestoreDbTask.setPending(spyContext)
 
         // When
-        LoaderTask(
-                app,
-                bgAllAppsList,
-                BgDataModel(),
-                modelDelegate,
-                launcherBinder,
-                widgetsFilterDataProvider,
-            )
+        LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
             .runSyncOnBackgroundThread()
 
         // Then
         verify(spyContext, times(0)).sendBroadcast(any())
     }
+
+    @Test
+    @DisableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
+    fun `When broadcast flag off then installed item broadcast not sent`() {
+        // Given
+        spyOn(context)
+        val spyContext = context
+        whenever(
+                FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                    any(),
+                    any(),
+                    any(),
+                    any(),
+                )
+            )
+            .thenReturn(listOf(expectedBroadcastModel))
+
+        whenever(
+                FirstScreenBroadcastHelper.sendBroadcastsForModels(
+                    spyContext,
+                    listOf(expectedBroadcastModel),
+                )
+            )
+            .thenCallRealMethod()
+
+        Settings.Secure.putInt(
+            spyContext.contentResolver,
+            "disable_launcher_broadcast_installed_apps",
+            0,
+        )
+        RestoreDbTask.setPending(spyContext)
+
+        // When
+        LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
+            .runSyncOnBackgroundThread()
+
+        // Then
+        verify(spyContext, times(0)).sendBroadcast(any())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_FIRST_SCREEN_BROADCAST_ARCHIVING_EXTRAS)
+    fun `When failsafe secure setting on then installed item broadcast not sent`() {
+        // Given
+        spyOn(context)
+        val spyContext = context
+        whenever(
+                FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast(
+                    any(),
+                    any(),
+                    any(),
+                    any(),
+                )
+            )
+            .thenReturn(listOf(expectedBroadcastModel))
+
+        whenever(
+                FirstScreenBroadcastHelper.sendBroadcastsForModels(
+                    spyContext,
+                    listOf(expectedBroadcastModel),
+                )
+            )
+            .thenCallRealMethod()
+
+        Settings.Secure.putInt(
+            spyContext.contentResolver,
+            "disable_launcher_broadcast_installed_apps",
+            1,
+        )
+        RestoreDbTask.setPending(spyContext)
+
+        // When
+        LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
+            .runSyncOnBackgroundThread()
+
+        // Then
+        verify(spyContext, times(0)).sendBroadcast(any())
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+    fun `When flag on then archived AllApps icons found on Workspace loaded from db`() {
+        // Given
+        // Given
+        val activityInfo: LauncherActivityInfo = mock()
+        val applicationInfo: ApplicationInfo = mock<ApplicationInfo>().apply { isArchived = true }
+        whenever(activityInfo.applicationInfo).thenReturn(applicationInfo)
+        val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)
+        val expectedComponent = ComponentName("package", "class")
+        val workspaceIconRequests =
+            listOf(
+                IconRequestInfo<WorkspaceItemInfo>(
+                    WorkspaceItemInfo().apply {
+                        intent = Intent().apply { component = expectedComponent }
+                    },
+                    activityInfo,
+                    expectedIconBlob,
+                    false, /* useLowResIcon */
+                )
+            )
+        val expectedAppInfo = AppInfo().apply { componentName = expectedComponent }
+        // When
+        val loader = LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
+        val actualIconRequest =
+            loader.getAppInfoIconRequestInfo(expectedAppInfo, activityInfo, workspaceIconRequests)
+        // Then
+        assertThat(actualIconRequest.iconBlob).isEqualTo(expectedIconBlob)
+        assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+    fun `When flag on then unarchived AllApps icons not loaded from db`() {
+        // Given
+        val activityInfo: LauncherActivityInfo = mock()
+        val applicationInfo: ApplicationInfo = mock<ApplicationInfo>().apply { isArchived = false }
+        whenever(activityInfo.applicationInfo).thenReturn(applicationInfo)
+        val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)
+        val expectedComponent = ComponentName("package", "class")
+        val workspaceIconRequests =
+            listOf(
+                IconRequestInfo<WorkspaceItemInfo>(
+                    WorkspaceItemInfo().apply {
+                        intent = Intent().apply { component = expectedComponent }
+                    },
+                    activityInfo,
+                    expectedIconBlob,
+                    false, /* useLowResIcon */
+                )
+            )
+        val expectedAppInfo = AppInfo().apply { componentName = expectedComponent }
+        // When
+        val loader = LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
+        val actualIconRequest =
+            loader.getAppInfoIconRequestInfo(expectedAppInfo, activityInfo, workspaceIconRequests)
+        // Then
+        assertThat(actualIconRequest.iconBlob).isNull()
+        assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo)
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+    fun `When flag on then archived AllApps icon not found on Workspace not loaded from db`() {
+        // Given
+        val activityInfo: LauncherActivityInfo = mock()
+        val applicationInfo: ApplicationInfo = mock<ApplicationInfo>().apply { isArchived = true }
+        whenever(activityInfo.applicationInfo).thenReturn(applicationInfo)
+        val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)
+        val expectedComponent = ComponentName("package", "class")
+        val workspaceIconRequests =
+            listOf(
+                IconRequestInfo<WorkspaceItemInfo>(
+                    WorkspaceItemInfo().apply {
+                        intent = Intent().apply { component = expectedComponent }
+                    },
+                    activityInfo,
+                    expectedIconBlob,
+                    false, /* useLowResIcon */
+                )
+            )
+        val expectedAppInfo =
+            AppInfo().apply { componentName = ComponentName("differentPkg", "differentClass") }
+        // When
+        val loader = LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
+        val actualIconRequest =
+            loader.getAppInfoIconRequestInfo(expectedAppInfo, activityInfo, workspaceIconRequests)
+        // Then
+        assertThat(actualIconRequest.iconBlob).isNull()
+        assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo)
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_RESTORE_ARCHIVED_APP_ICONS_FROM_DB)
+    fun `When flag off then archived AllApps icons not loaded from db`() {
+        // Given
+        val activityInfo: LauncherActivityInfo = mock()
+        val applicationInfo: ApplicationInfo = mock<ApplicationInfo>().apply { isArchived = true }
+        whenever(activityInfo.applicationInfo).thenReturn(applicationInfo)
+        val expectedIconBlob = byteArrayOf(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)
+        val workspaceIconRequests =
+            listOf(
+                IconRequestInfo<WorkspaceItemInfo>(
+                    WorkspaceItemInfo(),
+                    activityInfo,
+                    expectedIconBlob,
+                    false, /* useLowResIcon */
+                )
+            )
+        val expectedAppInfo = AppInfo()
+        // When
+        val loader = LoaderTask(app, bgAllAppsList, BgDataModel(), modelDelegate, launcherBinder)
+        val actualIconRequest =
+            loader.getAppInfoIconRequestInfo(expectedAppInfo, activityInfo, workspaceIconRequests)
+        // Then
+        assertThat(actualIconRequest.iconBlob).isNull()
+        assertThat(actualIconRequest.itemInfo).isEqualTo(expectedAppInfo)
+    }
+
+    @LauncherAppSingleton
+    @Component(modules = [AllModulesForTest::class])
+    interface TestComponent : LauncherAppComponent {
+        @Component.Builder
+        interface Builder : LauncherAppComponent.Builder {
+            @BindsInstance fun bindUserCache(userCache: UserCache): Builder
+
+            @BindsInstance fun bindLauncherModel(model: LauncherModel): Builder
+
+            @BindsInstance fun bindIconCache(iconCache: IconCache): Builder
+
+            override fun build(): TestComponent
+        }
+    }
 }
 
 private fun LoaderTask.runSyncOnBackgroundThread() {
diff --git a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
index d553f47..8db049c 100644
--- a/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
+++ b/tests/src/com/android/launcher3/model/WorkspaceItemProcessorExtraTest.kt
@@ -58,6 +58,7 @@
 import org.mockito.Mockito.RETURNS_DEEP_STUBS
 import org.mockito.Mockito.verify
 import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
 import org.mockito.kotlin.doAnswer
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
@@ -220,7 +221,8 @@
             )
             .commit()
         val widgetInfoCaptor = ArgumentCaptor.forClass(LauncherAppWidgetInfo::class.java)
-        verify(mockCursor).checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel))
+        verify(mockCursor)
+            .checkAndAddItem(widgetInfoCaptor.capture(), eq(mockBgDataModel), anyOrNull())
         val actualWidgetInfo = widgetInfoCaptor.value
         with(actualWidgetInfo) {
             assertThat(providerName).isEqualTo(expectedWidgetInfo.providerName)
@@ -271,7 +273,7 @@
         itemProcessorUnderTest.processItem()
 
         // Then
-        verify(mockCursor).checkAndAddItem(any(), any())
+        verify(mockCursor).checkAndAddItem(any(), any(), anyOrNull())
     }
 
     private fun createWorkspaceItemProcessorUnderTest(
diff --git a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
index 2e2b6cd..05cf926 100644
--- a/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
+++ b/tests/src/com/android/launcher3/nonquickstep/DeviceProfileDumpTest.kt
@@ -19,7 +19,6 @@
 import com.android.launcher3.AbstractDeviceProfileTest
 import com.android.launcher3.DeviceProfile
 import com.android.launcher3.Flags
-import com.android.launcher3.InvariantDeviceProfile
 import com.android.launcher3.util.rule.setFlags
 import org.junit.Before
 import org.junit.Test
@@ -46,7 +45,7 @@
     @Test
     fun dumpPortraitGesture() {
         initializeDevice(instance.deviceName, isGestureMode = true, isLandscape = false)
-        val dp = getDeviceProfileForGrid(instance.gridName)
+        val dp = context.appComponent.idp.getDeviceProfile(context)
         dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps
 
         assertDump(dp, instance.filename("Portrait"))
@@ -55,7 +54,7 @@
     @Test
     fun dumpPortrait3Button() {
         initializeDevice(instance.deviceName, isGestureMode = false, isLandscape = false)
-        val dp = getDeviceProfileForGrid(instance.gridName)
+        val dp = context.appComponent.idp.getDeviceProfile(context)
         dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps
 
         assertDump(dp, instance.filename("Portrait3Button"))
@@ -64,7 +63,7 @@
     @Test
     fun dumpLandscapeGesture() {
         initializeDevice(instance.deviceName, isGestureMode = true, isLandscape = true)
-        val dp = getDeviceProfileForGrid(instance.gridName)
+        val dp = context.appComponent.idp.getDeviceProfile(context)
         dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps
 
         val testName =
@@ -79,7 +78,7 @@
     @Test
     fun dumpLandscape3Button() {
         initializeDevice(instance.deviceName, isGestureMode = false, isLandscape = true)
-        val dp = getDeviceProfileForGrid(instance.gridName)
+        val dp = context.appComponent.idp.getDeviceProfile(context)
         dp.isTaskbarPresentInApps = instance.isTaskbarPresentInApps
 
         val testName =
@@ -101,26 +100,25 @@
                     deviceSpecFolded = deviceSpecs["twopanel-phone"]!!,
                     isLandscape = isLandscape,
                     isGestureMode = isGestureMode,
+                    gridName = instance.gridName,
                 )
             "tablet" ->
                 initializeVarsForTablet(
                     deviceSpec = deviceSpec,
                     isLandscape = isLandscape,
                     isGestureMode = isGestureMode,
+                    gridName = instance.gridName,
                 )
             else ->
                 initializeVarsForPhone(
                     deviceSpec = deviceSpec,
                     isVerticalBar = isLandscape,
                     isGestureMode = isGestureMode,
+                    gridName = instance.gridName,
                 )
         }
     }
 
-    private fun getDeviceProfileForGrid(gridName: String): DeviceProfile {
-        return InvariantDeviceProfile(context, gridName).getDeviceProfile(context)
-    }
-
     private fun assertDump(dp: DeviceProfile, filename: String) {
         assertDump(dp, folderName, filename)
     }
diff --git a/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt b/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
index ca2ef42..707c2c1 100644
--- a/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
+++ b/tests/src/com/android/launcher3/pm/InstallSessionHelperTest.kt
@@ -23,6 +23,7 @@
 import android.content.pm.PackageInstaller
 import android.content.pm.PackageManager
 import android.graphics.Bitmap
+import android.os.Process.myUserHandle
 import android.os.UserHandle
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -51,7 +52,7 @@
     private val sandboxContext = spy(launcherModelHelper.sandboxContext)
     private val packageManager = sandboxContext.packageManager
     private val expectedAppPackage = "expectedAppPackage"
-    private val expectedInstallerPackage = "expectedInstallerPackage"
+    private val expectedInstallerPackage = sandboxContext.packageName
     private val mockPackageInstaller: PackageInstaller = mock()
 
     private lateinit var installSessionHelper: InstallSessionHelper
@@ -60,7 +61,6 @@
     @Before
     fun setup() {
         whenever(packageManager.packageInstaller).thenReturn(mockPackageInstaller)
-        whenever(sandboxContext.packageName).thenReturn(expectedInstallerPackage)
         launcherApps = sandboxContext.spyService(LauncherApps::class.java)
         installSessionHelper = InstallSessionHelper(sandboxContext)
     }
@@ -73,14 +73,14 @@
                 sessionId = 0
                 installerPackageName = expectedInstallerPackage
                 appPackageName = expectedAppPackage
-                userId = 0
+                userId = myUserHandle().identifier
             }
         val expectedVerifiedSession2 =
             PackageInstaller.SessionInfo().apply {
                 sessionId = 1
                 installerPackageName = expectedInstallerPackage
                 appPackageName = "app2"
-                userId = 0
+                userId = myUserHandle().identifier
             }
         val expectedSessions = listOf(expectedVerifiedSession1, expectedVerifiedSession2)
         whenever(launcherApps.allPackageInstallerSessions).thenReturn(expectedSessions)
@@ -97,13 +97,13 @@
             PackageInstaller.SessionInfo().apply {
                 installerPackageName = expectedInstallerPackage
                 appPackageName = expectedAppPackage
-                userId = 0
+                userId = myUserHandle().identifier
             }
         whenever(launcherApps.allPackageInstallerSessions)
             .thenReturn(listOf(expectedVerifiedSession))
         // When
         val actualSession =
-            installSessionHelper.getActiveSessionInfo(UserHandle(0), expectedAppPackage)
+            installSessionHelper.getActiveSessionInfo(myUserHandle(), expectedAppPackage)
         // Then
         assertThat(actualSession).isEqualTo(expectedVerifiedSession)
     }
@@ -116,7 +116,7 @@
                 sessionId = 1
                 installerPackageName = expectedInstallerPackage
                 appPackageName = expectedAppPackage
-                userId = 0
+                userId = myUserHandle().identifier
             }
         whenever(mockPackageInstaller.getSessionInfo(1)).thenReturn(expectedSession)
         // When
@@ -147,14 +147,14 @@
                 sessionId = 0
                 installerPackageName = expectedInstallerPackage
                 appPackageName = expectedAppPackage
-                userId = 0
+                userId = myUserHandle().identifier
             }
         val expectedVerifiedSession2 =
             PackageInstaller.SessionInfo().apply {
                 sessionId = 1
                 installerPackageName = expectedInstallerPackage
                 appPackageName = "app2"
-                userId = 0
+                userId = myUserHandle().identifier
             }
         val expectedSessions = listOf(expectedVerifiedSession1, expectedVerifiedSession2)
         whenever(launcherApps.allPackageInstallerSessions).thenReturn(expectedSessions)
@@ -174,7 +174,7 @@
                 sessionId = 1
                 installerPackageName = expectedInstallerPackage
                 appPackageName = "app2"
-                userId = 0
+                userId = myUserHandle().identifier
             }
         whenever(launcherApps.allPackageInstallerSessions).thenReturn(listOf(expectedSession))
         // When
@@ -196,7 +196,7 @@
                 sessionId = 1
                 installerPackageName = expectedInstallerPackage
                 appPackageName = "app2"
-                userId = 0
+                userId = myUserHandle().identifier
             }
         whenever(launcherApps.allPackageInstallerSessions).thenReturn(listOf(expectedSession))
         // When
@@ -219,7 +219,7 @@
                 sessionId = 1
                 installerPackageName = expectedInstallerPackage
                 appPackageName = "appPackage"
-                userId = 0
+                userId = myUserHandle().identifier
                 appIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8)
                 appLabel = "appLabel"
                 installReason = PackageManager.INSTALL_REASON_USER
@@ -249,7 +249,7 @@
                 sessionId = 1
                 installerPackageName = expectedInstallerPackage
                 appPackageName = "appPackage"
-                userId = 0
+                userId = myUserHandle().identifier
                 appIcon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8)
                 appLabel = "appLabel"
                 installReason = PackageManager.INSTALL_REASON_USER
diff --git a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
index ae54e95..2531f6b 100644
--- a/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
+++ b/tests/src/com/android/launcher3/popup/SystemShortcutTest.java
@@ -70,7 +70,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.ApiWrapper;
+import com.android.launcher3.util.AllModulesForTest;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.LauncherModelHelper.SandboxModelContext;
 import com.android.launcher3.util.LauncherMultivalentJUnit;
@@ -81,9 +81,6 @@
 import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
 import com.android.launcher3.widget.picker.model.data.WidgetPickerData;
 
-import dagger.BindsInstance;
-import dagger.Component;
-
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -94,6 +91,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import dagger.BindsInstance;
+import dagger.Component;
+
 @SmallTest
 @RunWith(LauncherMultivalentJUnit.class)
 public class SystemShortcutTest {
@@ -109,7 +109,6 @@
     private AppInfo mAppInfo;
 
     @Mock UserCache mUserCache;
-    @Mock ApiWrapper mApiWrapper;
     @Mock UserIconInfo mUserIconInfo;
     @Mock LauncherActivityInfo mLauncherActivityInfo;
     @Mock ApplicationInfo mApplicationInfo;
@@ -121,9 +120,8 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mSandboxContext.initDaggerComponent(
-                DaggerSystemShortcutTest_TestComponent.builder().bindApiWrapper(
-                        ApiWrapper.INSTANCE.get(mSandboxContext)));
-        mSandboxContext.putObject(UserCache.INSTANCE, mUserCache);
+                DaggerSystemShortcutTest_TestComponent.builder().bindUserCache(mUserCache)
+        );
         mTestContext = new TestSandboxModelContextWrapper(mSandboxContext) {
             @Override
             public StatsLogManager getStatsLogManager() {
@@ -414,14 +412,13 @@
     }
 
     @LauncherAppSingleton
-    @Component
+    @Component(modules = { AllModulesForTest.class })
     interface TestComponent extends LauncherAppComponent {
         @Component.Builder
         interface Builder extends LauncherAppComponent.Builder {
-            @BindsInstance Builder bindApiWrapper(ApiWrapper wrapper);
-
-            @Override
-            TestComponent build();
+            @BindsInstance
+            SystemShortcutTest.TestComponent.Builder bindUserCache(UserCache userCache);
+            @Override LauncherAppComponent build();
         }
     }
 }
diff --git a/tests/src/com/android/launcher3/tablet/TaplIsTabletTest.kt b/tests/src/com/android/launcher3/tablet/TaplIsTabletTest.kt
deleted file mode 100644
index a6de607..0000000
--- a/tests/src/com/android/launcher3/tablet/TaplIsTabletTest.kt
+++ /dev/null
@@ -1,48 +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.tablet
-
-import android.platform.test.rule.AllowedDevices
-import android.platform.test.rule.DeviceProduct
-import com.android.launcher3.Launcher
-import com.android.launcher3.ui.AbstractLauncherUiTest
-import junit.framework.TestCase.assertFalse
-import junit.framework.TestCase.assertTrue
-import org.junit.Test
-
-class TaplIsTabletTest : AbstractLauncherUiTest<Launcher>() {
-
-    /** Investigating b/366237798 by isolating and seeing flake rate of mLauncher.isTablet */
-    @Test
-    @AllowedDevices(
-        DeviceProduct.CF_FOLDABLE,
-        DeviceProduct.CF_TABLET,
-        DeviceProduct.TANGORPRO,
-        DeviceProduct.FELIX,
-        DeviceProduct.COMET,
-    )
-    fun isTabletShouldBeTrue() {
-        assertTrue(mLauncher.isTablet)
-    }
-
-    /** Investigating b/366237798 by isolating and seeing flake rate of mLauncher.isTablet */
-    @Test
-    @AllowedDevices(DeviceProduct.CF_PHONE, DeviceProduct.CHEETAH)
-    fun isTabletShouldBeFalse() {
-        assertFalse(mLauncher.isTablet)
-    }
-}
diff --git a/tests/src/com/android/launcher3/ui/BaseLauncherTaplTest.java b/tests/src/com/android/launcher3/ui/BaseLauncherTaplTest.java
index 8449853..9fa1500 100644
--- a/tests/src/com/android/launcher3/ui/BaseLauncherTaplTest.java
+++ b/tests/src/com/android/launcher3/ui/BaseLauncherTaplTest.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.ui;
 
+import static android.os.Process.myUserHandle;
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -219,7 +220,8 @@
 
     protected void clearPackageData(String pkg) throws IOException, InterruptedException {
         assertTrue("pm clear command failed",
-                mDevice.executeShellCommand("pm clear " + pkg)
+                mDevice.executeShellCommand(
+                        String.format("pm clear --user %d %s", myUserHandle().getIdentifier(), pkg))
                         .contains("Success"));
         assertTrue("pm wait-for-handler command failed",
                 mDevice.executeShellCommand("pm wait-for-handler")
diff --git a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
index e5c5c19..e38cfec 100644
--- a/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
+++ b/tests/src/com/android/launcher3/ui/PortraitLandscapeRunner.java
@@ -1,9 +1,13 @@
 package com.android.launcher3.ui;
 
+import static com.android.launcher3.LauncherPrefs.FIXED_LANDSCAPE_MODE;
+
 import android.util.Log;
 import android.view.Surface;
 
+import com.android.launcher3.Flags;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherPrefs;
 import com.android.launcher3.tapl.TestHelpers;
 import com.android.launcher3.util.rule.FailureWatcher;
 
@@ -67,10 +71,12 @@
                     Log.e(TAG, "Error", e);
                     throw e;
                 } finally {
+
                     mTest.mDevice.setOrientationNatural();
                     mTest.executeOnLauncher(launcher ->
                     {
                         if (launcher != null) {
+                            LauncherPrefs.get(launcher).put(FIXED_LANDSCAPE_MODE, false);
                             launcher.getRotationHelper().forceAllowRotationForTesting(false);
                         }
                     });
@@ -90,12 +96,20 @@
             }
 
             private void evaluateInLandscape() throws Throwable {
+                mTest.executeOnLauncher(launcher -> LauncherPrefs.get(launcher)
+                        .put(FIXED_LANDSCAPE_MODE, shouldHaveFixedLandscape(launcher)));
                 mTest.mDevice.setOrientationLeft();
                 mTest.mLauncher.setExpectedRotation(Surface.ROTATION_90);
                 AbstractLauncherUiTest.checkDetectedLeaks(mTest.mLauncher, true);
                 base.evaluate();
                 mTest.getDevice().pressHome();
             }
+
+            private boolean shouldHaveFixedLandscape(Launcher launcher) {
+                return Flags.oneGridSpecs()
+                        && !launcher.getDeviceProfile().isTablet
+                        && !launcher.getDeviceProfile().isMultiDisplay;
+            }
         };
     }
 }
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
index 342eedf..1338e60 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3Test.java
@@ -21,7 +21,6 @@
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.Launcher;
 
 import org.junit.Test;
@@ -31,9 +30,8 @@
 @RunWith(AndroidJUnit4.class)
 public class TaplTestsLauncher3Test extends AbstractLauncherUiTest<Launcher> {
 
-    @ScreenRecord // b/322823478
     @Test
-    public void testDevicePressMenu() throws Exception {
+    public void testDevicePressMenu() {
         mDevice.pressMenu();
         mDevice.waitForIdle();
         executeOnLauncher(
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index 4dba78a..b6c1135 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -26,6 +26,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
+import android.os.Process;
 import android.util.Log;
 import android.view.View;
 
@@ -73,7 +74,9 @@
 
     @Before
     public void setUp() throws Exception {
-        String output = executeShellCommand("pm create-user --profileOf 0 --managed TestProfile");
+        String output = executeShellCommand(String.format(
+                "pm create-user --profileOf %d --managed TestProfile",
+                Process.myUserHandle().getIdentifier()));
         updateWorkProfileSetupSuccessful("pm create-user", output);
 
         String[] tokens = output.split("\\s+");
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 2fb7987..7e8d759 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -51,6 +51,7 @@
 import com.android.launcher3.util.BlockingBroadcastReceiver;
 import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
 import com.android.launcher3.util.TestUtil;
+import com.android.launcher3.util.rule.ScreenRecordRule;
 import com.android.launcher3.util.rule.ShellCommandRule;
 
 import org.junit.Before;
@@ -74,6 +75,9 @@
     @Rule
     public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher();
 
+    @Rule
+    public ScreenRecordRule mScreenRecordRule = new ScreenRecordRule();
+
     private String mCallbackAction;
     private String mShortcutId;
     private int mAppWidgetId;
@@ -87,6 +91,7 @@
     @Test
     public void testEmpty() throws Throwable { /* needed while the broken tests are being fixed */ }
 
+    @ScreenRecordRule.ScreenRecord // b/386243192
     @Test
     public void testPinWidgetNoConfig() throws Throwable {
         runTest("pinWidgetNoConfig", true, (info, view) -> info instanceof LauncherAppWidgetInfo
@@ -95,6 +100,7 @@
                         .equals(AppWidgetNoConfig.class.getName()));
     }
 
+    @ScreenRecordRule.ScreenRecord // b/386243192
     @Test
     public void testPinWidgetNoConfig_customPreview() throws Throwable {
         // Command to set custom preview
@@ -108,6 +114,7 @@
                         .equals(AppWidgetNoConfig.class.getName()), command);
     }
 
+    @ScreenRecordRule.ScreenRecord // b/386243192
     @Test
     public void testPinWidgetWithConfig() throws Throwable {
         runTest("pinWidgetWithConfig", true,
@@ -117,6 +124,7 @@
                                 .equals(AppWidgetWithConfig.class.getName()));
     }
 
+    @ScreenRecordRule.ScreenRecord // b/386243192
     @Test
     public void testPinShortcut() throws Throwable {
         // Command to set the shortcut id
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
index 460ffc4..3e3e643 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
@@ -51,7 +51,6 @@
     @Test
     @PortraitLandscape
     public void testDragIcon() throws Throwable {
-        mLauncher.enableDebugTracing(); // b/289161193
         commitTransactionAndLoadHome(new FavoriteItemsTransaction(mTargetContext));
 
         waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
@@ -72,7 +71,6 @@
                 TestUtil.DEFAULT_UI_TIMEOUT);
         assertNotNull("Widget not found on the workspace", widget);
         widget.launch(getAppPackageName());
-        mLauncher.disableDebugTracing(); // b/289161193
     }
 
     /**
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
index 2edd129..e544b19 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplTwoPanelWorkspaceTest.java
@@ -37,7 +37,6 @@
 import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
 import com.android.launcher3.util.LauncherLayoutBuilder;
 import com.android.launcher3.util.TestUtil;
-import com.android.launcher3.util.rule.ScreenRecordRule;
 
 import org.junit.After;
 import org.junit.Before;
@@ -283,7 +282,6 @@
 
     @Test
     @PortraitLandscape
-    @ScreenRecordRule.ScreenRecord // b/330232490
     public void testEmptyPagesGetRemovedIfBothPagesAreEmpty() {
         Workspace workspace = mLauncher.getWorkspace();
 
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
index 237f2a9..cab1ebe 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
@@ -114,8 +114,15 @@
      * Similar to {@link TaplWorkspaceTest#testWorkspace} but here we also make sure we can delete
      * the pages.
      */
+    @ScreenRecord // b/381918059
     @Test
-    public void testAddAndDeletePageAndFling() {
+    public void testAddAndDeletePageAndFling() throws Exception {
+        // Set workspace  that includes the chrome Activity app icon on the hotseat.
+        LauncherLayoutBuilder builder = new LauncherLayoutBuilder()
+                .atHotseat(0).putApp("com.android.chrome", "com.google.android.apps.chrome.Main");
+        mLauncherLayout = TestUtil.setLauncherDefaultLayout(mTargetContext, builder);
+        reinitializeLauncherData();
+
         Workspace workspace = mLauncher.getWorkspace();
         // Get the first app from the hotseat
         HomeAppIcon hotSeatIcon = workspace.getHotseatAppIcon(0);
diff --git a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
index c852729..38970fe 100644
--- a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java
@@ -23,7 +23,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.net.Uri;
@@ -37,6 +36,7 @@
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.allapps.AllAppsRecyclerView;
 import com.android.launcher3.celllayout.FavoriteItemsTransaction;
+import com.android.launcher3.dagger.LauncherComponentProvider;
 import com.android.launcher3.icons.mono.ThemedIconDrawable;
 import com.android.launcher3.popup.ArrowPopup;
 import com.android.launcher3.util.BaseLauncherActivityTest;
@@ -61,8 +61,7 @@
         setThemeEnabled(false);
         new FavoriteItemsTransaction(targetContext()).commit();
         loadLauncherSync();
-        goToState(LauncherState.ALL_APPS);
-        freezeAllApps();
+        switchToAllApps();
 
         scrollToAppIcon(APP_NAME);
         BubbleTextView btv = getFromLauncher(
@@ -76,8 +75,7 @@
         setThemeEnabled(false);
         new FavoriteItemsTransaction(targetContext()).commit();
         loadLauncherSync();
-        goToState(LauncherState.ALL_APPS);
-        freezeAllApps();
+        switchToAllApps();
 
         scrollToAppIcon(TEST_APP_NAME);
         BubbleTextView btv = getFromLauncher(l -> findBtv(TEST_APP_NAME, l.getAppsView()));
@@ -95,8 +93,7 @@
         setThemeEnabled(true);
         new FavoriteItemsTransaction(targetContext()).commit();
         loadLauncherSync();
-        goToState(LauncherState.ALL_APPS);
-        freezeAllApps();
+        switchToAllApps();
 
         scrollToAppIcon(APP_NAME);
         BubbleTextView btv = getFromLauncher(l ->
@@ -109,8 +106,7 @@
     public void testShortcutIconWithTheme() throws Exception {
         setThemeEnabled(true);
         loadLauncherSync();
-        goToState(LauncherState.ALL_APPS);
-        freezeAllApps();
+        switchToAllApps();
 
         scrollToAppIcon(TEST_APP_NAME);
         BubbleTextView btv = getFromLauncher(l -> findBtv(TEST_APP_NAME, l.getAppsView()));
@@ -143,7 +139,7 @@
         return icon;
     }
 
-    private void setThemeEnabled(boolean isEnabled) throws Exception {
+    private void setThemeEnabled(boolean isEnabled) {
         Uri uri = new Uri.Builder()
                 .scheme(ContentResolver.SCHEME_CONTENT)
                 .authority(targetContext().getPackageName() + ".grid_control")
@@ -151,11 +147,17 @@
                 .build();
         ContentValues values = new ContentValues();
         values.put("boolean_value", isEnabled);
-        try (ContentProviderClient client = targetContext().getContentResolver()
-                .acquireContentProviderClient(uri)) {
-            int result = client.update(uri, values, null);
-            assertTrue(result > 0);
-        }
+
+        int result = LauncherComponentProvider.get(targetContext()).getGridCustomizationsProxy()
+                .update(uri, values, null, null);
+        assertTrue(result > 0);
+    }
+
+    private void switchToAllApps() {
+        goToState(LauncherState.ALL_APPS);
+        waitForState("Launcher internal state didn't switch to All Apps",
+                () -> LauncherState.ALL_APPS);
+        freezeAllApps();
     }
 
     private void scrollToAppIcon(String appName) {
diff --git a/tests/src/com/android/launcher3/util/WallpaperThemeManagerTest.kt b/tests/src/com/android/launcher3/util/WallpaperThemeManagerTest.kt
new file mode 100644
index 0000000..4c8fd8a
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/WallpaperThemeManagerTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util
+
+import android.app.Activity
+import android.content.ComponentCallbacks
+import android.content.res.Configuration
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.any
+import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
+import com.android.dx.mockito.inline.extended.ExtendedMockito.eq
+import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
+import com.android.dx.mockito.inline.extended.ExtendedMockito.times
+import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
+import com.android.dx.mockito.inline.extended.StaticMockitoSession
+import com.android.launcher3.util.WallpaperThemeManager.Companion.setWallpaperDependentTheme
+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.Captor
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.never
+import org.mockito.kotlin.whenever
+
+/** Tests for WallpaperThemeManager */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WallpaperThemeManagerTest {
+
+    @get:Rule val context = SandboxApplication()
+
+    @Mock lateinit var activity: Activity
+    @Captor lateinit var callbacksCaptor: ArgumentCaptor<ComponentCallbacks>
+
+    private lateinit var mockSession: StaticMockitoSession
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        mockSession = mockitoSession().spyStatic(Themes::class.java).startMocking()
+
+        doReturn(1).`when`<Int> { Themes.getActivityThemeRes(any()) }
+        doReturn(context).whenever(activity).applicationContext
+    }
+
+    @After
+    fun tearDown() {
+        mockSession.finishMocking()
+    }
+
+    @Test
+    fun `correct theme set on activity create`() {
+        activity.setWallpaperDependentTheme()
+        verify(activity, times(1)).setTheme(eq(1))
+    }
+
+    @Test
+    fun `ignores update if theme does not change`() {
+        activity.setWallpaperDependentTheme()
+        verify(activity).registerComponentCallbacks(callbacksCaptor.capture())
+        callbacksCaptor.value.onConfigurationChanged(Configuration())
+        verify(activity, never()).recreate()
+    }
+
+    @Test
+    fun `activity recreated if theme changes`() {
+        activity.setWallpaperDependentTheme()
+        verify(activity).registerComponentCallbacks(callbacksCaptor.capture())
+
+        doReturn(3).`when`<Int> { Themes.getActivityThemeRes(any()) }
+        callbacksCaptor.value.onConfigurationChanged(Configuration())
+        verify(activity, times(1)).recreate()
+    }
+}
diff --git a/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java b/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
index a0f227e..d093bf7 100644
--- a/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
+++ b/tests/src/com/android/launcher3/util/rule/ShellCommandRule.java
@@ -24,7 +24,6 @@
 import android.os.Process;
 
 import androidx.annotation.Nullable;
-import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.UiDevice;
 
 import com.android.systemui.shared.system.PackageManagerWrapper;
@@ -92,8 +91,9 @@
      * Grants the launcher permission to bind widgets.
      */
     public static ShellCommandRule grantWidgetBind() {
-        return new ShellCommandRule("appwidget grantbind --package "
-                + InstrumentationRegistry.getTargetContext().getPackageName(), null);
+        return new ShellCommandRule(String.format("appwidget grantbind --package %s --user %d",
+                getInstrumentation().getTargetContext().getPackageName(),
+                Process.myUserHandle().getIdentifier()), null);
     }
 
     /**
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
index bbcc6a8..033cfb0 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIconMenu.java
@@ -63,5 +63,12 @@
         return new SplitScreenMenuItem(mLauncher, menuItem);
     }
 
+    /** Returns the Bubble menu item. */
+    public BubbleMenuItem getBubbleMenuItem() {
+        final UiObject2 menuItem = mLauncher.waitForObjectInContainer(mDeepShortcutsContainer,
+                AppIcon.getMenuItemSelector("Bubble", mLauncher));
+        return new BubbleMenuItem(mLauncher, menuItem);
+    }
+
     protected abstract AppIconMenuItem createMenuItem(UiObject2 menuItem);
 }
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index b15afc1..214f158 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -157,12 +157,7 @@
              LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                      "dismissing all tasks")) {
             final BySelector clearAllSelector = mLauncher.getOverviewObjectSelector("clear_all");
-            for (int i = 0;
-                    i < FLINGS_FOR_DISMISS_LIMIT
-                            && !verifyActiveContainer().hasObject(clearAllSelector);
-                    ++i) {
-                flingForwardImpl();
-            }
+            flingForwardUntilClearAllVisibleImpl();
 
             final Runnable clickClearAll = () -> mLauncher.clickLauncherObject(
                     mLauncher.waitForObjectInContainer(verifyActiveContainer(),
@@ -183,6 +178,26 @@
     }
 
     /**
+     * Scrolls until Clear-all button is visible.
+     */
+    public void flingForwardUntilClearAllVisible() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            flingForwardUntilClearAllVisibleImpl();
+        }
+    }
+
+    private void flingForwardUntilClearAllVisibleImpl() {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "flinging forward to clear all")) {
+            final BySelector clearAllSelector = mLauncher.getOverviewObjectSelector("clear_all");
+            for (int i = 0; i < FLINGS_FOR_DISMISS_LIMIT && !verifyActiveContainer().hasObject(
+                    clearAllSelector); ++i) {
+                flingForwardImpl();
+            }
+        }
+    }
+
+    /**
      * Touch to the right of current task. This should dismiss overview and go back to Workspace.
      */
     public Workspace touchOutsideFirstTask() {
@@ -440,7 +455,7 @@
                     "Not expecting an actions bar: device is tablet and task is not centered");
             return false;
         }
-        if (task.isGrouped() && (!mLauncher.isAppPairsEnabled() || !isTablet)) {
+        if (task.isGrouped() && !isTablet) {
             testLogD(TAG, "Not expecting an actions bar: device is phone and task is split");
             // Overview actions aren't visible for split screen tasks, except for save app pair
             // button on tablets.
diff --git a/tests/tapl/com/android/launcher3/tapl/BubbleMenuItem.kt b/tests/tapl/com/android/launcher3/tapl/BubbleMenuItem.kt
new file mode 100644
index 0000000..77391f1
--- /dev/null
+++ b/tests/tapl/com/android/launcher3/tapl/BubbleMenuItem.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.tapl
+
+import androidx.test.uiautomator.UiObject2
+
+/**
+ * A class representing the Bubble menu item in the app long-press menu, which moves the app into a
+ * bubble.
+ */
+class BubbleMenuItem(
+    private val launcher: LauncherInstrumentation,
+    private val uiObject: UiObject2,
+) {
+
+    fun click() {
+        launcher.addContextLayer("want to create bubble from app long-press menu").use {
+            LauncherInstrumentation.log(
+                "clicking on bubble menu item ${uiObject.visibleCenter} in ${
+                    launcher.getVisibleBounds(
+                        uiObject
+                    )
+                }"
+            )
+            launcher.clickLauncherObject(uiObject)
+        }
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Home.java b/tests/tapl/com/android/launcher3/tapl/Home.java
index 85e28e8..4055100 100644
--- a/tests/tapl/com/android/launcher3/tapl/Home.java
+++ b/tests/tapl/com/android/launcher3/tapl/Home.java
@@ -60,7 +60,8 @@
 
     @Override
     protected boolean zeroButtonToOverviewGestureStateTransitionWhileHolding() {
-        return true;
+        return !mLauncher.isRecentsWindowEnabled()
+                || super.zeroButtonToOverviewGestureStateTransitionWhileHolding();
     }
 
     @Override
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index fac73d3..e0d2f39 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -41,6 +41,7 @@
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.res.Configuration;
@@ -79,7 +80,6 @@
 import androidx.test.uiautomator.Until;
 
 import com.android.launcher3.testing.shared.ResourceUtils;
-import com.android.launcher3.testing.shared.TestInformationRequest;
 import com.android.launcher3.testing.shared.TestProtocol;
 import com.android.systemui.shared.system.QuickStepContract;
 
@@ -211,6 +211,8 @@
     private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE;
     private int mPointerCount = 0;
 
+    private boolean mWaitingForMotionUpEvent;
+
     private static Pattern getKeyEventPattern(String action, String keyCode) {
         return Pattern.compile("Key event: KeyEvent.*action=" + action + ".*keyCode=" + keyCode);
     }
@@ -377,10 +379,8 @@
         }
     }
 
-    Bundle getTestInfo(TestInformationRequest request) {
-        Bundle extra = new Bundle();
-        extra.putParcelable(TestProtocol.TEST_INFO_REQUEST_FIELD, request);
-        return getTestInfo(request.getRequestName(), null, extra);
+    Bundle getTestInfo(Intent request) {
+        return getTestInfo(request.getAction(), null, request.getExtras());
     }
 
     Insets getTargetInsets() {
@@ -754,7 +754,29 @@
         }
     }
 
+    private void cleanUpInputStream() {
+        if (!mWaitingForMotionUpEvent) {
+            return;
+        }
+        long downTime = SystemClock.uptimeMillis();
+        MotionEvent cleanUpEvent = getMotionEvent(
+                downTime,
+                downTime,
+                MotionEvent.ACTION_UP,
+                0,
+                0,
+                InputDevice.SOURCE_TOUCHSCREEN,
+                Configurator.getInstance().getToolType());
+        log("Test failed while a ACTION_UP event was still pending. "
+                + "Cleaning up the input stream by sending an ACTION_UP event forcefully: "
+                + "event= " + cleanUpEvent);
+
+        injectEventUnchecked(cleanUpEvent);
+        mWaitingForMotionUpEvent = false;
+    }
+
     void fail(String message) {
+        cleanUpInputStream();
         checkForAnomaly();
         if (mOnFailure != null) mOnFailure.run();
         Assert.fail(formatSystemHealthMessage(formatErrorWithEvents(
@@ -907,7 +929,11 @@
                     waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
                     waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
                     waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
-                    waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
+                    if (isTaskbarShownOnHome()) {
+                        waitForSystemLauncherObject(TASKBAR_RES_ID);
+                    } else {
+                        waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
+                    }
 
                     return waitForLauncherObject(WORKSPACE_RES_ID);
                 }
@@ -925,7 +951,9 @@
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
                     waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
-                    waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
+                    if (isTransientTaskbar()) {
+                        waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
+                    }
                     waitUntilSystemLauncherObjectGone(SPLIT_PLACEHOLDER_RES_ID);
                     waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
 
@@ -937,7 +965,8 @@
                     waitUntilSystemLauncherObjectGone(OVERVIEW_RES_ID);
                     waitUntilLauncherObjectGone(KEYBOARD_QUICK_SWITCH_RES_ID);
 
-                    if (is3PLauncher() && isTablet() && !isTransientTaskbar()) {
+                    if ((is3PLauncher() && isTablet() && !isTransientTaskbar())
+                            || isTaskbarShownOnHome()) {
                         waitForSystemLauncherObject(TASKBAR_RES_ID);
                     } else {
                         waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
@@ -956,7 +985,7 @@
                     waitUntilLauncherObjectGone(APPS_RES_ID);
                     waitUntilLauncherObjectGone(WORKSPACE_RES_ID);
                     waitUntilLauncherObjectGone(WIDGETS_RES_ID);
-                    if (isTablet() && !is3PLauncher()) {
+                    if (isTablet() && !is3PLauncher() && !isRecentsWindowEnabled()) {
                         waitForSystemLauncherObject(TASKBAR_RES_ID);
                     } else {
                         waitUntilSystemLauncherObjectGone(TASKBAR_RES_ID);
@@ -1010,6 +1039,11 @@
         }
     }
 
+    boolean isRecentsWindowEnabled() {
+        return getTestInfo(TestProtocol.REQUEST_IS_RECENTS_WINDOW_ENABLED)
+                .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    }
+
     public void waitForModelQueueCleared() {
         getTestInfo(TestProtocol.REQUEST_MODEL_QUEUE_CLEARED);
     }
@@ -1245,8 +1279,6 @@
         if (getNavigationModel() == NavigationModel.ZERO_BUTTON
                 || isThreeFingerTrackpadGesture) {
             final Point displaySize = getRealDisplaySize();
-            // TODO(b/225505986): change startY and endY back to displaySize.y / 2 once the
-            //  issue is solved.
             int startX = isThreeFingerTrackpadGesture ? displaySize.x / 4 : 0;
             int endX = isThreeFingerTrackpadGesture ? displaySize.x * 3 / 4 : displaySize.x / 2;
             linearGesture(startX, displaySize.y / 4, endX, displaySize.y / 4,
@@ -2012,11 +2044,6 @@
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
-    boolean isAppPairsEnabled() {
-        return getTestInfo(TestProtocol.REQUEST_FLAG_ENABLE_APP_PAIRS).getBoolean(
-                TestProtocol.TEST_INFO_RESPONSE_FIELD);
-    }
-
     public void sendPointer(long downTime, long currentTime, int action, Point point,
             GestureScope gestureScope) {
         sendPointer(downTime, currentTime, action, point, gestureScope,
@@ -2024,8 +2051,38 @@
     }
 
     private void injectEvent(InputEvent event) {
-        assertTrue("injectInputEvent failed: event=" + event,
-                mInstrumentation.getUiAutomation().injectInputEvent(event, true, false));
+        if (event instanceof MotionEvent motionEvent) {
+            switch (motionEvent.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    assertTrue("Attempting to inject a second ACTION_DOWN event before a "
+                                    + "ACTION_UP event: " + event,
+                            !mWaitingForMotionUpEvent);
+                    mWaitingForMotionUpEvent = true;
+                    break;
+                case MotionEvent.ACTION_CANCEL:
+                case MotionEvent.ACTION_UP:
+                    assertTrue("Attempting to inject an unexpected ACTION_UP event: " + event,
+                            mWaitingForMotionUpEvent);
+                    mWaitingForMotionUpEvent = false;
+
+                    break;
+                default:
+                    // Nothing to do
+                    break;
+            }
+        }
+        assertTrue("injectInputEvent failed: event=" + event, injectEventUnchecked(event));
+    }
+
+    private boolean injectEventUnchecked(InputEvent event) {
+        boolean result = mInstrumentation.getUiAutomation().injectInputEvent(event, true, false);
+
+        // Only MotionEvents need to be recycled.
+        if (event instanceof MotionEvent motionEvent) {
+            motionEvent.recycle();
+        }
+
+        return result;
     }
 
     public void sendPointer(long downTime, long currentTime, int action, Point point,
@@ -2079,12 +2136,13 @@
                 downTime, currentTime, action, point.x, point.y, pointerCount,
                 mTrackpadGestureType)
                 : getMotionEvent(downTime, currentTime, action, point.x, point.y, source, toolType);
+        int button = isRightClick ? MotionEvent.BUTTON_SECONDARY : MotionEvent.BUTTON_PRIMARY;
+        if (action == MotionEvent.ACTION_BUTTON_PRESS) {
+            event.setButtonState(event.getButtonState() | button);
+        }
         if (action == MotionEvent.ACTION_BUTTON_PRESS
                 || action == MotionEvent.ACTION_BUTTON_RELEASE) {
-            event.setActionButton(MotionEvent.BUTTON_PRIMARY);
-        }
-        if (isRightClick) {
-            event.setButtonState(event.getButtonState() | MotionEvent.BUTTON_SECONDARY);
+            event.setActionButton(button);
         }
         injectEvent(event);
     }
@@ -2195,11 +2253,17 @@
         sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter,
                 GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
                 /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
+        sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_PRESS, targetCenter,
+                GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
+                /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
         try {
             expectEvent(TestProtocol.SEQUENCE_MAIN, longClickEvent);
             final UiObject2 result = waitForLauncherObject(resName);
             return result;
         } finally {
+            sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_RELEASE, targetCenter,
+                    GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
+                    /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
             sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, targetCenter,
                     GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_TOUCHSCREEN,
                     /* isRightClick= */ true, MotionEvent.TOOL_TYPE_STYLUS);
@@ -2214,11 +2278,17 @@
         sendPointer(downTime, downTime, MotionEvent.ACTION_DOWN, targetCenter,
                 GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
                 /* isRightClick= */ true);
+        sendPointer(downTime, downTime, MotionEvent.ACTION_BUTTON_PRESS, targetCenter,
+                GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
+                /* isRightClick= */ true);
         try {
             expectEvent(TestProtocol.SEQUENCE_MAIN, rightClickEvent);
             final UiObject2 result = waitForLauncherObject(resName);
             return result;
         } finally {
+            sendPointer(downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_BUTTON_RELEASE,
+                    targetCenter, GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
+                    /* isRightClick= */ true);
             sendPointer(downTime, SystemClock.uptimeMillis(), ACTION_UP, targetCenter,
                     GestureScope.DONT_EXPECT_PILFER, InputDevice.SOURCE_MOUSE,
                     /* isRightClick= */ true);
@@ -2358,6 +2428,12 @@
                 .getBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
+    /** Whether taskbar will be shown on home for current default display. */
+    public boolean isTaskbarShownOnHome() {
+        return getTestInfo(TestProtocol.REQUEST_TASKBAR_SHOWN_ON_HOME).getBoolean(
+                TEST_INFO_RESPONSE_FIELD);
+    }
+
     public boolean isImeDocked() {
         return getTestInfo(TestProtocol.REQUEST_TASKBAR_IME_DOCKED).getBoolean(
                 TestProtocol.TEST_INFO_RESPONSE_FIELD);
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 8512d73..02c6630 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -24,7 +24,6 @@
 import android.graphics.Rect;
 
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.BySelector;
 import androidx.test.uiautomator.UiObject2;
@@ -74,16 +73,18 @@
             return getCombinedSplitTaskHeight();
         }
 
-        UiObject2 taskSnapshot1 = findObjectInTask((isDesktop() ? DESKTOP : DEFAULT).snapshotRes);
-        return taskSnapshot1.getVisibleBounds().height();
+        if (isDesktop()) {
+            return getTaskSnapshot(DESKTOP).getVisibleBounds().height();
+        }
+        return getTaskSnapshot(DEFAULT).getVisibleBounds().height();
     }
 
     /**
      * Calculates the visible height for split tasks, containing 2 snapshot tiles and a divider.
      */
     private int getCombinedSplitTaskHeight() {
-        UiObject2 taskSnapshot1 = findObjectInTask(SPLIT_TOP_OR_LEFT.snapshotRes);
-        UiObject2 taskSnapshot2 = findObjectInTask(SPLIT_BOTTOM_OR_RIGHT.snapshotRes);
+        UiObject2 taskSnapshot1 = getTaskSnapshot(SPLIT_TOP_OR_LEFT);
+        UiObject2 taskSnapshot2 = getTaskSnapshot(SPLIT_BOTTOM_OR_RIGHT);
 
         // If the split task is partly off screen, taskSnapshot1 can be invisible.
         if (taskSnapshot1 == null) {
@@ -98,39 +99,11 @@
         return bottom - top;
     }
 
-    /**
-     * Returns the width of the visible task, or the combined width of two tasks in split with a
-     * divider between.
-     */
-    int getVisibleWidth() {
-        if (isGrouped()) {
-            return getCombinedSplitTaskWidth();
-        }
-
-        UiObject2 taskSnapshot1 = findObjectInTask(DEFAULT.snapshotRes);
-        return taskSnapshot1.getVisibleBounds().width();
-    }
-
-    /**
-     * Calculates the visible width for split tasks, containing 2 snapshot tiles and a divider.
-     */
-    private int getCombinedSplitTaskWidth() {
-        UiObject2 taskSnapshot1 = findObjectInTask(SPLIT_TOP_OR_LEFT.snapshotRes);
-        UiObject2 taskSnapshot2 = findObjectInTask(SPLIT_BOTTOM_OR_RIGHT.snapshotRes);
-
-        int left = Math.min(
-                taskSnapshot1.getVisibleBounds().left, taskSnapshot2.getVisibleBounds().left);
-        int right = Math.max(
-                taskSnapshot1.getVisibleBounds().right, taskSnapshot2.getVisibleBounds().right);
-
-        return right - left;
-    }
-
-    int getTaskCenterX() {
+    public int getTaskCenterX() {
         return mTask.getVisibleCenter().x;
     }
 
-    int getTaskCenterY() {
+    public int getTaskCenterY() {
         return mTask.getVisibleCenter().y;
     }
 
@@ -143,6 +116,22 @@
     }
 
     /**
+     * Returns the task snapshot (thumbnail) for the given `OverviewTaskContainer`.
+     *
+     * For some reason `BySelector` does not work with `hasChild` or `hasParent` so instead we
+     * grab all the views matching the id: "snapshot" and filter for the correct parent.
+     */
+    private UiObject2 getTaskSnapshot(OverviewTaskContainer overviewTaskContainer) {
+        BySelector snapshotSelector = mLauncher.getOverviewObjectSelector("snapshot");
+        List<UiObject2> snapshots = mTask.findObjects(snapshotSelector);
+        return snapshots.stream()
+                .filter(snapshot -> snapshot.getParent().getResourceName()
+                        .contains(overviewTaskContainer.taskContentViewRes))
+                .findFirst()
+                .orElse(snapshots.getFirst());
+    }
+
+    /**
      * Dismisses the task by swiping up.
      */
     public void dismiss() {
@@ -158,6 +147,7 @@
             }
 
             boolean taskWasFocused = mLauncher.isTablet()
+                    && !isDesktop()
                     && getVisibleHeight() == mLauncher.getOverviewTaskSize().height();
             List<Integer> originalTasksCenterX =
                     getCurrentTasksCenterXList().stream().sorted().toList();
@@ -211,6 +201,36 @@
     }
 
     /**
+     * Starts dismissing the task by swiping up, then cancels, and task springs back to start.
+     */
+    public void dismissCancel() {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+             LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                     "want to start dismissing an overview task then cancel")) {
+            verifyActiveContainer();
+            int taskCountBeforeDismiss = mOverview.getTaskCount();
+            mLauncher.assertNotEquals("Unable to find a task", 0, taskCountBeforeDismiss);
+
+            final Rect taskBounds = mLauncher.getVisibleBounds(mTask);
+            final int centerX = taskBounds.centerX();
+            final int centerY = taskBounds.bottom - 1;
+            final int endCenterY = centerY - (taskBounds.height() / 4);
+            mLauncher.executeAndWaitForLauncherEvent(
+                    // Set slowDown to true so we do not fling the task at the end of the drag, as
+                    // we want it to cancel and return back to the origin. We use 30 steps to
+                    // perform the gesture slowly as well, to avoid flinging.
+                    () -> mLauncher.linearGesture(centerX, centerY, centerX, endCenterY,
+                            /* steps= */ 30, /* slowDown= */ true,
+                            LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER),
+                    event -> TestProtocol.DISMISS_ANIMATION_ENDS_MESSAGE.equals(
+                            event.getClassName()),
+                    () -> "Canceling swipe to dismiss did not end with task at origin.",
+                    "cancel swiping to dismiss");
+
+        }
+    }
+
+    /**
      * Clicks the task.
      */
     public LaunchedAppState open() {
@@ -274,19 +294,13 @@
         }
     }
 
-    private UiObject2 findObjectInTask(String resName) {
-        return mTask.findObject(mLauncher.getOverviewObjectSelector(resName));
-    }
-
     /**
      * Returns whether the given String is contained in this Task's contentDescription. Also returns
      * true if both Strings are null.
-     *
-     * TODO(b/342627272): remove Nullable support once the bug causing it to be null is fixed.
      */
-    public boolean containsContentDescription(@Nullable String expected,
+    public boolean containsContentDescription(String expected,
             OverviewTaskContainer overviewTaskContainer) {
-        String actual = findObjectInTask(overviewTaskContainer.snapshotRes).getContentDescription();
+        String actual = getTaskSnapshot(overviewTaskContainer).getContentDescription();
         if (actual == null && expected == null) {
             return true;
         }
@@ -300,7 +314,7 @@
      * Returns whether the given String is contained in this Task's contentDescription. Also returns
      * true if both Strings are null
      */
-    public boolean containsContentDescription(@Nullable String expected) {
+    public boolean containsContentDescription(String expected) {
         return containsContentDescription(expected, DEFAULT);
     }
 
@@ -332,19 +346,19 @@
      */
     public enum OverviewTaskContainer {
         // The main task when the task is not split.
-        DEFAULT("snapshot", "icon"),
+        DEFAULT("task_content_view", "icon"),
         // The first task in split task.
-        SPLIT_TOP_OR_LEFT("snapshot", "icon"),
+        SPLIT_TOP_OR_LEFT("task_content_view", "icon"),
         // The second task in split task.
-        SPLIT_BOTTOM_OR_RIGHT("bottomright_snapshot", "bottomRight_icon"),
+        SPLIT_BOTTOM_OR_RIGHT("bottomright_task_content_view", "bottomRight_icon"),
         // The desktop task.
         DESKTOP("background", "icon");
 
-        public final String snapshotRes;
+        public final String taskContentViewRes;
         public final String iconAppRes;
 
-        OverviewTaskContainer(String snapshotRes, String iconAppRes) {
-            this.snapshotRes = snapshotRes;
+        OverviewTaskContainer(String taskContentViewRes, String iconAppRes) {
+            this.taskContentViewRes = taskContentViewRes;
             this.iconAppRes = iconAppRes;
         }
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/Taskbar.java b/tests/tapl/com/android/launcher3/tapl/Taskbar.java
index b4aaab7..d4e6d31 100644
--- a/tests/tapl/com/android/launcher3/tapl/Taskbar.java
+++ b/tests/tapl/com/android/launcher3/tapl/Taskbar.java
@@ -115,6 +115,23 @@
         }
     }
 
+    /**
+     *  Opens the Home all apps page by clicking the taskbar all apps icon. To be used to open all
+     *  apps when taskbar is visible on home.
+     */
+    public HomeAllApps openAllAppsOnHome() {
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to open home all apps from taskbar");
+             LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+
+            mLauncher.clickLauncherObject(mLauncher.waitForObjectInContainer(
+                    mLauncher.waitForSystemLauncherObject(TASKBAR_RES_ID),
+                    getAllAppsButtonSelector()));
+
+            return mLauncher.getAllApps();
+        }
+    }
+
     /** Opens the Taskbar all apps page with the meta keyboard shortcut. */
     public TaskbarAllApps openAllAppsFromKeyboardShortcut() {
         try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index a29362f..68c7049 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -29,6 +29,7 @@
 import static junit.framework.TestCase.assertNotNull;
 import static junit.framework.TestCase.assertTrue;
 
+import android.content.Intent;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.SystemClock;
@@ -44,9 +45,7 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
-import com.android.launcher3.testing.shared.HotseatCellCenterRequest;
 import com.android.launcher3.testing.shared.TestProtocol;
-import com.android.launcher3.testing.shared.WorkspaceCellCenterRequest;
 
 import java.util.List;
 import java.util.Map;
@@ -59,7 +58,7 @@
  */
 public final class Workspace extends Home {
     private static final int FLING_STEPS = 10;
-    private static final int DEFAULT_DRAG_STEPS = 10;
+    private static final int DEFAULT_DRAG_STEPS = 20;
     private static final String DROP_BAR_RES_ID = "drop_target_bar";
     private static final String DELETE_TARGET_TEXT_ID = "delete_target_text";
     private static final String UNINSTALL_TARGET_TEXT_ID = "uninstall_target_text";
@@ -499,20 +498,23 @@
     }
 
     static Point getCellCenter(LauncherInstrumentation launcher, int cellX, int cellY) {
-        return launcher.getTestInfo(WorkspaceCellCenterRequest.builder().setCellX(cellX).setCellY(
-                cellY).build()).getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+        return getCellCenter(launcher, cellX, cellY, 1, 1);
     }
 
     static Point getCellCenter(LauncherInstrumentation launcher, int cellX, int cellY, int spanX,
             int spanY) {
-        return launcher.getTestInfo(WorkspaceCellCenterRequest.builder().setCellX(cellX)
-                .setCellY(cellY).setSpanX(spanX).setSpanY(spanY).build())
+        return launcher.getTestInfo(
+                new Intent(TestProtocol.REQUEST_WORKSPACE_CELL_CENTER)
+                        .putExtra(TestProtocol.TEST_INFO_PARAM_CELL_SPAN,
+                                new Rect(cellX, cellY, cellX + spanX, cellY + spanY)))
                 .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
-    static Point getHotseatCellCenter(LauncherInstrumentation launcher, int cellInd) {
-        return launcher.getTestInfo(HotseatCellCenterRequest.builder()
-                .setCellInd(cellInd).build()).getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+    static Point getHotseatCellCenter(LauncherInstrumentation launcher, int cellIndex) {
+        return launcher.getTestInfo(
+                new Intent(TestProtocol.REQUEST_HOTSEAT_CELL_CENTER)
+                        .putExtra(TestProtocol.TEST_INFO_PARAM_INDEX, cellIndex))
+                .getParcelable(TestProtocol.TEST_INFO_RESPONSE_FIELD);
     }
 
     /** Returns the number of rows and columns in the workspace */
@@ -607,7 +609,7 @@
                 launcher,
                 launchable,
                 destSupplier,
-                /* isDecelerating= */ false,
+                /* isDecelerating= */ !isDraggingToFolder,
                 () -> launcher.expectEvent(TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT),
                 /* expectDropEvents= */ null,
                 /* startsActivity = */ false,
@@ -680,7 +682,7 @@
 
             launcher.movePointer(dragStart, targetDest,
                     DEFAULT_DRAG_STEPS, isDecelerating, downTime, SystemClock.uptimeMillis(),
-                    false, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
+                    !isDraggingToFolder, LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER);
 
             dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents, startsActivity);
         }
@@ -841,7 +843,9 @@
 
     @Override
     protected String getSwipeHeightRequestName() {
-        return TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT;
+        return mLauncher.isRecentsWindowEnabled()
+                ? super.getSwipeHeightRequestName()
+                : TestProtocol.REQUEST_HOME_TO_OVERVIEW_SWIPE_HEIGHT;
     }
 
     @Override