Snap for 13256841 from 500698152c20e4765585e83f7ff21fb23751d7d4 to 25Q2-release

Change-Id: Ia536b44ce15da1d9cf63eaab3abc9a35176c81ed
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 4abfbbe..3aac1b6 100644
--- a/quickstep/res/layout/task.xml
+++ b/quickstep/res/layout/task.xml
@@ -29,8 +29,8 @@
     launcher:hoverBorderColor="@color/materialColorPrimary">
 
     <ViewStub
-        android:id="@+id/task_content_view"
-        android:inflatedId="@id/task_content_view"
+        android:id="@+id/snapshot"
+        android:inflatedId="@id/snapshot"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
diff --git a/quickstep/res/layout/task_content_view.xml b/quickstep/res/layout/task_content_view.xml
deleted file mode 100644
index 9055ccd..0000000
--- a/quickstep/res/layout/task_content_view.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?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.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 a7c4856..3e6f5ed 100644
--- a/quickstep/res/layout/task_grouped.xml
+++ b/quickstep/res/layout/task_grouped.xml
@@ -34,14 +34,14 @@
     launcher:hoverBorderColor="@color/materialColorPrimary">
 
     <ViewStub
-        android:id="@+id/task_content_view"
-        android:inflatedId="@id/task_content_view"
+        android:id="@+id/snapshot"
+        android:inflatedId="@id/snapshot"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
     <ViewStub
-        android:id="@+id/bottomright_task_content_view"
-        android:inflatedId="@id/bottomright_task_content_view"
+        android:id="@+id/bottomright_snapshot"
+        android:inflatedId="@id/bottomright_snapshot"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
diff --git a/quickstep/res/layout/task_header_view.xml b/quickstep/res/layout/task_header_view.xml
deleted file mode 100644
index 849153f..0000000
--- a/quickstep/res/layout/task_header_view.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?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:id="@+id/task_header_view"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:paddingTop="@dimen/task_thumbnail_header_padding_top_bottom"
-    android:paddingBottom="@dimen/task_thumbnail_header_padding_top_bottom"
-    android:paddingStart="@dimen/task_thumbnail_header_padding_start_end"
-    android:paddingEnd="@dimen/task_thumbnail_header_padding_start_end"
-    android:background="@drawable/task_thumbnail_header_bg">
-    <ImageView
-        android:id="@+id/header_app_icon"
-        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:layout_marginEnd="@dimen/task_thumbnail_header_margin_between_views"
-        android:contentDescription="@string/header_app_icon_description"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="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:layout_marginEnd="@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: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:layout_marginEnd="@dimen/task_thumbnail_header_margin_between_views"
-        android:background="@null"
-        android:contentDescription="@string/header_close_icon_description"
-        android:src="@drawable/task_header_close_button"
-        android:tint="@android:color/darker_gray"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintHorizontal_bias="1"
-        app:layout_constraintStart_toEndOf="@id/header_app_title"
-        app:layout_constraintTop_toTopOf="parent" />
-</com.android.quickstep.views.TaskHeaderView>
diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml
index 8280e13..3b96615 100644
--- a/quickstep/res/layout/task_thumbnail.xml
+++ b/quickstep/res/layout/task_thumbnail.xml
@@ -17,8 +17,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/snapshot"
     android:layout_width="match_parent"
-    android:layout_height="0dp"
-    android:layout_weight="1" >
+    android:layout_height="match_parent" >
 
     <com.android.quickstep.views.FixedSizeImageView
         android:id="@+id/task_thumbnail"
diff --git a/quickstep/res/layout/task_thumbnail_view_header.xml b/quickstep/res/layout/task_thumbnail_view_header.xml
new file mode 100644
index 0000000..70e4a42
--- /dev/null
+++ b/quickstep/res/layout/task_thumbnail_view_header.xml
@@ -0,0 +1,72 @@
+<?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.TaskThumbnailViewHeader
+    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_thumbnail_view_header"
+    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"
+            app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintVertical_bias="0.5"
+            app:layout_constraintHorizontal_chainStyle="spread_inside" />
+        <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:layout_marginEnd="@dimen/task_thumbnail_header_margin_between_views"
+            android:text="@string/header_default_app_title"
+            app:layout_constraintStart_toEndOf="@id/header_app_icon"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintVertical_bias="0.5" />
+        <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_constraintHorizontal_bias="1"
+            app:layout_constraintVertical_bias="0.5" />
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.quickstep.views.TaskThumbnailViewHeader>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index cb3c446..86d44c9 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -82,9 +82,9 @@
     <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_padding_top_bottom">6dp</dimen>
-    <dimen name="task_thumbnail_header_padding_start_end">12dp</dimen>
-    <dimen name="task_thumbnail_header_margin_between_views">8dp</dimen>
+    <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>
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/BarsLocationAnimatorHelper.kt b/quickstep/src/com/android/launcher3/taskbar/BarsLocationAnimatorHelper.kt
index b8060e1..ad847b4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/BarsLocationAnimatorHelper.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/BarsLocationAnimatorHelper.kt
@@ -20,6 +20,7 @@
 import android.animation.AnimatorSet
 import android.animation.ObjectAnimator
 import android.animation.ValueAnimator
+import android.content.Context
 import android.view.View
 import androidx.dynamicanimation.animation.SpringForce
 import com.android.app.animation.Interpolators
@@ -30,11 +31,10 @@
 
 /** Animator helper that creates bars animators. */
 object BarsLocationAnimatorHelper {
-
-    private const val FADE_OUT_ANIM_ALPHA_DURATION_MS: Long = 50L
-    private const val FADE_OUT_ANIM_ALPHA_DELAY_MS: Long = 50L
-    private const val FADE_OUT_ANIM_POSITION_DURATION_MS: Long = 100L
-    private const val FADE_IN_ANIM_ALPHA_DURATION_MS: Long = 100L
+    const val FADE_OUT_ANIM_ALPHA_DURATION_MS: Long = 50L
+    const val FADE_OUT_ANIM_ALPHA_DELAY_MS: Long = 50L
+    const val FADE_OUT_ANIM_POSITION_DURATION_MS: Long = 100L
+    const val FADE_IN_ANIM_ALPHA_DURATION_MS: Long = 100L
 
     // Use STIFFNESS_MEDIUMLOW which is not defined in the API constants
     private const val FADE_IN_ANIM_POSITION_SPRING_STIFFNESS: Float = 400f
@@ -45,13 +45,13 @@
     // During fade in animation we shift the bubble bar 1/60th of the screen width
     private const val FADE_IN_ANIM_POSITION_SHIFT: Float = 1 / 60f
 
-    private val View.screenWidth: Int
+    private val Context.screenWidth: Int
         get() = resources.displayMetrics.widthPixels
 
-    private val View.outShift: Float
+    val Context.outShift: Float
         get() = screenWidth * FADE_OUT_ANIM_POSITION_SHIFT
 
-    private val View.inShiftX: Float
+    val Context.inShiftX: Float
         get() = screenWidth * FADE_IN_ANIM_POSITION_SHIFT
 
     /**
@@ -108,7 +108,7 @@
         targetViewAlphaAnim: ObjectAnimator,
         bubbleBarView: View,
     ): Animator {
-        val shift: Float = bubbleBarView.outShift
+        val shift: Float = bubbleBarView.context.outShift
 
         val onLeft = newLocation.isOnLeft(bubbleBarView.isLayoutRtl)
         val startTx: Float
@@ -132,15 +132,19 @@
         return createLocationInAnimator(startTx, finalTx, targetViewAlphaAnim, bubbleBarView)
     }
 
-    /** Creates an animator for the bubble bar view out part. */
+    /**
+     * Creates an animator for the bubble bar view out part.
+     *
+     * @param targetLocation the location bubble bar should animate to.
+     */
     @JvmStatic
     fun getBubbleBarLocationOutAnimator(
         bubbleBarView: View,
-        bubbleBarLocation: BubbleBarLocation,
+        targetLocation: BubbleBarLocation,
         targetViewAlphaAnim: ObjectAnimator,
     ): Animator {
-        val onLeft = bubbleBarLocation.isOnLeft(bubbleBarView.isLayoutRtl)
-        val shift = bubbleBarView.outShift
+        val onLeft = targetLocation.isOnLeft(bubbleBarView.isLayoutRtl)
+        val shift = bubbleBarView.context.outShift
         val finalTx = bubbleBarView.translationX + (if (onLeft) -shift else shift)
         return this.createLocationOutAnimator(finalTx, targetViewAlphaAnim, bubbleBarView)
     }
@@ -152,7 +156,7 @@
         navButtonsView: View,
         navBarTargetTranslationX: Float,
     ): Animator {
-        val outShift: Float = navButtonsView.outShift
+        val outShift: Float = navButtonsView.context.outShift
         val isNavBarOnRight: Boolean = location.isOnLeft(navButtonsView.isLayoutRtl)
         val finalOutTx =
             navButtonsView.translationX + (if (isNavBarOnRight) outShift else -outShift)
@@ -162,7 +166,7 @@
                 ObjectAnimator.ofFloat(navButtonsView, LauncherAnimUtils.VIEW_ALPHA, 0f),
                 navButtonsView,
             )
-        val inShift: Float = navButtonsView.inShiftX
+        val inShift: Float = navButtonsView.context.inShiftX
         val inStartX = navBarTargetTranslationX + (if (isNavBarOnRight) -inShift else inShift)
         val fadeIn: Animator =
             createLocationInAnimator(
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index 7d82fc4..5b1e859 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -146,7 +146,8 @@
 
     private boolean isIn3pHomeOrRecents() {
         TopTaskTracker.CachedTaskInfo topTask = TopTaskTracker.INSTANCE
-                .get(mControllers.taskbarActivityContext).getCachedTopTask(true);
+                .get(mControllers.taskbarActivityContext).getCachedTopTask(true,
+                        mRecentsContainer.asContext().getDisplayId());
         return topTask.isHomeTask() || topTask.isRecentsTask();
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
index ab147bb..515cfe2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchView.java
@@ -47,7 +47,6 @@
 
 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;
@@ -180,17 +179,6 @@
 
         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);
@@ -359,9 +347,21 @@
                 });
     }
 
-    private void initializeScrollArrows() {
+
+    void enableScrollArrowSupport() {
+        if (mSupportsScrollArrows) {
+            return;
+        }
         mSupportsScrollArrows = true;
 
+        if (mIsRtl) {
+            mStartScrollArrow.setContentDescription(
+                    getResources().getString(R.string.quick_switch_scroll_arrow_right));
+            mEndScrollArrow.setContentDescription(
+                    getResources().getString(R.string.quick_switch_scroll_arrow_left));
+        }
+
+
         mStartScrollArrow.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index b5f2532..a312d5f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -34,6 +34,7 @@
 
 import com.android.internal.jank.Cuj;
 import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.Flags;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
@@ -119,6 +120,10 @@
         mWasDesktopTaskFilteredOut = wasDesktopTaskFilteredOut;
         mWasOpenedFromTaskbar = wasOpenedFromTaskbar;
 
+        if (Flags.taskbarOverflow() && wasOpenedFromTaskbar) {
+            mKeyboardQuickSwitchView.enableScrollArrowSupport();
+        }
+
         mKeyboardQuickSwitchView.applyLoadPlan(
                 mOverlayContext,
                 tasks,
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index f03b7d1..cf3b952 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -693,7 +693,11 @@
         if (mBackButton == null) {
             return;
         }
-        setupBackButtonAccessibility(mBackButton, accessibilityDelegate);
+        if (predictiveBackThreeButtonNav()) {
+            setupBackButtonAccessibility(mBackButton, accessibilityDelegate);
+        } else {
+            mBackButton.setAccessibilityDelegate(accessibilityDelegate);
+        }
     }
 
     public void setWallpaperVisible(boolean isVisible) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 142f458..4b977e0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -510,6 +510,8 @@
                     } else {
                         // This will take care of calling maybeOnDragEnd() after the animation
                         animateGlobalDragViewToOriginalPosition(btv, dragEvent);
+                        //TODO(b/399678274): hide drop target in shell
+                        notifyBubbleBarItemDragCanceled();
                     }
                     mActivity.getDragLayer().setOnDragListener(null);
 
@@ -536,10 +538,10 @@
             mControllers.taskbarAutohideSuspendController.updateFlag(
                     TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, false);
             mActivity.onDragEnd();
+            // 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();
             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.
@@ -563,12 +565,17 @@
             BubbleBarViewController bubbleBarViewController = bc.bubbleBarViewController;
             boolean showingDropTarget = bubbleBarViewController.isShowingDropTarget();
             if (showingDropTarget) {
-                bubbleBarViewController.onItemDroppedInBubbleBarDragZone();
+                bubbleBarViewController.onItemDragCompleted();
             }
             return showingDropTarget;
         }).orElse(false);
     }
 
+    private void notifyBubbleBarItemDragCanceled() {
+        mControllers.bubbleControllers.ifPresent(bc ->
+                bc.bubbleBarViewController.onItemDraggedOutsideBubbleBarDropZone());
+    }
+
     @Override
     protected void endDrag() {
         if (mDisallowGlobalDrag && !mIsDropHandledByDropTarget) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index f342fa5..cc6cc64 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -348,13 +348,17 @@
             controllers.bubbleControllers.isPresent &&
                 controllers.bubbleControllers.get().bubbleBarViewController.isBubbleBarVisible()
         var insetsIsTouchableRegion = true
+        // Prevents the taskbar from taking touches and conflicting with setup wizard
         if (
             context.isPhoneButtonNavMode &&
+                context.isUserSetupComplete &&
                 (!controllers.navbarButtonsViewController.isImeVisible ||
                     !controllers.navbarButtonsViewController.isImeRenderingNavButtons)
         ) {
             insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME)
             insetsIsTouchableRegion = false
+            debugTouchableRegion.lastSetTouchableReason =
+                "Phone button nav mode: Fullscreen touchable, IME not affecting nav buttons"
         } else if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
             // Let touches pass through us.
             insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index c3bc7d9..f3cbdb5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -17,8 +17,8 @@
 
 import static android.content.Context.RECEIVER_EXPORTED;
 import static android.content.Context.RECEIVER_NOT_EXPORTED;
-import static android.content.pm.PackageManager.FEATURE_PC;
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
+import static android.content.pm.PackageManager.FEATURE_PC;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
 
@@ -82,6 +82,8 @@
 import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.quickstep.AllAppsActionManager;
 import com.android.quickstep.RecentsActivity;
+import com.android.quickstep.SystemDecorationChangeObserver;
+import com.android.quickstep.SystemDecorationChangeObserver.DisplayDecorationListener;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.fallback.window.RecentsDisplayModel;
 import com.android.quickstep.fallback.window.RecentsWindowFlags;
@@ -107,7 +109,7 @@
 /**
  * Class to manage taskbar lifecycle
  */
-public class TaskbarManager {
+public class TaskbarManager implements DisplayDecorationListener {
     private static final String TAG = "TaskbarManager";
     private static final boolean DEBUG = false;
     private static final int TASKBAR_DESTROY_DURATION = 100;
@@ -138,6 +140,7 @@
             Settings.Secure.NAV_BAR_KIDS_MODE);
 
     private final Context mBaseContext;
+    private final int mPrimaryDisplayId;
     private final TaskbarNavButtonCallbacks mNavCallbacks;
     // TODO: Remove this during the connected displays lifecycle refactor.
     private final Context mPrimaryWindowContext;
@@ -434,26 +437,28 @@
             TaskbarNavButtonCallbacks navCallbacks,
             RecentsDisplayModel recentsDisplayModel) {
         mBaseContext = context;
+        mPrimaryDisplayId = mBaseContext.getDisplayId();
         mAllAppsActionManager = allAppsActionManager;
         mNavCallbacks = navCallbacks;
         mRecentsDisplayModel = recentsDisplayModel;
 
         // Set up primary display.
-        int primaryDisplayId = getDefaultDisplayId();
         debugPrimaryTaskbar("TaskbarManager constructor");
-        mPrimaryWindowContext = createWindowContext(primaryDisplayId);
+        mPrimaryWindowContext = createWindowContext(getDefaultDisplayId());
         mPrimaryWindowManager = mPrimaryWindowContext.getSystemService(WindowManager.class);
         DesktopVisibilityController.INSTANCE.get(
                 mPrimaryWindowContext).registerTaskbarDesktopModeListener(
                 mTaskbarDesktopModeListener);
-        createTaskbarRootLayout(primaryDisplayId);
-        createNavButtonController(primaryDisplayId);
-        createAndRegisterComponentCallbacks(primaryDisplayId);
+        createTaskbarRootLayout(getDefaultDisplayId());
+        createNavButtonController(getDefaultDisplayId());
+        createAndRegisterComponentCallbacks(getDefaultDisplayId());
 
         SettingsCache.INSTANCE.get(mPrimaryWindowContext)
                 .register(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
         SettingsCache.INSTANCE.get(mPrimaryWindowContext)
                 .register(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
+        SystemDecorationChangeObserver.getINSTANCE().get(mPrimaryWindowContext)
+                .registerDisplayDecorationListener(this);
         mShutdownReceiver =
                 new SimpleBroadcastReceiver(
                         mPrimaryWindowContext, UI_HELPER_EXECUTOR, i -> destroyAllTaskbars());
@@ -957,6 +962,7 @@
      * 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.
      */
+    @Override
     public void onDisplayAddSystemDecorations(int displayId) {
         debugTaskbarManager("onDisplayAddSystemDecorations: ", displayId);
         Display display = getDisplay(displayId);
@@ -1020,6 +1026,7 @@
      * Signal from SysUI indicating that a previously connected non-mirroring display was just
      * removed from the primary device.
      */
+    @Override
     public void onDisplayRemoved(int displayId) {
         debugTaskbarManager("onDisplayRemoved: ", displayId);
         if (!DesktopExperienceFlags.ENABLE_TASKBAR_CONNECTED_DISPLAYS.isTrue() || isDefaultDisplay(
@@ -1059,6 +1066,7 @@
     /**
      * Signal from SysUI indicating that system decorations should be removed from the display.
      */
+    @Override
     public void onDisplayRemoveSystemDecorations(int displayId) {
         // The display mirroring starts. The handling logic is the same as when removing a
         // display.
@@ -1102,6 +1110,8 @@
                 .unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
         SettingsCache.INSTANCE.get(mPrimaryWindowContext)
                 .unregister(NAV_BAR_KIDS_MODE, mOnSettingsChangeListener);
+        SystemDecorationChangeObserver.getINSTANCE().get(mPrimaryWindowContext)
+                .unregisterDisplayDecorationListener(this);
         debugPrimaryTaskbar("destroy: unregistering component callbacks");
         removeAndUnregisterComponentCallbacks(getDefaultDisplayId());
         mShutdownReceiver.unregisterReceiverSafely();
@@ -1669,7 +1679,7 @@
     }
 
     private int getDefaultDisplayId() {
-        return mBaseContext.getDisplayId();
+        return mPrimaryDisplayId;
     }
 
     /**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacksFactory.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacksFactory.kt
index 17da533..6e94889 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacksFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewCallbacksFactory.kt
@@ -42,7 +42,8 @@
                 if (contextualSearchInvoked) {
                     val runningPackage =
                         TopTaskTracker.INSTANCE[activity].getCachedTopTask(
-                                /* filterOnlyVisibleRecents */ true
+                                /* filterOnlyVisibleRecents */ true,
+                                activity.display.displayId,
                             )
                             .getPackageName()
                     activity.statsLogManager
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 6340bb1..f15ef8c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -952,7 +952,7 @@
         // or fade in while already in in-app state.
         Interpolator interpolator = mIsHotseatIconOnTopWhenAligned ? LINEAR : FINAL_FRAME;
 
-        int offsetY = launcherDp.getTaskbarOffsetY();
+        int offsetY = taskbarDp.getTaskbarOffsetY();
         setter.setFloat(mTaskbarIconTranslationYForHome, VALUE, -offsetY, interpolator);
         setter.setFloat(mTaskbarNavButtonTranslationY, VALUE, -offsetY, interpolator);
         setter.setFloat(mTaskbarNavButtonTranslationYForInAppDisplay, VALUE, offsetY, interpolator);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 1abef8a..6380c23 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
@@ -560,30 +561,52 @@
         // First animator hides the bar.
         // After it completes, bubble positions in the bar and arrow position is updated.
         // Second animator is started to show the bar.
-        ObjectAnimator alphaOutAnim = ObjectAnimator.ofFloat(
-                this, getLocationAnimAlphaProperty(), 0f);
-        mBubbleBarLocationAnimator = BarsLocationAnimatorHelper.getBubbleBarLocationOutAnimator(
-                this,
-                bubbleBarLocation,
-                alphaOutAnim);
+        mBubbleBarLocationAnimator = animateToBubbleBarLocationOut(bubbleBarLocation);
         mBubbleBarLocationAnimator.addListener(AnimatorListeners.forEndCallback(() -> {
-            updateBubblesLayoutProperties(bubbleBarLocation);
-            mBubbleBarBackground.setAnchorLeft(bubbleBarLocation.isOnLeft(isLayoutRtl()));
-            ObjectAnimator alphaInAnim = ObjectAnimator.ofFloat(BubbleBarView.this,
-                    getLocationAnimAlphaProperty(), 1f);
-
             // Animate it in
-            mBubbleBarLocationAnimator = BarsLocationAnimatorHelper.getBubbleBarLocationInAnimator(
-                    bubbleBarLocation,
-                    mBubbleBarLocation,
-                    getDistanceFromOtherSide(),
-                    alphaInAnim,
-                    BubbleBarView.this);
+            mBubbleBarLocationAnimator = animateToBubbleBarLocationIn(mBubbleBarLocation,
+                    bubbleBarLocation);
             mBubbleBarLocationAnimator.start();
         }));
         mBubbleBarLocationAnimator.start();
     }
 
+    /** Creates animator for animating bubble bar in. */
+    public Animator animateToBubbleBarLocationIn(BubbleBarLocation fromLocation,
+            BubbleBarLocation toLocation) {
+        updateBubblesLayoutProperties(toLocation);
+        mBubbleBarBackground.setAnchorLeft(toLocation.isOnLeft(isLayoutRtl()));
+        ObjectAnimator alphaInAnim = ObjectAnimator.ofFloat(BubbleBarView.this,
+                getLocationAnimAlphaProperty(), 1f);
+        return BarsLocationAnimatorHelper.getBubbleBarLocationInAnimator(toLocation, fromLocation,
+                getDistanceFromOtherSide(), alphaInAnim, this);
+    }
+
+    /**
+     * Creates animator for animating bubble bar out.
+     *
+     * @param targetLocation the location bubble br should animate to.
+     */
+    public Animator animateToBubbleBarLocationOut(BubbleBarLocation targetLocation) {
+        ObjectAnimator alphaOutAnim = ObjectAnimator.ofFloat(
+                this, getLocationAnimAlphaProperty(), 0f);
+        Animator outAnimation = BarsLocationAnimatorHelper.getBubbleBarLocationOutAnimator(
+                this,
+                targetLocation,
+                alphaOutAnim);
+        outAnimation.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
+                // need to restore the original bar view state in case icon is dropped to the bubble
+                // bar original location
+                updateBubblesLayoutProperties(targetLocation);
+                mBubbleBarBackground.setAnchorLeft(targetLocation.isOnLeft(isLayoutRtl()));
+                setTranslationX(0f);
+            }
+        });
+        return outAnimation;
+    }
+
     /**
      * Get property that can be used to animate the alpha value for the bar.
      * When a bubble is being dragged, uses {@link #BUBBLE_DRAG_ALPHA}.
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 9fb283c..ce4a14f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -194,9 +194,11 @@
     private boolean mHiddenForStashed;
     private boolean mShouldShowEducation;
     public boolean mOverflowAdded;
-    private boolean mIsLocationUpdatedForDropTarget = false;
     private boolean mWasStashedBeforeEnteringBubbleDragZone = false;
 
+    /** This field is used solely to track the bubble bar location prior to the start of the drag */
+    private @Nullable BubbleBarLocation mBubbleBarDragLocation;
+
     private BubbleBarViewAnimator mBubbleBarViewAnimator;
     private final FrameLayout mBubbleBarContainer;
     private BubbleBarFlyoutController mBubbleBarFlyoutController;
@@ -364,7 +366,7 @@
             @Override
             public boolean isOnLeft() {
                 boolean shouldRevertLocation =
-                        mBarView.isShowingDropTarget() && mIsLocationUpdatedForDropTarget;
+                        mBarView.isShowingDropTarget() && isLocationUpdatedForDropTarget();
                 boolean isOnLeft = mBarView.getBubbleBarLocation().isOnLeft(mBarView.isLayoutRtl());
                 return shouldRevertLocation != isOnLeft;
             }
@@ -616,6 +618,17 @@
         mBarView.animateToBubbleBarLocation(bubbleBarLocation);
     }
 
+    /** Return animator for animating bubble bar in. */
+    public Animator animateBubbleBarLocationIn(BubbleBarLocation fromLocation,
+            BubbleBarLocation toLocation) {
+        return mBarView.animateToBubbleBarLocationIn(fromLocation, toLocation);
+    }
+
+    /** Return animator for animating bubble bar out. */
+    public Animator animateBubbleBarLocationOut(BubbleBarLocation toLocation) {
+        return mBarView.animateToBubbleBarLocationOut(toLocation);
+    }
+
     /** Returns whether the Bubble Bar is currently displaying a drop target. */
     public boolean isShowingDropTarget() {
         return mBarView.isShowingDropTarget();
@@ -628,25 +641,18 @@
      * updated.
      */
     public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation bubbleBarLocation) {
-        Log.w("BBAnimation", "onDragItemOverBubbleBarDragZone()");
+        mBubbleBarDragLocation = bubbleBarLocation;
         mBarView.showDropTarget(/* isDropTarget = */ true);
-        boolean isRtl = mBarView.isLayoutRtl();
-        mIsLocationUpdatedForDropTarget = getBubbleBarLocation().isOnLeft(isRtl)
-                != bubbleBarLocation.isOnLeft(isRtl);
         mWasStashedBeforeEnteringBubbleDragZone = hasBubbles()
             && mBubbleStashController.isStashed();
         if (mWasStashedBeforeEnteringBubbleDragZone) {
-            if (mIsLocationUpdatedForDropTarget) {
-                // bubble bar is stashed an location updated
-                //TODO(b/399678274) add animation
-                mBubbleStashController.showBubbleBarImmediate();
-                animateBubbleBarLocation(bubbleBarLocation);
-            } else {
-                // bubble bar is stashed and location the same - just un-stash
-                mBubbleStashController.showBubbleBar(/* expandBubbles = */ false);
-            }
+            // bubble bar is stashed - un-stash at drag location
+            mBubbleStashController.showBubbleBarAtLocation(
+                    /* fromLocation = */ getBubbleBarLocation(),
+                    /* toLocation = */  mBubbleBarDragLocation
+            );
         } else if (hasBubbles()) {
-            if (mIsLocationUpdatedForDropTarget) {
+            if (isLocationUpdatedForDropTarget()) {
                 // bubble bar has bubbles and location is changed - animate bar to the opposite side
                 animateBubbleBarLocation(bubbleBarLocation);
             }
@@ -661,7 +667,12 @@
      * {@link #onDragItemOverBubbleBarDragZone}}.
      */
     public boolean isLocationUpdatedForDropTarget() {
-        return mIsLocationUpdatedForDropTarget;
+        if (mBubbleBarDragLocation == null) {
+            return false;
+        }
+        boolean isRtl = mBarView.isLayoutRtl();
+        return getBubbleBarLocation().isOnLeft(isRtl)
+                != mBubbleBarDragLocation.isOnLeft(isRtl);
     }
 
     /**
@@ -671,39 +682,34 @@
      * mode and reset the value returned from {@link #isLocationUpdatedForDropTarget()} to false.
      */
     public void onItemDraggedOutsideBubbleBarDropZone() {
-        Log.w("BBAnimation", "onItemDraggedOutsideBubbleBarDropZone()");
-        mBarView.showDropTarget(/* isDropTarget = */ false);
-        if (mWasStashedBeforeEnteringBubbleDragZone) {
-            if (mIsLocationUpdatedForDropTarget) {
-                // bubble bar was stashed and location updated
-                //TODO(b/399678274) add animation
-                animateBubbleBarLocation(getBubbleBarLocation());
-                mBubbleStashController.stashBubbleBarImmediate();
-            } else {
-                // bubble bar was stashed and location the same - just stash it back
-                mBubbleStashController.stashBubbleBar();
-            }
+        if (!isShowingDropTarget()) {
+            return;
+        }
+        if (mWasStashedBeforeEnteringBubbleDragZone && mBubbleBarDragLocation != null) {
+            // bubble bar was stashed - stash at original location
+            mBubbleStashController.stashBubbleBarToLocation(
+                    /* fromLocation = */ mBubbleBarDragLocation,
+                    /* toLocation = */ getBubbleBarLocation()
+            );
         } else if (hasBubbles()) {
-            if (mIsLocationUpdatedForDropTarget) {
-                // bubble bar has bubbles and location was changed - return to the original location
+            if (isLocationUpdatedForDropTarget()) {
+                // bubble bar has bubbles and location was changed - return to the original
+                // location
                 animateBubbleBarLocation(getBubbleBarLocation());
             }
         }
-        mBubbleBarPinController.hideDropTarget();
-        mIsLocationUpdatedForDropTarget = false;
-        mWasStashedBeforeEnteringBubbleDragZone = false;
+        onItemDragCompleted();
     }
 
     /**
      * 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() {
-        Log.w("BBAnimation", "onItemDroppedInBubbleBarDragZone()");
+    public void onItemDragCompleted() {
         mBarView.showDropTarget(/* isDropTarget = */ false);
         mBubbleBarPinController.hideDropTarget();
-        mIsLocationUpdatedForDropTarget = false;
         mWasStashedBeforeEnteringBubbleDragZone = false;
+        mBubbleBarDragLocation = null;
     }
 
     /**
@@ -842,6 +848,7 @@
         boolean hiddenForStashedAndNotAnimating =
                 mHiddenForStashed && !mBubbleBarViewAnimator.isAnimating();
         if (mHiddenForSysui || mHiddenForNoBubbles || hiddenForStashedAndNotAnimating) {
+            //TODO(b/404870188) this visibility change cause search view drag misbehavior
             mBarView.setVisibility(INVISIBLE);
         } else {
             mBarView.setVisibility(VISIBLE);
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
index 3640c3b..ec540e0 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleStashedHandleViewController.java
@@ -272,6 +272,11 @@
         updateTranslationY();
     }
 
+    /** Sets translation X for stash handle. */
+    public void setTranslationX(float translationX) {
+        mStashedHandleView.setTranslationX(translationX);
+    }
+
     private void updateTranslationY() {
         mStashedHandleView.setTranslationY(mTranslationForSwipeY + mTranslationForStashY);
     }
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 fec1eaf..ec272ac 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/BubbleStashController.kt
@@ -131,6 +131,12 @@
      */
     fun stashBubbleBar()
 
+    /**
+     * Animates the bubble bar to the handle at provided location. Does not update bubble bar
+     * location.
+     */
+    fun stashBubbleBarToLocation(fromLocation: BubbleBarLocation, toLocation: BubbleBarLocation) {}
+
     /** Shows the bubble bar, and expands bubbles depending on [expandBubbles]. */
     fun showBubbleBar(expandBubbles: Boolean) {
         showBubbleBar(expandBubbles = expandBubbles, bubbleBarGesture = false)
@@ -144,6 +150,9 @@
      */
     fun showBubbleBar(expandBubbles: Boolean, bubbleBarGesture: Boolean)
 
+    /** Animates the bubble bar at the provided location. Does not update bubble bar location. */
+    fun showBubbleBarAtLocation(fromLocation: BubbleBarLocation, toLocation: BubbleBarLocation) {}
+
     // TODO(b/354218264): Move to BubbleBarViewAnimator
     /**
      * The difference on the Y axis between the center of the handle and the center of the bubble
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 9c148e2..82bb071 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/stashing/TransientBubbleStashController.kt
@@ -18,6 +18,7 @@
 
 import android.animation.Animator
 import android.animation.AnimatorSet
+import android.animation.ValueAnimator
 import android.content.Context
 import android.graphics.Rect
 import android.view.MotionEvent
@@ -31,6 +32,12 @@
 import com.android.launcher3.R
 import com.android.launcher3.anim.AnimatedFloat
 import com.android.launcher3.anim.SpringAnimationBuilder
+import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.FADE_IN_ANIM_ALPHA_DURATION_MS
+import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.FADE_OUT_ANIM_ALPHA_DELAY_MS
+import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.FADE_OUT_ANIM_ALPHA_DURATION_MS
+import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.FADE_OUT_ANIM_POSITION_DURATION_MS
+import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.inShiftX
+import com.android.launcher3.taskbar.BarsLocationAnimatorHelper.outShift
 import com.android.launcher3.taskbar.TaskbarInsetsController
 import com.android.launcher3.taskbar.TaskbarStashController.TASKBAR_STASH_ALPHA_START_DELAY
 import com.android.launcher3.taskbar.TaskbarStashController.TRANSIENT_TASKBAR_STASH_ALPHA_DURATION
@@ -44,6 +51,7 @@
 import com.android.launcher3.util.MultiPropertyFactory
 import com.android.wm.shell.shared.animation.PhysicsAnimator
 import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.ContextUtils.isRtl
 import kotlin.math.max
 
 class TransientBubbleStashController(
@@ -187,6 +195,11 @@
     }
 
     override fun showBubbleBarImmediate(bubbleBarTranslationY: Float) {
+        showBubbleBarImmediateVisually(bubbleBarTranslationY)
+        onIsStashedChanged()
+    }
+
+    private fun showBubbleBarImmediateVisually(bubbleBarTranslationY: Float) {
         bubbleStashedHandleViewController?.setTranslationYForSwipe(0f)
         stashHandleViewAlpha?.value = 0f
         this.bubbleBarTranslationYAnimator.updateValue(bubbleBarTranslationY)
@@ -197,10 +210,14 @@
         bubbleBarBackgroundScaleY.updateValue(1f)
         isStashed = false
         bubbleBarViewController.setHiddenForStashed(false)
-        onIsStashedChanged()
     }
 
     override fun stashBubbleBarImmediate() {
+        stashBubbleBarImmediateVisually()
+        onIsStashedChanged()
+    }
+
+    private fun stashBubbleBarImmediateVisually() {
         bubbleStashedHandleViewController?.setTranslationYForSwipe(0f)
         stashHandleViewAlpha?.value = 1f
         this.bubbleBarTranslationYAnimator.updateValue(getStashTranslation())
@@ -212,7 +229,6 @@
         bubbleBarBackgroundScaleY.updateValue(getStashScaleY())
         isStashed = true
         bubbleBarViewController.setHiddenForStashed(true)
-        onIsStashedChanged()
     }
 
     override fun getTouchableHeight(): Int =
@@ -247,6 +263,29 @@
         updateStashedAndExpandedState(stash = true, expand = false)
     }
 
+    override fun stashBubbleBarToLocation(
+        fromLocation: BubbleBarLocation,
+        toLocation: BubbleBarLocation,
+    ) {
+        if (fromLocation.isSameSideWith(toLocation)) {
+            updateStashedAndExpandedState(
+                stash = true,
+                expand = false,
+                updateTouchRegionOnEnd = false,
+            )
+            return
+        }
+        cancelAnimation()
+        animator =
+            AnimatorSet().apply {
+                playSequentially(
+                    bubbleBarViewController.animateBubbleBarLocationOut(toLocation),
+                    createHandleInAnimator(location = toLocation),
+                )
+                start()
+            }
+    }
+
     override fun showBubbleBar(expandBubbles: Boolean, bubbleBarGesture: Boolean) {
         updateStashedAndExpandedState(
             stash = false,
@@ -255,6 +294,33 @@
         )
     }
 
+    override fun showBubbleBarAtLocation(
+        fromLocation: BubbleBarLocation,
+        toLocation: BubbleBarLocation,
+    ) {
+        if (fromLocation.isSameSideWith(toLocation)) {
+            updateStashedAndExpandedState(
+                stash = false,
+                expand = false,
+                updateTouchRegionOnEnd = false,
+            )
+            return
+        }
+        cancelAnimation()
+        val bubbleBarInAnimation =
+            bubbleBarViewController.animateBubbleBarLocationIn(fromLocation, toLocation).apply {
+                doOnStart { showBubbleBarImmediateVisually(bubbleBarTranslationY) }
+            }
+        animator =
+            AnimatorSet().apply {
+                playSequentially(
+                    createHandleOutAnimator(location = toLocation),
+                    bubbleBarInAnimation,
+                )
+                start()
+            }
+    }
+
     override fun getDiffBetweenHandleAndBarCenters(): Float {
         // the difference between the centers of the handle and the bubble bar is the difference
         // between their distance from the bottom of the screen.
@@ -392,7 +458,7 @@
             bubbleBarAlpha.value = 1f
         }
         animatorSet.doOnEnd {
-            animator = null
+            cancelAnimation()
             controllersAfterInitAction.runAfterInit {
                 if (isStashed) {
                     bubbleBarAlpha.value = 0f
@@ -486,6 +552,7 @@
         stash: Boolean,
         expand: Boolean,
         bubbleBarGesture: Boolean = false,
+        updateTouchRegionOnEnd: Boolean = true,
     ) {
         if (bubbleBarViewController.isHiddenForNoBubbles) {
             // If there are no bubbles the bar and handle are invisible, nothing to do here.
@@ -498,11 +565,13 @@
             // notify the view controller that the stash state is about to change so that it can
             // cancel an ongoing animation if there is one.
             bubbleBarViewController.onStashStateChanging()
-            animator?.cancel()
+            cancelAnimation()
             animator =
                 createStashAnimator(isStashed, BAR_STASH_DURATION).apply {
                     updateBarVisibility(isStashed)
-                    updateTouchRegionOnAnimationEnd()
+                    if (updateTouchRegionOnEnd) {
+                        updateTouchRegionOnAnimationEnd()
+                    }
                     start()
                 }
         }
@@ -512,6 +581,11 @@
         }
     }
 
+    private fun cancelAnimation() {
+        animator?.cancel()
+        animator = null
+    }
+
     override fun getHandleViewAlpha(): MultiPropertyFactory<View>.MultiProperty? =
         // only return handle alpha if the bubble bar is stashed and has bubbles
         if (isStashed && bubbleBarViewController.hasBubbles()) {
@@ -534,6 +608,49 @@
         return this
     }
 
+    // TODO(b/399678274) add tests
+    private fun createHandleInAnimator(location: BubbleBarLocation): Animator? {
+        val stashHandleViewController = bubbleStashedHandleViewController ?: return null
+        val handleAlpha = stashHandleViewController.stashedHandleAlpha.get(0)
+        val shift = context.inShiftX
+        val startX = if (location.isOnLeft(context.isRtl)) shift else -shift
+        val handleTranslationX =
+            ValueAnimator.ofFloat(startX, 0f).apply {
+                addUpdateListener { v ->
+                    stashHandleViewController.setTranslationX(v.animatedValue as Float)
+                }
+                duration = FADE_IN_ANIM_ALPHA_DURATION_MS
+            }
+        val handleAlphaAnimation =
+            handleAlpha.animateToValue(1f).apply { duration = FADE_IN_ANIM_ALPHA_DURATION_MS }
+        return AnimatorSet().apply {
+            playTogether(handleTranslationX, handleAlphaAnimation)
+            doOnStart { stashBubbleBarImmediateVisually() }
+        }
+    }
+
+    private fun createHandleOutAnimator(location: BubbleBarLocation): Animator? {
+        val stashHandleViewController = bubbleStashedHandleViewController ?: return null
+        val handleAlpha = stashHandleViewController.stashedHandleAlpha.get(0)
+        val shift = context.outShift
+        val endX = if (location.isOnLeft(context.isRtl)) -shift else shift
+        val handleTranslationX =
+            ValueAnimator.ofFloat(0f, endX).apply {
+                addUpdateListener { v ->
+                    stashHandleViewController.setTranslationX(v.animatedValue as Float)
+                }
+                duration = FADE_OUT_ANIM_POSITION_DURATION_MS
+                // in case item dropped to the opposite side - need to clear translation
+                doOnEnd { stashHandleViewController.setTranslationX(0f) }
+            }
+        val handleAlphaAnimation =
+            handleAlpha.animateToValue(0f).apply {
+                duration = FADE_OUT_ANIM_ALPHA_DURATION_MS
+                startDelay = FADE_OUT_ANIM_ALPHA_DELAY_MS
+            }
+        return AnimatorSet().apply { playTogether(handleTranslationX, handleAlphaAnimation) }
+    }
+
     private fun Animator.setBubbleBarPivotDuringAnim(pivotX: Float, pivotY: Float): Animator {
         var initialPivotX = Float.NaN
         var initialPivotY = Float.NaN
@@ -549,4 +666,9 @@
         }
         return this
     }
+
+    private fun BubbleBarLocation.isSameSideWith(anotherLocation: BubbleBarLocation): Boolean {
+        val isRtl = context.isRtl
+        return this.isOnLeft(isRtl) == anotherLocation.isOnLeft(isRtl)
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/growth/ActionPerformers.kt b/quickstep/src/com/android/launcher3/taskbar/growth/ActionPerformers.kt
new file mode 100644
index 0000000..17c9509
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/growth/ActionPerformers.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.launcher3.taskbar.growth
+
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+
+object ActionPerformers {
+    fun interface DismissCallback {
+        fun invoke()
+    }
+
+    fun performActions(actions: List<Action>, context: Context, dismissCallback: DismissCallback) {
+        for (action in actions) {
+            performAction(action, context, dismissCallback)
+        }
+    }
+
+    private fun performAction(action: Action, context: Context, dismissCallback: DismissCallback) {
+        when (action) {
+            is Action.Dismiss -> {
+                // TODO: b/396239267 - Handle marking the campaign dismissed with dismissal
+                //  retention.
+                dismissCallback.invoke()
+            }
+            is Action.OpenUrl -> openUrl(action.url, context)
+        // Handle other actions
+        }
+    }
+
+    fun openUrl(url: String, context: Context) {
+        val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        context.startActivity(intent)
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
index e487f9f..3712a76 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
@@ -27,7 +27,6 @@
 import com.android.launcher3.DeviceProfile
 import com.android.launcher3.R
 import com.android.launcher3.Utilities
-import com.android.launcher3.taskbar.TaskbarActivityContext
 import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
 
 /**
@@ -48,7 +47,7 @@
     protected val startContextualContainer: ViewGroup,
     protected val imeSwitcher: ImageView?,
     protected val a11yButton: ImageView?,
-    protected val space: Space?
+    protected val space: Space?,
 ) : NavButtonLayoutter {
     protected val homeButton: ImageView? = navButtonContainer.findViewById(R.id.home)
     protected val recentsButton: ImageView? = navButtonContainer.findViewById(R.id.recent_apps)
@@ -69,26 +68,34 @@
         val params =
             FrameLayout.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
-                ViewGroup.LayoutParams.MATCH_PARENT
+                ViewGroup.LayoutParams.MATCH_PARENT,
             )
         params.gravity = Gravity.CENTER
         return params
     }
 
+    /**
+     * Adjusts the layout parameters of the nav bar container for setup in phone mode.
+     *
+     * @param nearestTouchFrameLayoutParams The layout parameters of the navButtonsView, which is
+     *   the ViewGroup that contains start, end, nav button ViewGroups
+     * @param deviceProfile The device profile containing information about the device's
+     *   configuration.
+     */
     fun adjustForSetupInPhoneMode(
-        navButtonsLayoutParams: FrameLayout.LayoutParams,
-        navButtonsViewLayoutParams: FrameLayout.LayoutParams,
-        deviceProfile: DeviceProfile
+        nearestTouchFrameLayoutParams: FrameLayout.LayoutParams,
+        deviceProfile: DeviceProfile,
     ) {
         val phoneOrPortraitSetupMargin =
             resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_margin)
-        navButtonsLayoutParams.marginStart = phoneOrPortraitSetupMargin
-        navButtonsLayoutParams.bottomMargin =
+        nearestTouchFrameLayoutParams.marginStart = phoneOrPortraitSetupMargin
+        nearestTouchFrameLayoutParams.bottomMargin =
             if (!deviceProfile.isLandscape) 0
             else
                 phoneOrPortraitSetupMargin -
-                        resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2
-        navButtonsViewLayoutParams.height =
+                    resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2
+
+        nearestTouchFrameLayoutParams.height =
             resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_height)
     }
 
@@ -97,7 +104,7 @@
         buttonSize: Int,
         barAxisMarginStart: Int,
         barAxisMarginEnd: Int,
-        gravity: Int
+        gravity: Int,
     ) {
         val contextualContainerParams =
             FrameLayout.LayoutParams(buttonSize, ViewGroup.LayoutParams.MATCH_PARENT)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
index 2497fbb..a199dba 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/NavButtonLayoutFactory.kt
@@ -66,7 +66,7 @@
             isInSetup: Boolean,
             isThreeButtonNav: Boolean,
             phoneMode: Boolean,
-            @Rotation surfaceRotation: Int
+            @Rotation surfaceRotation: Int,
         ): NavButtonLayoutter {
             val navButtonContainer =
                 navButtonsView.requireViewById<LinearLayout>(ID_END_NAV_BUTTONS)
@@ -77,6 +77,18 @@
             val isPhoneNavMode = phoneMode && isThreeButtonNav
             val isPhoneGestureMode = phoneMode && !isThreeButtonNav
             return when {
+                isInSetup -> {
+                    SetupNavLayoutter(
+                        resources,
+                        navButtonsView,
+                        navButtonContainer,
+                        endContextualContainer,
+                        startContextualContainer,
+                        imeSwitcher,
+                        a11yButton,
+                        space,
+                    )
+                }
                 isPhoneNavMode -> {
                     if (!deviceProfile.isLandscape) {
                         navButtonsView.setIsVertical(false)
@@ -87,7 +99,7 @@
                             startContextualContainer,
                             imeSwitcher,
                             a11yButton,
-                            space
+                            space,
                         )
                     } else if (surfaceRotation == ROTATION_90) {
                         navButtonsView.setIsVertical(true)
@@ -98,7 +110,7 @@
                             startContextualContainer,
                             imeSwitcher,
                             a11yButton,
-                            space
+                            space,
                         )
                     } else {
                         navButtonsView.setIsVertical(true)
@@ -109,36 +121,23 @@
                             startContextualContainer,
                             imeSwitcher,
                             a11yButton,
-                            space
+                            space,
                         )
                     }
                 }
                 isPhoneGestureMode -> {
                     PhoneGestureLayoutter(
                         resources,
-                        navButtonsView,
                         navButtonContainer,
                         endContextualContainer,
                         startContextualContainer,
                         imeSwitcher,
                         a11yButton,
-                        space
+                        space,
                     )
                 }
                 deviceProfile.isTaskbarPresent -> {
                     return when {
-                        isInSetup -> {
-                            SetupNavLayoutter(
-                                resources,
-                                navButtonsView,
-                                navButtonContainer,
-                                endContextualContainer,
-                                startContextualContainer,
-                                imeSwitcher,
-                                a11yButton,
-                                space
-                            )
-                        }
                         isKidsMode -> {
                             KidsNavLayoutter(
                                 resources,
@@ -147,7 +146,7 @@
                                 startContextualContainer,
                                 imeSwitcher,
                                 a11yButton,
-                                space
+                                space,
                             )
                         }
                         else ->
@@ -158,7 +157,7 @@
                                 startContextualContainer,
                                 imeSwitcher,
                                 a11yButton,
-                                space
+                                space,
                             )
                     }
                 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
index 390ec34..e0f2a22 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneGestureLayoutter.kt
@@ -17,25 +17,21 @@
 package com.android.launcher3.taskbar.navbutton
 
 import android.content.res.Resources
-import android.view.Gravity
 import android.view.ViewGroup
-import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.LinearLayout
 import android.widget.Space
-import com.android.launcher3.DeviceProfile
 import com.android.launcher3.taskbar.TaskbarActivityContext
 
 /** Layoutter for showing gesture navigation on phone screen. No buttons here, no-op container */
 class PhoneGestureLayoutter(
     resources: Resources,
-    navButtonsView: NearestTouchFrame,
     navBarContainer: LinearLayout,
     endContextualContainer: ViewGroup,
     startContextualContainer: ViewGroup,
     imeSwitcher: ImageView?,
     a11yButton: ImageView?,
-    space: Space?
+    space: Space?,
 ) :
     AbstractNavButtonLayoutter(
         resources,
@@ -44,33 +40,10 @@
         startContextualContainer,
         imeSwitcher,
         a11yButton,
-        space
+        space,
     ) {
-    private val mNavButtonsView = navButtonsView
 
     override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
-        // TODO: look into if we should use SetupNavLayoutter instead.
-        if (!context.isUserSetupComplete) {
-            // 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
-            val navButtonsViewLayoutParams =
-                mNavButtonsView.layoutParams as FrameLayout.LayoutParams
-            val deviceProfile: DeviceProfile = context.deviceProfile
-
-            navButtonsLayoutParams.marginEnd = 0
-            navButtonsLayoutParams.gravity = Gravity.START
-            context.setTaskbarWindowSize(context.setupWindowSize)
-
-            adjustForSetupInPhoneMode(
-                navButtonsLayoutParams,
-                navButtonsViewLayoutParams,
-                deviceProfile
-            )
-            mNavButtonsView.layoutParams = navButtonsViewLayoutParams
-            navButtonContainer.layoutParams = navButtonsLayoutParams
-        }
-
         endContextualContainer.removeAllViews()
         startContextualContainer.removeAllViews()
     }
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
index e032430..eb3fdeb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -29,12 +29,15 @@
 import com.android.launcher3.R
 import com.android.launcher3.taskbar.TaskbarActivityContext
 
+const val SUW_THEME_SYSTEM_PROPERTY = "setupwizard.theme"
+const val GLIF_EXPRESSIVE_THEME = "glif_expressive"
+const val GLIF_EXPRESSIVE_LIGHT_THEME = "glif_expressive_light"
 const val SQUARE_ASPECT_RATIO_BOTTOM_BOUND = 0.95
 const val SQUARE_ASPECT_RATIO_UPPER_BOUND = 1.05
 
 class SetupNavLayoutter(
     resources: Resources,
-    navButtonsView: NearestTouchFrame,
+    nearestTouchFrame: NearestTouchFrame,
     navButtonContainer: LinearLayout,
     endContextualContainer: ViewGroup,
     startContextualContainer: ViewGroup,
@@ -51,17 +54,19 @@
         a11yButton,
         space,
     ) {
-    private val mNavButtonsView = navButtonsView
+    // mNearestTouchFrame is a ViewGroup that contains start, end, nav button ViewGroups
+    private val mNearestTouchFrame = nearestTouchFrame
 
     override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
-        val SUWTheme = SystemProperties.get("setupwizard.theme", "")
-        if (SUWTheme == "glif_expressive" || SUWTheme == "glif_expressive_light") {
+        val SUWTheme = SystemProperties.get(SUW_THEME_SYSTEM_PROPERTY, "")
+        if (SUWTheme == GLIF_EXPRESSIVE_THEME || SUWTheme == GLIF_EXPRESSIVE_LIGHT_THEME) {
             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
-        val navButtonsViewLayoutParams = mNavButtonsView.layoutParams as FrameLayout.LayoutParams
+        val navButtonsOverallViewGroupLayoutParams =
+            mNearestTouchFrame.layoutParams as FrameLayout.LayoutParams
         val deviceProfile: DeviceProfile = context.deviceProfile
 
         navButtonsLayoutParams.marginEnd = 0
@@ -77,18 +82,14 @@
         ) {
             navButtonsLayoutParams.marginStart =
                 resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_start_margin)
-            navButtonsViewLayoutParams.bottomMargin =
+            navButtonsOverallViewGroupLayoutParams.bottomMargin =
                 resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_bottom_margin)
             navButtonsLayoutParams.height =
                 resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_height)
         } else {
-            adjustForSetupInPhoneMode(
-                navButtonsLayoutParams,
-                navButtonsViewLayoutParams,
-                deviceProfile,
-            )
+            adjustForSetupInPhoneMode(navButtonsOverallViewGroupLayoutParams, deviceProfile)
         }
-        mNavButtonsView.layoutParams = navButtonsViewLayoutParams
+        mNearestTouchFrame.layoutParams = navButtonsOverallViewGroupLayoutParams
         navButtonContainer.layoutParams = navButtonsLayoutParams
 
         endContextualContainer.removeAllViews()
diff --git a/quickstep/src/com/android/quickstep/DisplayModel.kt b/quickstep/src/com/android/quickstep/DisplayModel.kt
index 3de6fd0..0b8af40 100644
--- a/quickstep/src/com/android/quickstep/DisplayModel.kt
+++ b/quickstep/src/com/android/quickstep/DisplayModel.kt
@@ -23,10 +23,13 @@
 import android.view.Display
 import androidx.core.util.valueIterator
 import com.android.quickstep.DisplayModel.DisplayResource
+import com.android.quickstep.SystemDecorationChangeObserver.Companion.INSTANCE
+import com.android.quickstep.SystemDecorationChangeObserver.DisplayDecorationListener
 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) {
+abstract class DisplayModel<RESOURCE_TYPE : DisplayResource>(val context: Context) :
+    DisplayDecorationListener {
 
     companion object {
         private const val TAG = "DisplayModel"
@@ -34,19 +37,20 @@
     }
 
     private val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+    private var systemDecorationChangeObserver: SystemDecorationChangeObserver? = null
     protected val displayResourceArray = SparseArray<RESOURCE_TYPE>()
 
-    fun onDisplayAddSystemDecorations(displayId: Int) {
+    override fun onDisplayAddSystemDecorations(displayId: Int) {
         if (DEBUG) Log.d(TAG, "onDisplayAdded: displayId=$displayId")
         storeDisplayResource(displayId)
     }
 
-    fun onDisplayRemoved(displayId: Int) {
+    override fun onDisplayRemoved(displayId: Int) {
         if (DEBUG) Log.d(TAG, "onDisplayRemoved: displayId=$displayId")
         deleteDisplayResource(displayId)
     }
 
-    fun onDisplayRemoveSystemDecorations(displayId: Int) {
+    override fun onDisplayRemoveSystemDecorations(displayId: Int) {
         if (DEBUG) Log.d(TAG, "onDisplayRemoveSystemDecorations: displayId=$displayId")
         deleteDisplayResource(displayId)
     }
@@ -54,12 +58,16 @@
     protected abstract fun createDisplayResource(display: Display): RESOURCE_TYPE
 
     protected fun initializeDisplays() {
+        systemDecorationChangeObserver = INSTANCE[context]
+        systemDecorationChangeObserver?.registerDisplayDecorationListener(this)
         displayManager.displays
             .filter { getDisplayResource(it.displayId) == null }
             .forEach { storeDisplayResource(it.displayId) }
     }
 
     fun destroy() {
+        systemDecorationChangeObserver?.unregisterDisplayDecorationListener(this)
+        systemDecorationChangeObserver = null
         displayResourceArray.valueIterator().forEach { displayResource ->
             displayResource.cleanup()
         }
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index faaade9..fa8e484 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -261,7 +261,8 @@
         return launcher != null
                 && launcher.getStateManager().getState() == OVERVIEW
                 && launcher.isStarted()
-                && TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false).isHomeTask();
+                && TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false,
+                launcher.getDisplayId()).isHomeTask();
     }
 
     private boolean isInMinusOne() {
@@ -270,7 +271,8 @@
         return launcher != null
                 && launcher.getStateManager().getState() == NORMAL
                 && !launcher.isStarted()
-                && TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false).isHomeTask();
+                && TopTaskTracker.INSTANCE.get(launcher).getCachedTopTask(false,
+                launcher.getDisplayId()).isHomeTask();
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index dab78c5..a772415 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -504,7 +504,7 @@
             for (TaskInfo taskInfo : recentTaskInfo.getTaskInfoList()) {
                 Task task = createTask(taskInfo, minimizedTaskIds);
                 List<Task> tasks = perDisplayTasks.computeIfAbsent(
-                        ExternalDisplaysKt.getDisplayId(task),
+                        ExternalDisplaysKt.getSafeDisplayId(task),
                         k -> new ArrayList<>());
                 tasks.add(task);
             }
diff --git a/quickstep/src/com/android/quickstep/SystemDecorationChangeObserver.kt b/quickstep/src/com/android/quickstep/SystemDecorationChangeObserver.kt
new file mode 100644
index 0000000..4559478
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/SystemDecorationChangeObserver.kt
@@ -0,0 +1,81 @@
+/*
+ * 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 android.util.Log
+import com.android.launcher3.dagger.ApplicationContext
+import com.android.launcher3.dagger.LauncherAppSingleton
+import com.android.launcher3.util.DaggerSingletonObject
+import com.android.launcher3.util.Executors.MAIN_EXECUTOR
+import com.android.quickstep.dagger.QuickstepBaseAppComponent
+import javax.inject.Inject
+
+@LauncherAppSingleton
+class SystemDecorationChangeObserver @Inject constructor(@ApplicationContext context: Context) {
+    companion object {
+        private const val TAG = "SystemDecorationChangeObserver"
+        private const val DEBUG = false
+
+        @JvmStatic
+        val INSTANCE: DaggerSingletonObject<SystemDecorationChangeObserver> =
+            DaggerSingletonObject<SystemDecorationChangeObserver>(
+                QuickstepBaseAppComponent::getSystemDecorationChangeObserver
+            )
+    }
+
+    interface DisplayDecorationListener {
+        fun onDisplayAddSystemDecorations(displayId: Int)
+
+        fun onDisplayRemoved(displayId: Int)
+
+        fun onDisplayRemoveSystemDecorations(displayId: Int)
+    }
+
+    fun notifyAddSystemDecorations(displayId: Int) {
+        if (DEBUG) Log.d(TAG, "SystemDecorationAdded: $displayId")
+        for (listener in mDisplayDecorationListeners) {
+            MAIN_EXECUTOR.execute { listener.onDisplayAddSystemDecorations(displayId) }
+        }
+    }
+
+    fun notifyOnDisplayRemoved(displayId: Int) {
+        if (DEBUG) Log.d(TAG, "displayRemoved: $displayId")
+        for (listener in mDisplayDecorationListeners) {
+            MAIN_EXECUTOR.execute { listener.onDisplayRemoved(displayId) }
+        }
+    }
+
+    fun notifyDisplayRemoveSystemDecorations(displayId: Int) {
+        if (DEBUG) Log.d(TAG, "SystemDecorationRemoved: $displayId")
+        for (listener in mDisplayDecorationListeners) {
+            MAIN_EXECUTOR.execute { listener.onDisplayRemoveSystemDecorations(displayId) }
+        }
+    }
+
+    private val mDisplayDecorationListeners = ArrayList<DisplayDecorationListener>()
+
+    fun registerDisplayDecorationListener(listener: DisplayDecorationListener) {
+        if (DEBUG) Log.d(TAG, "registerDisplayDecorationListener")
+        mDisplayDecorationListeners.add(listener)
+    }
+
+    fun unregisterDisplayDecorationListener(listener: DisplayDecorationListener) {
+        if (DEBUG) Log.d(TAG, "unregisterDisplayDecorationListener")
+        mDisplayDecorationListeners.remove(listener)
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/TopTaskTracker.java b/quickstep/src/com/android/quickstep/TopTaskTracker.java
index 8116a88..7e773e3 100644
--- a/quickstep/src/com/android/quickstep/TopTaskTracker.java
+++ b/quickstep/src/com/android/quickstep/TopTaskTracker.java
@@ -22,8 +22,10 @@
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.android.launcher3.Flags.enableOverviewOnConnectedDisplays;
 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.quickstep.fallback.window.RecentsWindowFlags.enableOverviewOnConnectedDisplays;
 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;
@@ -47,6 +49,7 @@
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.dagger.QuickstepBaseAppComponent;
 import com.android.quickstep.util.DesksUtils;
+import com.android.quickstep.util.ExternalDisplaysKt;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -316,21 +319,26 @@
      */
     @NonNull
     @UiThread
-    public CachedTaskInfo getCachedTopTask(boolean filterOnlyVisibleRecents) {
+    public CachedTaskInfo getCachedTopTask(boolean filterOnlyVisibleRecents, int displayId) {
         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 as gesture nav is only supported there
-            return new CachedTaskInfo(mVisibleTasks.get(DEFAULT_DISPLAY));
+            return new CachedTaskInfo(mVisibleTasks.get(displayId));
         } else {
             if (filterOnlyVisibleRecents) {
                 // Since we only know about the top most task, any filtering may not be applied on
                 // the cache. The second to top task may change while the top task is still the
                 // same.
-                RunningTaskInfo[] tasks = TraceHelper.allowIpcs("getCachedTopTask.true", () ->
+                TaskInfo[] tasks = TraceHelper.allowIpcs("getCachedTopTask.true", () ->
                         ActivityManagerWrapper.getInstance().getRunningTasks(true));
-                return new CachedTaskInfo(Arrays.asList(tasks));
+                if (enableOverviewOnConnectedDisplays()) {
+                    return new CachedTaskInfo(Arrays.stream(tasks).filter(
+                            info -> ExternalDisplaysKt.getSafeDisplayId(info)
+                                    == displayId).toList());
+                } else {
+                    return new CachedTaskInfo(Arrays.asList(tasks));
+                }
             }
 
             if (mOrderedTaskList.isEmpty()) {
@@ -344,7 +352,12 @@
             // Strip the pinned task and recents task
             tasks.removeIf(t -> t.taskId == mPinnedTaskId || isRecentsTask(t)
                     ||  DesksUtils.isDesktopWallpaperTask(t));
-            return new CachedTaskInfo(tasks);
+            if (enableOverviewOnConnectedDisplays()) {
+                return new CachedTaskInfo(tasks.stream().filter(
+                        info -> ExternalDisplaysKt.getSafeDisplayId(info) == displayId).toList());
+            } else {
+                return new CachedTaskInfo(tasks);
+            }
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 4ab8d1b..30936ad 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -314,32 +314,25 @@
         @BinderThread
         @Override
         public void onDisplayAddSystemDecorations(int displayId) {
-            executeForTaskbarManager(taskbarManager ->
-                    taskbarManager.onDisplayAddSystemDecorations(displayId));
-
-            executeForRecentsDisplayModel(displayModel ->
-                    displayModel.onDisplayAddSystemDecorations(displayId));
+            executeForTouchInteractionService(tis ->
+                    tis.mSystemDecorationChangeObserver.notifyAddSystemDecorations(displayId));
         }
 
         @BinderThread
         @Override
         public void onDisplayRemoved(int displayId) {
-            executeForTaskbarManager(taskbarManager ->
-                    taskbarManager.onDisplayRemoved(displayId));
             executeForTouchInteractionService(tis -> {
+                tis.mSystemDecorationChangeObserver.notifyOnDisplayRemoved(displayId);
                 tis.mDeviceState.clearSysUIStateFlagsForDisplay(displayId);
             });
-            executeForRecentsDisplayModel(displayModel ->
-                    displayModel.onDisplayRemoved(displayId));
         }
 
         @BinderThread
         @Override
         public void onDisplayRemoveSystemDecorations(int displayId) {
-            executeForTaskbarManager(taskbarManager ->
-                    taskbarManager.onDisplayRemoveSystemDecorations(displayId));
-            executeForRecentsDisplayModel(displayModel ->
-                    displayModel.onDisplayRemoveSystemDecorations(displayId));
+            executeForTouchInteractionService(tis -> {
+                tis.mSystemDecorationChangeObserver.notifyDisplayRemoveSystemDecorations(displayId);
+            });
         }
 
         @BinderThread
@@ -451,15 +444,6 @@
             }));
         }
 
-        private void executeForRecentsDisplayModel(
-                @NonNull Consumer<RecentsDisplayModel> recentsDisplayModelConsumer) {
-            MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
-                RecentsDisplayModel recentsDisplayModel = tis.mRecentsDisplayModel;
-                if (recentsDisplayModel == null) return;
-                recentsDisplayModelConsumer.accept(recentsDisplayModel);
-            }));
-        }
-
         /**
          * Returns the {@link TaskbarManager}.
          * <p>
@@ -596,6 +580,8 @@
 
     private RecentsDisplayModel mRecentsDisplayModel;
 
+    private SystemDecorationChangeObserver mSystemDecorationChangeObserver;
+
     @Override
     public void onCreate() {
         super.onCreate();
@@ -607,6 +593,7 @@
         mDeviceState = RecentsAnimationDeviceState.INSTANCE.get(this);
         mRotationTouchHelper = RotationTouchHelper.INSTANCE.get(this);
         mRecentsDisplayModel = RecentsDisplayModel.getINSTANCE().get(this);
+        mSystemDecorationChangeObserver = SystemDecorationChangeObserver.getINSTANCE().get(this);
         mAllAppsActionManager = new AllAppsActionManager(
                 this, UI_HELPER_EXECUTOR, this::createAllAppsPendingIntent);
         mTrackpadsConnected = new ActiveTrackpadList(this, () -> {
@@ -1061,7 +1048,8 @@
 
     private boolean isHoverActionWithoutConsumer(MotionEvent event) {
         // Only process these events when taskbar is present.
-        TaskbarActivityContext tac = mTaskbarManager.getCurrentActivityContext();
+        int displayId = event.getDisplayId();
+        TaskbarActivityContext tac = mTaskbarManager.getTaskbarForDisplay(displayId);
         boolean isTaskbarPresent = tac != null && tac.getDeviceProfile().isTaskbarPresent
                 && !tac.isPhoneMode();
         return event.isHoverEvent() && (mUncheckedConsumer.getType() & TYPE_CURSOR_HOVER) == 0
@@ -1088,7 +1076,7 @@
             // previousTaskInfo can be null iff previousGestureState == GestureState.DEFAULT_STATE
             taskInfo = previousTaskInfo != null
                     ? previousTaskInfo
-                    : TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false);
+                    : TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false, displayId);
             gestureState.updateRunningTask(taskInfo);
             gestureState.updateLastStartedTaskIds(previousGestureState.getLastStartedTaskIds());
             gestureState.updatePreviouslyAppearedTaskIds(
@@ -1098,7 +1086,7 @@
                     mOverviewComponentObserver,
                     displayId,
                     ActiveGestureLog.INSTANCE.incrementLogId());
-            taskInfo = TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false);
+            taskInfo = TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false, displayId);
             gestureState.updateRunningTask(taskInfo);
         }
         gestureState.setTrackpadGestureType(trackpadGestureType);
diff --git a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
index d79a8ea..23b8a82 100644
--- a/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
+++ b/quickstep/src/com/android/quickstep/dagger/QuickstepBaseAppComponent.java
@@ -25,6 +25,7 @@
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.RotationTouchHelper;
 import com.android.quickstep.SimpleOrientationTouchTransformer;
+import com.android.quickstep.SystemDecorationChangeObserver;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TopTaskTracker;
 import com.android.quickstep.fallback.window.RecentsDisplayModel;
@@ -71,4 +72,5 @@
 
     SimpleOrientationTouchTransformer getSimpleOrientationTouchTransformer();
 
+    SystemDecorationChangeObserver getSystemDecorationChangeObserver();
 }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
index 107babd..571a546 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressHandler.java
@@ -88,7 +88,7 @@
      */
     @Nullable
     @VisibleForTesting
-    final Runnable getLongPressRunnable(NavHandle navHandle) {
+    final Runnable getLongPressRunnable(NavHandle navHandle, int displayId) {
         if (!isContextualSearchEntrypointEnabled(navHandle)) {
             Log.i(TAG, "Contextual Search invocation failed: entry point disabled");
             mVibratorWrapper.cancelVibrate();
@@ -116,7 +116,7 @@
                 Log.i(TAG, "Contextual Search invocation successful");
 
                 String runningPackage = TopTaskTracker.INSTANCE.get(mContext).getCachedTopTask(
-                        /* filterOnlyVisibleRecents */ true).getPackageName();
+                        /* filterOnlyVisibleRecents */ true, displayId).getPackageName();
                 mStatsLogManager.logger().withPackageName(runningPackage)
                         .log(LAUNCHER_LAUNCH_ASSISTANT_SUCCESSFUL_NAV_HANDLE);
             } else {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index baabde8..af7c975 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -214,7 +214,7 @@
                 && !mDeepPressLogged) {
             // Log deep press even if feature is disabled.
             String runningPackage = mTopTaskTracker.getCachedTopTask(
-                    /* filterOnlyVisibleRecents */ true).getPackageName();
+                    /* filterOnlyVisibleRecents */ true, getDisplayId()).getPackageName();
             mStatsLogManager.logger().withPackageName(runningPackage).log(
                     mNavHandle.isNavHandleStashedTaskbar() ? LAUNCHER_DEEP_PRESS_STASHED_TASKBAR
                             : LAUNCHER_DEEP_PRESS_NAVBAR);
@@ -233,12 +233,13 @@
             Log.d(TAG, "triggerLongPress");
         }
         String runningPackage = mTopTaskTracker.getCachedTopTask(
-                /* filterOnlyVisibleRecents */ true).getPackageName();
+                /* filterOnlyVisibleRecents */ true, getDisplayId()).getPackageName();
         mStatsLogManager.logger().withPackageName(runningPackage).log(
                 mNavHandle.isNavHandleStashedTaskbar() ? LAUNCHER_LONG_PRESS_STASHED_TASKBAR
                         : LAUNCHER_LONG_PRESS_NAVBAR);
 
-        Runnable longPressRunnable = mNavHandleLongPressHandler.getLongPressRunnable(mNavHandle);
+        Runnable longPressRunnable = mNavHandleLongPressHandler.getLongPressRunnable(mNavHandle,
+                getDisplayId());
         if (longPressRunnable == null) {
             return;
         }
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index 5995ca2..c7e2ade6 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -30,8 +30,11 @@
 import android.graphics.Matrix;
 import android.graphics.Outline;
 import android.graphics.Rect;
+import android.graphics.Typeface;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.RippleDrawable;
+import android.os.SystemProperties;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -56,11 +59,12 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.util.SettingsCache;
 import com.android.launcher3.views.ClipIconView;
 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.Flags;
 import com.android.wm.shell.shared.TypefaceUtils.FontFamily;
 
 import com.airbnb.lottie.LottieAnimationView;
@@ -80,6 +84,10 @@
     private static final String PIXEL_TIPS_APP_PACKAGE_NAME = "com.google.android.apps.tips";
     private static final CharSequence DEFAULT_PIXEL_TIPS_APP_NAME = "Pixel Tips";
 
+    private static final String SUW_THEME_SYSTEM_PROPERTY = "setupwizard.theme";
+    private static final String GLIF_EXPRESSIVE_THEME = "glif_expressive";
+    private static final String GLIF_EXPRESSIVE_LIGHT_THEME = "glif_expressive_light";
+
     private static final int FEEDBACK_ANIMATION_MS = 133;
     private static final int SUBTITLE_ANNOUNCE_DELAY_MS = 3000;
     private static final int DONE_BUTTON_ANNOUNCE_DELAY_MS = 4000;
@@ -115,6 +123,7 @@
     protected View mExitingAppView;
     protected int mExitingAppRadius;
     private final AlertDialog mSkipTutorialDialog;
+    private final boolean mIsExpressiveThemeEnabledInSUW;
 
     private boolean mGestureCompleted = false;
     protected LottieAnimationView mAnimatedGestureDemonstration;
@@ -172,7 +181,11 @@
 
         mFeedbackTitleView.setText(getIntroductionTitle());
         mFeedbackSubtitleView.setText(getIntroductionSubtitle());
-        setTitleTypefaces();
+
+        String SUWTheme = SystemProperties.get(SUW_THEME_SYSTEM_PROPERTY, "");
+        mIsExpressiveThemeEnabledInSUW = SUWTheme.equals(GLIF_EXPRESSIVE_THEME) || SUWTheme.equals(
+                GLIF_EXPRESSIVE_LIGHT_THEME);
+        maybeSetTitleTypefaces();
 
         mExitingAppView.setClipToOutline(true);
         mExitingAppView.setOutlineProvider(new ViewOutlineProvider() {
@@ -406,12 +419,21 @@
 
         mFeedbackTitleView.setText(titleResId);
         mFeedbackSubtitleView.setText(subtitleResId);
+
+        boolean isUserSetupComplete = SettingsCache.INSTANCE.get(mContext).getValue(
+                Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
+        boolean userSetupNotCompleteAndExpressiveThemeEnabled =
+                !isUserSetupComplete && mIsExpressiveThemeEnabledInSUW;
+        boolean userSetupCompleteAndNewFontsEnabled = isUserSetupComplete && Flags.enableGsf();
+
         if (isGestureSuccessful) {
             if (mTutorialFragment.isAtFinalStep()) {
-                TypefaceUtils.setTypeface(
-                        mDoneButton,
-                        FontFamily.GSF_LABEL_LARGE
-                );
+                if (userSetupCompleteAndNewFontsEnabled
+                        || userSetupNotCompleteAndExpressiveThemeEnabled) {
+                    mDoneButton.setTypeface(
+                            Typeface.create(FontFamily.GSF_LABEL_LARGE.getValue(),
+                                    Typeface.NORMAL));
+                }
                 showActionButton();
             }
 
@@ -437,7 +459,7 @@
         mCheckmarkAnimation.setVisibility(View.VISIBLE);
         mCheckmarkAnimation.playAnimation();
         mFeedbackTitleView.setTextAppearance(getSuccessTitleTextAppearance());
-        setTitleTypefaces();
+        maybeSetTitleTypefaces();
     }
 
     public boolean isGestureCompleted() {
@@ -492,7 +514,7 @@
         mFeedbackTitleView.setTextAppearance(getTitleTextAppearance());
         mDoneButton.setTextAppearance(getDoneButtonTextAppearance());
 
-        setTitleTypefaces();
+        maybeSetTitleTypefaces();
         mDoneButton.getBackground().setTint(getDoneButtonColor());
         mCheckmarkAnimation.setAnimation(mTutorialFragment.isAtFinalStep()
                 ? R.raw.checkmark_animation_end
@@ -514,16 +536,15 @@
     /**
      * 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
-        );
+    private void maybeSetTitleTypefaces() {
+        if (mIsExpressiveThemeEnabledInSUW || Flags.enableGsf()) {
+            mFeedbackTitleView.setTypeface(Typeface.create(mTutorialFragment.isLargeScreen()
+                            ? FontFamily.GSF_DISPLAY_MEDIUM_EMPHASIZED.getValue()
+                            : FontFamily.GSF_DISPLAY_SMALL_EMPHASIZED.getValue(),
+                    Typeface.NORMAL));
+            mFeedbackSubtitleView.setTypeface(
+                    Typeface.create(FontFamily.GSF_BODY_LARGE.getValue(), Typeface.NORMAL));
+        }
     }
 
     protected void resetViewsForBackGesture() {
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
index b8f43a4..4b9eb9e 100644
--- a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
@@ -275,7 +275,7 @@
         desiredTaskId: Int,
         banner: View,
     ): Pair<Float, Float> {
-        val snapshotParams = thumbnailViews[0].layoutParams as LinearLayout.LayoutParams
+        val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
         val translationX = banner.height.toFloat()
         val translationY: Float
         if (splitBounds == null) {
diff --git a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
index 15eb69e..74ae688 100644
--- a/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/PortraitPagedViewHandler.kt
@@ -255,7 +255,7 @@
                 }
             } else {
                 if (desiredTaskId == splitBounds.leftTopTaskId) {
-                    val snapshotParams = thumbnailViews[0].layoutParams as LinearLayout.LayoutParams
+                    val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
                     val bottomRightTaskPlusDividerPercent =
                         (splitBounds.rightBottomTaskPercent + splitBounds.dividerPercent)
                     translationY =
diff --git a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
index 80b50cb..456115f 100644
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -26,7 +26,6 @@
 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
@@ -152,7 +151,7 @@
         desiredTaskId: Int,
         banner: View,
     ): Pair<Float, Float> {
-        val snapshotParams = thumbnailViews[0].layoutParams as LinearLayout.LayoutParams
+        val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
         val translationX: Float = (taskViewWidth - banner.height).toFloat()
         val translationY: Float
         if (splitBounds == null) {
diff --git a/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
index 679daf8..619075f 100644
--- a/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
+++ b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
@@ -17,51 +17,18 @@
 package com.android.quickstep.recents.ui.mapper
 
 import android.view.View.OnClickListener
-import com.android.launcher3.Flags.enableDesktopExplodedView
 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.ThumbnailHeader
 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
@@ -69,26 +36,48 @@
      *
      * @param taskData The [TaskData] to convert. Can be null or a specific subclass.
      * @param isLiveTile A flag indicating whether the task data represents live tile.
+     * @param hasHeader A flag indicating whether the UI should display a header.
+     * @param clickCloseListener A callback when the close button in the UI is clicked.
      * @return A [TaskThumbnailUiState] representing the UI state for the given task data.
      */
-    fun toTaskThumbnailUiState(taskData: TaskData?, isLiveTile: Boolean): TaskThumbnailUiState =
+    fun toTaskThumbnailUiState(
+        taskData: TaskData?,
+        isLiveTile: Boolean,
+        hasHeader: Boolean,
+        clickCloseListener: OnClickListener?,
+    ): TaskThumbnailUiState =
         when {
             taskData !is TaskData.Data -> Uninitialized
-            isLiveTile -> LiveTile
+            isLiveTile -> createLiveTileState(taskData, hasHeader, clickCloseListener)
             isBackgroundOnly(taskData) -> BackgroundOnly(taskData.backgroundColor)
             isSnapshotSplash(taskData) ->
                 SnapshotSplash(
-                    Snapshot(
-                        taskData.thumbnailData?.thumbnail!!,
-                        taskData.thumbnailData.rotation,
-                        taskData.backgroundColor,
-                    ),
+                    createSnapshotState(taskData, hasHeader, clickCloseListener),
                     taskData.icon,
                 )
-
             else -> Uninitialized
         }
 
+    private fun createSnapshotState(
+        taskData: TaskData.Data,
+        hasHeader: Boolean,
+        clickCloseListener: OnClickListener?,
+    ): Snapshot =
+        if (canHeaderBeCreated(taskData, hasHeader, clickCloseListener)) {
+            Snapshot.WithHeader(
+                taskData.thumbnailData?.thumbnail!!,
+                taskData.thumbnailData.rotation,
+                taskData.backgroundColor,
+                ThumbnailHeader(taskData.icon!!, taskData.titleDescription!!, clickCloseListener!!),
+            )
+        } else {
+            Snapshot.WithoutHeader(
+                taskData.thumbnailData?.thumbnail!!,
+                taskData.thumbnailData.rotation,
+                taskData.backgroundColor,
+            )
+        }
+
     private fun isBackgroundOnly(taskData: TaskData.Data) =
         taskData.isLocked || taskData.thumbnailData == null
 
@@ -100,9 +89,21 @@
         hasHeader: Boolean,
         clickCloseListener: OnClickListener?,
     ) =
-        enableDesktopExplodedView() &&
-            hasHeader &&
+        hasHeader &&
             taskData.icon != null &&
             taskData.titleDescription != null &&
             clickCloseListener != null
+
+    private fun createLiveTileState(
+        taskData: TaskData.Data,
+        hasHeader: Boolean,
+        clickCloseListener: OnClickListener?,
+    ) =
+        if (canHeaderBeCreated(taskData, hasHeader, clickCloseListener)) {
+            // TODO(http://b/353965691): figure out what to do when `icon` or `titleDescription` is
+            //  null.
+            LiveTile.WithHeader(
+                ThumbnailHeader(taskData.icon!!, taskData.titleDescription!!, clickCloseListener!!)
+            )
+        } else LiveTile.WithoutHeader
 }
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskContentView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskContentView.kt
deleted file mode 100644
index a40929c..0000000
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskContentView.kt
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * 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.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()
-        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 createHeaderView(taskHeaderState: TaskHeaderUiState) {
-        if (taskHeaderView == null && taskHeaderState is TaskHeaderUiState.ShowHeader) {
-            taskHeaderView =
-                LayoutInflater.from(context).inflate(R.layout.task_header_view, this, false)
-                    as TaskHeaderView
-            addView(taskHeaderView, 0)
-        }
-    }
-
-    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?,
-    ) {
-        createHeaderView(taskHeaderState)
-        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
deleted file mode 100644
index 09fb540..0000000
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskHeaderUiState.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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/TaskThumbnailUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
index a5c9ac0..db593d3 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
@@ -19,6 +19,7 @@
 import android.graphics.Bitmap
 import android.graphics.drawable.Drawable
 import android.view.Surface
+import android.view.View.OnClickListener
 import androidx.annotation.ColorInt
 
 sealed class TaskThumbnailUiState {
@@ -26,14 +27,37 @@
 
     data class BackgroundOnly(@ColorInt val backgroundColor: Int) : 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,
+    sealed class LiveTile : TaskThumbnailUiState() {
+        data class WithHeader(val header: ThumbnailHeader) : LiveTile()
+
+        data object WithoutHeader : LiveTile()
+    }
+
+    sealed class Snapshot {
+        abstract val bitmap: Bitmap
+        abstract val thumbnailRotation: Int
+        abstract val backgroundColor: Int
+
+        data class WithHeader(
+            override val bitmap: Bitmap,
+            @Surface.Rotation override val thumbnailRotation: Int,
+            @ColorInt override val backgroundColor: Int,
+            val header: ThumbnailHeader,
+        ) : Snapshot()
+
+        data class WithoutHeader(
+            override val bitmap: Bitmap,
+            @Surface.Rotation override val thumbnailRotation: Int,
+            @ColorInt override val backgroundColor: Int,
+        ) : Snapshot()
+    }
+
+    data class ThumbnailHeader(
+        val icon: Drawable,
+        val title: String,
+        val clickCloseListener: OnClickListener,
     )
 }
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 78a16f1..0edbacc 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -19,24 +19,32 @@
 import android.content.Context
 import android.graphics.Color
 import android.graphics.Matrix
+import android.graphics.Outline
+import android.graphics.Path
+import android.graphics.Rect
 import android.graphics.drawable.ShapeDrawable
 import android.util.AttributeSet
 import android.util.Log
+import android.view.LayoutInflater
 import android.view.View
+import android.view.ViewOutlineProvider
 import android.widget.FrameLayout
 import androidx.annotation.ColorInt
 import androidx.core.view.isInvisible
+import com.android.launcher3.Flags.enableDesktopExplodedView
 import com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA
 import com.android.launcher3.R
 import com.android.launcher3.util.MultiPropertyFactory
+import com.android.launcher3.util.ViewPool
 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.views.FixedSizeImageView
+import com.android.quickstep.views.TaskThumbnailViewHeader
 
-class TaskThumbnailView : FrameLayout {
+class TaskThumbnailView : FrameLayout, ViewPool.Reusable {
     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) }
@@ -45,9 +53,30 @@
     private val dimAlpha: MultiPropertyFactory<View> by lazy {
         MultiPropertyFactory(scrimView, VIEW_ALPHA, ScrimViewAlpha.entries.size, ::maxOf)
     }
+    private val outlinePath = Path()
+    private var onSizeChanged: ((width: Int, height: Int) -> Unit)? = null
+
+    private var taskThumbnailViewHeader: TaskThumbnailViewHeader? = null
 
     private var uiState: TaskThumbnailUiState = Uninitialized
 
+    /**
+     * 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()
+        }
+
     constructor(context: Context) : super(context)
 
     constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
@@ -58,8 +87,39 @@
         defStyleAttr: Int,
     ) : super(context, attrs, defStyleAttr)
 
-    fun onRecycle() {
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        maybeCreateHeader()
+    }
+
+    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() {
         uiState = Uninitialized
+        onSizeChanged = null
+        outlineBounds = null
         resetViews()
     }
 
@@ -70,7 +130,7 @@
         resetViews()
         when (state) {
             is Uninitialized -> {}
-            is LiveTile -> drawLiveWindow()
+            is LiveTile -> drawLiveWindow(state)
             is SnapshotSplash -> drawSnapshotSplash(state)
             is BackgroundOnly -> drawBackground(state.backgroundColor)
         }
@@ -95,12 +155,25 @@
         splashIcon.alpha = value
     }
 
-    fun parentScaleXUpdated(scaleX: Float) {
+    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()
+    }
+
+    override fun setScaleX(scaleX: Float) {
+        super.setScaleX(scaleX)
         // Splash icon should ignore scale on TTV
         splashIcon.scaleX = 1 / scaleX
     }
 
-    fun parentScaleYUpdated(scaleY: Float) {
+    override fun setScaleY(scaleY: Float) {
+        super.setScaleY(scaleY)
         // Splash icon should ignore scale on TTV
         splashIcon.scaleY = 1 / scaleY
     }
@@ -114,14 +187,20 @@
         splashIcon.setImageDrawable(null)
         scrimView.alpha = 0f
         setBackgroundColor(Color.BLACK)
+        taskThumbnailViewHeader?.isInvisible = true
     }
 
     private fun drawBackground(@ColorInt background: Int) {
         setBackgroundColor(background)
     }
 
-    private fun drawLiveWindow() {
+    private fun drawLiveWindow(liveTile: LiveTile) {
         liveTileView.isInvisible = false
+
+        if (liveTile is LiveTile.WithHeader) {
+            taskThumbnailViewHeader?.isInvisible = false
+            taskThumbnailViewHeader?.setHeader(liveTile.header)
+        }
     }
 
     private fun drawSnapshotSplash(snapshotSplash: SnapshotSplash) {
@@ -133,6 +212,11 @@
     }
 
     private fun drawSnapshot(snapshot: Snapshot) {
+        if (snapshot is Snapshot.WithHeader) {
+            taskThumbnailViewHeader?.isInvisible = false
+            taskThumbnailViewHeader?.setHeader(snapshot.header)
+        }
+
         drawBackground(snapshot.backgroundColor)
         thumbnailView.setImageBitmap(snapshot.bitmap)
         thumbnailView.isInvisible = false
@@ -148,6 +232,16 @@
         Log.d(TAG, "[TaskThumbnailView@${Integer.toHexString(hashCode())}] $message")
     }
 
+    private fun maybeCreateHeader() {
+        if (enableDesktopExplodedView() && taskThumbnailViewHeader == null) {
+            taskThumbnailViewHeader =
+                LayoutInflater.from(context)
+                    .inflate(R.layout.task_thumbnail_view_header, this, false)
+                    as TaskThumbnailViewHeader
+            addView(taskThumbnailViewHeader)
+        }
+    }
+
     private companion object {
         const val TAG = "TaskThumbnailView"
         private const val MAX_SCRIM_ALPHA = 0.4f
diff --git a/quickstep/src/com/android/quickstep/util/AppPairsController.java b/quickstep/src/com/android/quickstep/util/AppPairsController.java
index 8385485..24c01ae 100644
--- a/quickstep/src/com/android/quickstep/util/AppPairsController.java
+++ b/quickstep/src/com/android/quickstep/util/AppPairsController.java
@@ -32,7 +32,6 @@
 import static com.android.wm.shell.shared.split.SplitScreenConstants.getIndex;
 import static com.android.wm.shell.shared.split.SplitScreenConstants.isPersistentSnapPosition;
 
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.LauncherApps;
 import android.util.Log;
@@ -93,10 +92,10 @@
     private static final int BITMASK_SIZE = 16;
     private static final int BITMASK_FOR_SNAP_POSITION = (1 << BITMASK_SIZE) - 1;
 
-    private Context mContext;
+    private ActivityContext mContext;
     private final SplitSelectStateController mSplitSelectStateController;
     private final StatsLogManager mStatsLogManager;
-    public AppPairsController(Context context,
+    public AppPairsController(ActivityContext context,
             SplitSelectStateController splitSelectStateController,
             StatsLogManager statsLogManager) {
         mContext = context;
@@ -208,7 +207,7 @@
         }
         AppPairInfo newAppPair = new AppPairInfo(apps);
 
-        IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
+        IconCache iconCache = LauncherAppState.getInstance(mContext.asContext()).getIconCache();
         MODEL_EXECUTOR.execute(() -> {
             newAppPair.getAppContents().forEach(member -> {
                 member.title = "";
@@ -216,8 +215,8 @@
                 iconCache.getTitleAndIcon(member, member.getMatchingLookupFlag());
             });
             MAIN_EXECUTOR.execute(() -> {
-                LauncherAccessibilityDelegate delegate =
-                        QuickstepLauncher.getLauncher(mContext).getAccessibilityDelegate();
+                LauncherAccessibilityDelegate delegate = QuickstepLauncher.getLauncher(
+                        mContext.asContext()).getAccessibilityDelegate();
                 if (delegate != null) {
                     delegate.addToWorkspace(newAppPair, true, (success) -> {
                         if (success) {
@@ -300,7 +299,7 @@
      */
     @Nullable
     private AppInfo resolveAppInfoByComponent(@NonNull ComponentKey key) {
-        AllAppsStore appsStore = ActivityContext.lookupContext(mContext)
+        AllAppsStore appsStore = ActivityContext.lookupContext(mContext.asContext())
                 .getAppsView().getAppsStore();
 
         // First look up the app info in order of:
@@ -326,7 +325,7 @@
         if (appInfo == null) {
             return null;
         }
-        return appInfo.makeWorkspaceItem(mContext);
+        return appInfo.makeWorkspaceItem(mContext.asContext());
     }
 
     /**
@@ -418,7 +417,7 @@
         } else {
             // Tapped an app pair while in a single app
             final TopTaskTracker.CachedTaskInfo runningTask = topTaskTracker
-                    .getCachedTopTask(false /* filterOnlyVisibleRecents */);
+                    .getCachedTopTask(false /* filterOnlyVisibleRecents */, context.getDisplayId());
 
             mSplitSelectStateController.findLastActiveTasksAndRunCallback(
                     componentKeys,
@@ -548,6 +547,6 @@
      */
     @VisibleForTesting
     public TopTaskTracker getTopTaskTracker() {
-        return TopTaskTracker.INSTANCE.get(mContext);
+        return TopTaskTracker.INSTANCE.get(mContext.asContext());
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
index 3bc9adc..e574cc7 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchInvoker.kt
@@ -87,7 +87,8 @@
             if (success) {
                 val runningPackage =
                     TopTaskTracker.INSTANCE[context].getCachedTopTask(
-                            /* filterOnlyVisibleRecents */ true
+                            /* filterOnlyVisibleRecents */ true,
+                            DEFAULT_DISPLAY,
                         )
                         .getPackageName()
                 statsLogManager
diff --git a/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
index a8d3c6d..136c496 100644
--- a/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
+++ b/quickstep/src/com/android/quickstep/util/ContextualSearchStateManager.java
@@ -18,6 +18,7 @@
 import static android.app.contextualsearch.ContextualSearchManager.ACTION_LAUNCH_CONTEXTUAL_SEARCH;
 import static android.app.contextualsearch.ContextualSearchManager.ENTRYPOINT_SYSTEM_ACTION;
 import static android.app.contextualsearch.ContextualSearchManager.FEATURE_CONTEXTUAL_SEARCH;
+import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
@@ -244,7 +245,8 @@
                                             ENTRYPOINT_SYSTEM_ACTION);
                             if (contextualSearchInvoked) {
                                 String runningPackage = mTopTaskTracker.getCachedTopTask(
-                                        /* filterOnlyVisibleRecents */ true).getPackageName();
+                                        /* filterOnlyVisibleRecents */ true,
+                                        DEFAULT_DISPLAY).getPackageName();
                                 StatsLogManager.newInstance(mContext).logger()
                                         .withPackageName(runningPackage)
                                         .log(LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_SYSTEM_ACTION);
diff --git a/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt b/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
index 455b312..0aaca31 100644
--- a/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
+++ b/quickstep/src/com/android/quickstep/util/ExternalDisplays.kt
@@ -16,6 +16,7 @@
 
 package com.android.quickstep.util
 
+import android.app.TaskInfo
 import android.view.Display.DEFAULT_DISPLAY
 import android.view.Display.INVALID_DISPLAY
 import com.android.systemui.shared.recents.model.Task
@@ -24,10 +25,9 @@
 val Int.isExternalDisplay
     get() = this != DEFAULT_DISPLAY
 
-/** Returns displayId of this [Task], default to [DEFAULT_DISPLAY] */
-val Task?.displayId
+val Int?.safeDisplayId
     get() =
-        this?.key?.displayId.let { displayId ->
+        this.let { displayId ->
             when (displayId) {
                 null -> DEFAULT_DISPLAY
                 INVALID_DISPLAY -> DEFAULT_DISPLAY
@@ -35,6 +35,14 @@
             }
         }
 
+/** Returns displayId of this [Task], default to [DEFAULT_DISPLAY] */
+val Task?.safeDisplayId
+    get() = this?.key?.displayId.safeDisplayId
+
 /** Returns if this task belongs tto [DEFAULT_DISPLAY] */
 val Task?.isExternalDisplay
-    get() = displayId.isExternalDisplay
+    get() = safeDisplayId.isExternalDisplay
+
+/** Returns displayId of this [TaskInfo], default to [DEFAULT_DISPLAY] */
+val TaskInfo?.safeDisplayId
+    get() = this?.displayId.safeDisplayId
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index d6e553d..96a5733 100644
--- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
+++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
@@ -192,7 +192,7 @@
         taskViewHeight: Int,
         isPrimaryTaskSplitting: Boolean,
     ) {
-        val taskContentView = taskContainer.taskContentView
+        val snapshot = taskContainer.snapshotView
         val iconView: View = taskContainer.iconView.asView()
         if (enableRefactorTaskThumbnail()) {
             builder.add(
@@ -241,11 +241,7 @@
             val centerThumbnailTranslationX: Float = (taskViewWidth - snapshotViewSize.x) / 2f
             val finalScaleX: Float = taskViewWidth.toFloat() / snapshotViewSize.x
             builder.add(
-                ObjectAnimator.ofFloat(
-                    taskContentView,
-                    View.TRANSLATION_X,
-                    centerThumbnailTranslationX,
-                )
+                ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_X, centerThumbnailTranslationX)
             )
             if (!enableOverviewIconMenu()) {
                 // icons are anchored from Gravity.END, so need to use negative translation
@@ -254,17 +250,15 @@
                     ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, -centerIconTranslationX)
                 )
             }
-            builder.add(ObjectAnimator.ofFloat(taskContentView, View.SCALE_X, finalScaleX))
+            builder.add(ObjectAnimator.ofFloat(snapshot, View.SCALE_X, finalScaleX))
 
             // Reset other dimensions
             // TODO(b/271468547), can't set Y translate to 0, need to account for top space
-            taskContentView.scaleY = 1f
+            snapshot.scaleY = 1f
             val translateYResetVal: Float =
                 if (!isPrimaryTaskSplitting) 0f
                 else deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
-            builder.add(
-                ObjectAnimator.ofFloat(taskContentView, View.TRANSLATION_Y, translateYResetVal)
-            )
+            builder.add(ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_Y, translateYResetVal))
         } else {
             val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
             // Center view first so scaling happens uniformly, alternatively we can move pivotY to 0
@@ -287,22 +281,18 @@
             }
             val finalScaleY: Float = thumbnailSize.toFloat() / snapshotViewSize.y
             builder.add(
-                ObjectAnimator.ofFloat(
-                    taskContentView,
-                    View.TRANSLATION_Y,
-                    centerThumbnailTranslationY,
-                )
+                ObjectAnimator.ofFloat(snapshot, 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(taskContentView, View.SCALE_Y, finalScaleY))
+            builder.add(ObjectAnimator.ofFloat(snapshot, View.SCALE_Y, finalScaleY))
 
             // Reset other dimensions
-            taskContentView.scaleX = 1f
-            builder.add(ObjectAnimator.ofFloat(taskContentView, View.TRANSLATION_X, 0f))
+            snapshot.scaleX = 1f
+            builder.add(ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_X, 0f))
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index fd8b356..08f2552 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -195,7 +195,7 @@
         mRecentTasksModel = recentsModel;
         mActivityBackCallback = activityBackCallback;
         mSplitAnimationController = new SplitAnimationController(this);
-        mAppPairsController = new AppPairsController(mContainer.asContext(), this, statsLogManager);
+        mAppPairsController = new AppPairsController(mContainer, this, statsLogManager);
         mSplitSelectDataHolder = new SplitSelectDataHolder(mContainer.asContext());
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index 8876633..8b12455 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -56,7 +56,6 @@
 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
@@ -78,14 +77,27 @@
 
     private val contentViewFullscreenParams = FullscreenDrawParams(context)
 
-    private val taskContentViewPool =
-        ViewPool<TaskContentView>(
-            context,
-            this,
-            R.layout.task_content_view,
-            VIEW_POOL_MAX_SIZE,
-            VIEW_POOL_INITIAL_SIZE,
-        )
+    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 tempPointF = PointF()
     private val lastComputedTaskSize = Rect()
@@ -243,7 +255,7 @@
             // 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 {
+                it.snapshotView.apply {
                     scaleX = 1.0f
                     scaleY = 1.0f
                     translationX = 0.0f
@@ -251,7 +263,7 @@
                 }
 
                 // Position the task to the same position as it would be on the desktop
-                it.taskContentView?.updateLayoutParams<LayoutParams> {
+                it.snapshotView.updateLayoutParams<LayoutParams> {
                     gravity = Gravity.LEFT or Gravity.TOP
                     width = taskWidth.toInt()
                     height = taskHeight.toInt()
@@ -262,7 +274,7 @@
                 if (
                     enableDesktopRecentsTransitionsCornersBugfix() && enableRefactorTaskThumbnail()
                 ) {
-                    it.taskContentView?.outlineBounds =
+                    it.thumbnailView.outlineBounds =
                         if (intersects(overviewTaskPosition, screenRect))
                             Rect(overviewTaskPosition).apply {
                                 intersectUnchecked(screenRect)
@@ -279,7 +291,7 @@
             } else {
                 // During the animation, apply translation and scale such that the view is
                 // transformed to where we want, without triggering layout.
-                it.taskContentView.apply {
+                it.snapshotView.apply {
                     pivotX = 0.0f
                     pivotY = 0.0f
                     translationX = taskLeft - left
@@ -313,19 +325,17 @@
         val backgroundViewIndex = contentView.indexOfChild(backgroundView)
         taskContainers =
             tasks.map { task ->
-                val taskContentView = taskContentViewPool.view
-                contentView.addView(taskContentView, backgroundViewIndex + 1)
                 val snapshotView =
                     if (enableRefactorTaskThumbnail()) {
-                        taskContentView.findViewById<TaskThumbnailView>(R.id.snapshot)
+                        taskThumbnailViewPool!!.view
                     } else {
-                        taskContentView.findViewById<TaskThumbnailViewDeprecated>(R.id.snapshot)
+                        taskThumbnailViewDeprecatedPool!!.view
                     }
+                contentView.addView(snapshotView, backgroundViewIndex + 1)
 
                 TaskContainer(
                     this,
                     task,
-                    taskContentView,
                     snapshotView,
                     iconView,
                     TransformingTouchDelegate(iconView.asView()),
@@ -478,8 +488,12 @@
     }
 
     private fun removeAndRecycleThumbnailView(taskContainer: TaskContainer) {
-        contentView.removeView(taskContainer.taskContentView)
-        taskContentViewPool.recycle(taskContainer.taskContentView)
+        contentView.removeView(taskContainer.snapshotView)
+        if (enableRefactorTaskThumbnail()) {
+            taskThumbnailViewPool!!.recycle(taskContainer.thumbnailView)
+        } else {
+            taskThumbnailViewDeprecatedPool!!.recycle(taskContainer.thumbnailViewDeprecated)
+        }
     }
 
     private fun updateTaskPositions() {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index 10a2e90..faa9e28 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -24,6 +24,7 @@
 import android.view.ViewStub
 import com.android.internal.jank.Cuj
 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.util.RunnableList
@@ -77,8 +78,8 @@
         val splitBoundsConfig = splitBoundsConfig ?: return
         val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID
         pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
-            leftTopTaskContainer.taskContentView,
-            rightBottomTaskContainer.taskContentView,
+            leftTopTaskContainer.snapshotView,
+            rightBottomTaskContainer.snapshotView,
             widthSize,
             heightSize,
             splitBoundsConfig,
@@ -94,8 +95,12 @@
 
     override fun inflateViewStubs() {
         super.inflateViewStubs()
-        findViewById<ViewStub>(R.id.bottomright_task_content_view)
-            ?.apply { layoutResource = R.layout.task_content_view }
+        findViewById<ViewStub>(R.id.bottomright_snapshot)
+            ?.apply {
+                layoutResource =
+                    if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail
+                    else R.layout.task_thumbnail_deprecated
+            }
             ?.inflate()
         findViewById<ViewStub>(R.id.bottomRight_icon)
             ?.apply {
@@ -123,7 +128,6 @@
             listOf(
                 createTaskContainer(
                     primaryTask,
-                    R.id.task_content_view,
                     R.id.snapshot,
                     R.id.icon,
                     R.id.show_windows,
@@ -133,8 +137,7 @@
                 ),
                 createTaskContainer(
                     secondaryTask,
-                    R.id.bottomright_task_content_view,
-                    R.id.snapshot,
+                    R.id.bottomright_snapshot,
                     R.id.bottomRight_icon,
                     R.id.show_windows_right,
                     R.id.bottomRight_digital_wellbeing_toast,
@@ -237,8 +240,8 @@
                 leftTopTaskContainer.iconView.asView(),
                 rightBottomTaskContainer.iconView.asView(),
                 taskIconHeight,
-                leftTopTaskContainer.taskContentView.measuredWidth,
-                leftTopTaskContainer.taskContentView.measuredHeight,
+                leftTopTaskContainer.snapshotView.measuredWidth,
+                leftTopTaskContainer.snapshotView.measuredHeight,
                 measuredHeight,
                 measuredWidth,
                 isLayoutRtl,
diff --git a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
index 46ed29b..f4fd127 100644
--- a/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
+++ b/quickstep/src/com/android/quickstep/views/IconAppChipView.kt
@@ -432,6 +432,11 @@
         }
     }
 
+    fun reset() {
+        setText(null)
+        setDrawable(null)
+    }
+
     override fun asView(): View = this
 
     enum class AppChipStatus {
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index afe7e92..0e769d0 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -22,6 +22,7 @@
 import android.view.View
 import android.view.View.OnClickListener
 import com.android.app.tracing.traceSection
+import com.android.launcher3.Flags.enableOverviewIconMenu
 import com.android.launcher3.Flags.enableRefactorTaskThumbnail
 import com.android.launcher3.model.data.TaskViewItemInfo
 import com.android.launcher3.util.SplitConfigurationOptions
@@ -31,7 +32,6 @@
 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.systemui.shared.recents.model.Task
 import com.android.systemui.shared.recents.model.ThumbnailData
@@ -40,7 +40,6 @@
 class TaskContainer(
     val taskView: TaskView,
     val task: Task,
-    val taskContentView: TaskContentView,
     val snapshotView: View,
     val iconView: TaskViewIcon,
     /**
@@ -112,8 +111,8 @@
     fun destroy() =
         traceSection("TaskContainer.destroy") {
             digitalWellBeingToast?.destroy()
-            taskContentView.scaleX = 1f
-            taskContentView.scaleY = 1f
+            snapshotView.scaleX = 1f
+            snapshotView.scaleY = 1f
             overlay.reset()
             if (enableRefactorTaskThumbnail()) {
                 isThumbnailValid = false
@@ -122,6 +121,10 @@
             } else {
                 thumbnailViewDeprecated.setShowSplashForSplitSelection(false)
             }
+
+            if (enableOverviewIconMenu()) {
+                (iconView as IconAppChipView).reset()
+            }
         }
 
     fun setOverlayEnabled(enabled: Boolean) {
@@ -174,9 +177,13 @@
         clickCloseListener: OnClickListener?,
     ) =
         traceSection("TaskContainer.setState") {
-            taskContentView.setState(
-                TaskUiStateMapper.toTaskHeaderState(state, hasHeader, clickCloseListener),
-                TaskUiStateMapper.toTaskThumbnailUiState(state, liveTile),
+            thumbnailView.setState(
+                TaskUiStateMapper.toTaskThumbnailUiState(
+                    state,
+                    liveTile,
+                    hasHeader,
+                    clickCloseListener,
+                ),
                 state?.taskId,
             )
             thumbnailData = if (state is TaskData.Data) state.thumbnailData else null
diff --git a/quickstep/src/com/android/quickstep/views/TaskHeaderView.kt b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt
similarity index 63%
rename from quickstep/src/com/android/quickstep/views/TaskHeaderView.kt
rename to quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt
index 1fda5a3..9a8805b 100644
--- a/quickstep/src/com/android/quickstep/views/TaskHeaderView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt
@@ -18,33 +18,23 @@
 
 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.constraintlayout.widget.ConstraintLayout
-import androidx.core.view.isGone
 import com.android.launcher3.R
-import com.android.quickstep.task.thumbnail.TaskHeaderUiState
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
 
-class TaskHeaderView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
-    ConstraintLayout(context, attrs) {
+class TaskThumbnailViewHeader
+@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
+    fun setHeader(header: ThumbnailHeader) {
+        headerTitleView.setText(header.title)
         headerIconView.setImageDrawable(header.icon)
         headerCloseButton.setOnClickListener(header.clickCloseListener)
     }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index b1561fa..fa3fd91 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -89,7 +89,6 @@
 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
@@ -97,8 +96,8 @@
 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.util.safeDisplayId
 import com.android.quickstep.views.IconAppChipView.AppChipStatus
 import com.android.quickstep.views.OverviewActionsView.DISABLED_NO_THUMBNAIL
 import com.android.quickstep.views.OverviewActionsView.DISABLED_ROTATED
@@ -155,7 +154,7 @@
         get() = this === recentsView?.selectedTaskView
 
     open val displayId: Int
-        get() = taskContainers.firstOrNull()?.task.displayId
+        get() = taskContainers.firstOrNull()?.task.safeDisplayId
 
     val isExternalDisplay: Boolean
         get() = displayId.isExternalDisplay
@@ -767,10 +766,13 @@
     }
 
     protected open fun inflateViewStubs() {
-        findViewById<ViewStub>(R.id.task_content_view)
-            ?.apply { layoutResource = R.layout.task_content_view }
+        findViewById<ViewStub>(R.id.snapshot)
+            ?.apply {
+                layoutResource =
+                    if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail
+                    else R.layout.task_thumbnail_deprecated
+            }
             ?.inflate()
-
         findViewById<ViewStub>(R.id.icon)
             ?.apply {
                 layoutResource =
@@ -917,7 +919,6 @@
             listOf(
                 createTaskContainer(
                     task,
-                    R.id.task_content_view,
                     R.id.snapshot,
                     R.id.icon,
                     R.id.show_windows,
@@ -952,9 +953,9 @@
             taskContainers.forEach { container ->
                 container.bind()
                 if (enableRefactorTaskThumbnail()) {
-                    container.taskContentView.cornerRadius =
+                    container.thumbnailView.cornerRadius =
                         thumbnailFullscreenParams.currentCornerRadius
-                    container.taskContentView.doOnSizeChange { width, height ->
+                    container.thumbnailView.doOnSizeChange { width, height ->
                         updateThumbnailValidity(container)
                         val thumbnailPosition = updateThumbnailMatrix(container, width, height)
                         container.refreshOverlay(thumbnailPosition)
@@ -981,7 +982,6 @@
 
     protected fun createTaskContainer(
         task: Task,
-        @IdRes taskContentViewId: Int,
         @IdRes thumbnailViewId: Int,
         @IdRes iconViewId: Int,
         @IdRes showWindowViewId: Int,
@@ -991,12 +991,10 @@
     ): TaskContainer =
         traceSection("TaskView.createTaskContainer") {
             val iconView = findViewById<View>(iconViewId) as TaskViewIcon
-            val taskContentView = findViewById<TaskContentView>(taskContentViewId)
             return TaskContainer(
                 this,
                 task,
-                taskContentView,
-                taskContentView.findViewById(thumbnailViewId),
+                findViewById(thumbnailViewId),
                 iconView,
                 TransformingTouchDelegate(iconView.asView()),
                 stagePosition,
@@ -1085,7 +1083,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
-        firstTaskContainer?.taskContentView?.updateLayoutParams<LayoutParams> {
+        firstTaskContainer?.snapshotView?.updateLayoutParams<LayoutParams> {
             topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
         }
         taskContainers.forEach { it.digitalWellBeingToast?.setupLayout() }
@@ -1099,11 +1097,11 @@
             val thumbnailBounds = Rect()
             if (relativeToDragLayer) {
                 container.dragLayer.getDescendantRectRelativeToSelf(
-                    it.taskContentView,
+                    it.snapshotView,
                     thumbnailBounds,
                 )
             } else {
-                thumbnailBounds.set(it.taskContentView)
+                thumbnailBounds.set(it.snapshotView)
             }
             bounds.union(thumbnailBounds)
         }
@@ -1817,7 +1815,7 @@
         updateFullscreenParams(thumbnailFullscreenParams)
         taskContainers.forEach {
             if (enableRefactorTaskThumbnail()) {
-                it.taskContentView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
+                it.thumbnailView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
             } else {
                 it.thumbnailViewDeprecated.setFullscreenParams(thumbnailFullscreenParams)
             }
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
deleted file mode 100644
index 8cc09d4..0000000
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/SplashHelper.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 7b1e445..0000000
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskContentViewScreenshotTest.kt
+++ /dev/null
@@ -1,124 +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.Context
-import android.graphics.Color
-import android.graphics.drawable.BitmapDrawable
-import android.platform.test.flag.junit.SetFlagsRule
-import android.view.LayoutInflater
-import com.android.launcher3.Flags
-import com.android.launcher3.R
-import com.android.launcher3.util.rule.setFlags
-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.Before
-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(order = 0) val setFlagsRule = SetFlagsRule()
-
-    @get:Rule(order = 1)
-    val screenshotRule =
-        ViewScreenshotTestRule(
-            emulationSpec,
-            ViewScreenshotGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)),
-        )
-
-    @Before
-    fun setUp() {
-        setFlagsRule.setFlags(true, Flags.FLAG_ENABLE_REFACTOR_TASK_THUMBNAIL)
-    }
-
-    @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
deleted file mode 100644
index e30554e..0000000
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskHeaderViewScreenshotTest.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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 45df735..80b2c16 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
@@ -16,14 +16,16 @@
 package com.android.quickstep.task.thumbnail
 
 import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Canvas
 import android.graphics.Color
 import android.graphics.Matrix
+import android.graphics.Paint
 import android.graphics.drawable.BitmapDrawable
 import android.view.LayoutInflater
 import android.view.Surface.ROTATION_0
+import androidx.core.graphics.set
 import com.android.launcher3.R
-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
@@ -88,25 +90,23 @@
     }
 
     @Test
-    fun taskThumbnailView_liveTile() {
+    fun taskThumbnailView_liveTile_withoutHeader() {
         screenshotRule.screenshotTest("taskThumbnailView_liveTile") { activity ->
             activity.actionBar?.hide()
-            createTaskThumbnailView(activity).apply { setState(TaskThumbnailUiState.LiveTile) }
+            createTaskThumbnailView(activity).apply {
+                setState(TaskThumbnailUiState.LiveTile.WithoutHeader)
+            }
         }
     }
 
     @Test
-    fun taskThumbnailView_image() {
+    fun taskThumbnailView_image_withoutHeader() {
         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,
-                        ),
+                        Snapshot.WithoutHeader(createBitmap(), ROTATION_0, Color.DKGRAY),
                         null,
                     )
                 )
@@ -115,14 +115,14 @@
     }
 
     @Test
-    fun taskThumbnailView_image_withImageMatrix() {
+    fun taskThumbnailView_image_withoutHeader_withImageMatrix() {
         screenshotRule.screenshotTest("taskThumbnailView_image_withMatrix") { activity ->
             activity.actionBar?.hide()
             createTaskThumbnailView(activity).apply {
                 val lessThanHeightMatchingAspectRatio = (VIEW_ENV_HEIGHT / 2) - 200
                 setState(
                     SnapshotSplash(
-                        Snapshot(
+                        Snapshot.WithoutHeader(
                             createBitmap(
                                 width = VIEW_ENV_WIDTH / 2,
                                 height = lessThanHeightMatchingAspectRatio,
@@ -139,17 +139,13 @@
     }
 
     @Test
-    fun taskThumbnailView_splash() {
+    fun taskThumbnailView_splash_withoutHeader() {
         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,
-                        ),
+                        Snapshot.WithoutHeader(createBitmap(), ROTATION_0, Color.DKGRAY),
                         BitmapDrawable(activity.resources, createSplash()),
                     )
                 )
@@ -159,14 +155,14 @@
     }
 
     @Test
-    fun taskThumbnailView_splash_withImageMatrix() {
+    fun taskThumbnailView_splash_withoutHeader_withImageMatrix() {
         screenshotRule.screenshotTest("taskThumbnailView_partial_splash_withMatrix") { activity ->
             activity.actionBar?.hide()
             createTaskThumbnailView(activity).apply {
                 val lessThanHeightMatchingAspectRatio = (VIEW_ENV_HEIGHT / 2) - 200
                 setState(
                     SnapshotSplash(
-                        Snapshot(
+                        Snapshot.WithoutHeader(
                             createBitmap(
                                 width = VIEW_ENV_WIDTH / 2,
                                 height = lessThanHeightMatchingAspectRatio,
@@ -233,9 +229,31 @@
         val taskThumbnailView =
             LayoutInflater.from(context).inflate(R.layout.task_thumbnail, null, false)
                 as TaskThumbnailView
+        taskThumbnailView.cornerRadius = CORNER_RADIUS
         return taskThumbnailView
     }
 
+    private fun createSplash() = createBitmap(width = 20, height = 20, rectColorRotation = 1)
+
+    private fun createBitmap(
+        width: Int = VIEW_ENV_WIDTH,
+        height: Int = VIEW_ENV_HEIGHT,
+        rectColorRotation: Int = 0,
+    ) =
+        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)
+            }
+        }
+
     companion object {
         @Parameters(name = "{0}")
         @JvmStatic
@@ -246,6 +264,8 @@
                 isLandscape = false,
             )
 
+        const val CORNER_RADIUS = 56f
+        val BITMAP_RECT_COLORS = listOf(Color.GREEN, Color.RED, Color.BLUE, Color.CYAN)
         const val VIEW_ENV_WIDTH = 1440
         const val VIEW_ENV_HEIGHT = 3120
     }
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 d2abed8..42adfec 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
@@ -35,7 +35,6 @@
 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
@@ -199,7 +198,6 @@
         return TaskContainer(
             taskView,
             task,
-            mock<TaskContentView>(),
             if (enableRefactorTaskThumbnail()) mock<TaskThumbnailView>()
             else mock<TaskThumbnailViewDeprecated>(),
             mock<TaskViewIcon>(),
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 cfeade8..ee9505c 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumerTest.java
@@ -36,6 +36,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -105,11 +106,12 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        when(mTopTaskTracker.getCachedTopTask(anyBoolean())).thenReturn(mTaskInfo);
+        when(mTopTaskTracker.getCachedTopTask(anyBoolean(), anyInt())).thenReturn(mTaskInfo);
         when(mDeviceState.getSquaredTouchSlop()).thenReturn(SQUARED_TOUCH_SLOP);
         when(mDelegate.allowInterceptByParent()).thenReturn(true);
         mLongPressTriggered.set(false);
-        when(mNavHandleLongPressHandler.getLongPressRunnable(any())).thenReturn(mLongPressRunnable);
+        when(mNavHandleLongPressHandler.getLongPressRunnable(any(), anyInt())).thenReturn(
+                mLongPressRunnable);
         when(mStatsLogger.withPackageName(any())).thenReturn(mStatsLogger);
         when(mStatsLatencyLogger.withInstanceId(any())).thenReturn(mStatsLatencyLogger);
         when(mStatsLatencyLogger.withLatency(anyLong())).thenReturn(mStatsLatencyLogger);
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
index 6c0d0ed..7ca194a 100644
--- 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
@@ -19,120 +19,33 @@
 import android.graphics.Bitmap
 import android.graphics.Color
 import android.graphics.drawable.ShapeDrawable
-import android.platform.test.annotations.DisableFlags
 import android.platform.test.annotations.EnableFlags
-import android.platform.test.flag.junit.SetFlagsRule
 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.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
 import com.android.systemui.shared.recents.model.ThumbnailData
 import com.google.common.truth.Truth.assertThat
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
 class TaskUiStateMapperTest {
 
-    @get:Rule val mSetFlagsRule = SetFlagsRule()
-
-    /** TaskHeaderUiState */
     @Test
-    fun taskData_isNull_returns_HideHeader() {
+    fun taskData_isNull_returns_Uninitialized() {
         val result =
-            TaskUiStateMapper.toTaskHeaderState(
+            TaskUiStateMapper.toTaskThumbnailUiState(
                 taskData = null,
+                isLiveTile = false,
                 hasHeader = false,
                 clickCloseListener = null,
             )
-        assertThat(result).isEqualTo(TaskHeaderUiState.HideHeader)
-    }
-
-    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
-    @Test
-    fun explodedFlagDisabled_returnsHideHeader() {
-        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.HideHeader
-        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_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)
     }
 
@@ -142,20 +55,79 @@
             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)
+                TaskUiStateMapper.toTaskThumbnailUiState(
+                    taskData = input,
+                    isLiveTile = true,
+                    hasHeader = false,
+                    clickCloseListener = null,
+                )
+            assertThat(result).isEqualTo(LiveTile.WithoutHeader)
+        }
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+    @Test
+    fun taskData_isLiveTileWithHeader_returns_LiveTileWithHeader() {
+        val inputs =
+            listOf(
+                TASK_DATA,
+                TASK_DATA.copy(thumbnailData = null),
+                TASK_DATA.copy(isLocked = true),
+                TASK_DATA.copy(title = null),
+            )
+        val closeCallback = View.OnClickListener {}
+        val expected =
+            LiveTile.WithHeader(
+                header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION, closeCallback)
+            )
+        inputs.forEach { taskData ->
+            val result =
+                TaskUiStateMapper.toTaskThumbnailUiState(
+                    taskData = taskData,
+                    isLiveTile = true,
+                    hasHeader = true,
+                    clickCloseListener = closeCallback,
+                )
+            assertThat(result).isEqualTo(expected)
+        }
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+    @Test
+    fun taskData_isLiveTileWithHeader_missingHeaderData_returns_LiveTileWithoutHeader() {
+        val inputs =
+            listOf(
+                TASK_DATA.copy(icon = null),
+                TASK_DATA.copy(titleDescription = null),
+                TASK_DATA.copy(icon = null, titleDescription = null),
+            )
+
+        inputs.forEach { taskData ->
+            val result =
+                TaskUiStateMapper.toTaskThumbnailUiState(
+                    taskData = taskData,
+                    isLiveTile = true,
+                    hasHeader = true,
+                    clickCloseListener = {},
+                )
+            assertThat(result).isEqualTo(LiveTile.WithoutHeader)
         }
     }
 
     @Test
     fun taskData_isStaticTile_returns_SnapshotSplash() {
         val result =
-            TaskUiStateMapper.toTaskThumbnailUiState(taskData = TASK_DATA, isLiveTile = false)
+            TaskUiStateMapper.toTaskThumbnailUiState(
+                taskData = TASK_DATA,
+                isLiveTile = false,
+                hasHeader = false,
+                clickCloseListener = null,
+            )
 
         val expected =
             TaskThumbnailUiState.SnapshotSplash(
                 snapshot =
-                    Snapshot(
+                    Snapshot.WithoutHeader(
                         backgroundColor = TASK_BACKGROUND_COLOR,
                         bitmap = TASK_THUMBNAIL,
                         thumbnailRotation = Surface.ROTATION_0,
@@ -166,12 +138,72 @@
         assertThat(result).isEqualTo(expected)
     }
 
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+    @Test
+    fun taskData_isStaticTile_withHeader_returns_SnapshotSplashWithHeader() {
+        val inputs = listOf(TASK_DATA, TASK_DATA.copy(title = null))
+        val closeCallback = View.OnClickListener {}
+        val expected =
+            TaskThumbnailUiState.SnapshotSplash(
+                snapshot =
+                    Snapshot.WithHeader(
+                        backgroundColor = TASK_BACKGROUND_COLOR,
+                        bitmap = TASK_THUMBNAIL,
+                        thumbnailRotation = Surface.ROTATION_0,
+                        header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION, closeCallback),
+                    ),
+                splash = TASK_ICON,
+            )
+        inputs.forEach { taskData ->
+            val result =
+                TaskUiStateMapper.toTaskThumbnailUiState(
+                    taskData = taskData,
+                    isLiveTile = false,
+                    hasHeader = true,
+                    clickCloseListener = closeCallback,
+                )
+            assertThat(result).isEqualTo(expected)
+        }
+    }
+
+    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
+    @Test
+    fun taskData_isStaticTile_missingHeaderData_returns_SnapshotSplashWithoutHeader() {
+        val inputs =
+            listOf(
+                TASK_DATA.copy(titleDescription = null, icon = null),
+                TASK_DATA.copy(titleDescription = null),
+                TASK_DATA.copy(icon = null),
+            )
+        val expected =
+            Snapshot.WithoutHeader(
+                backgroundColor = TASK_BACKGROUND_COLOR,
+                thumbnailRotation = Surface.ROTATION_0,
+                bitmap = TASK_THUMBNAIL,
+            )
+        inputs.forEach { taskData ->
+            val result =
+                TaskUiStateMapper.toTaskThumbnailUiState(
+                    taskData = taskData,
+                    isLiveTile = false,
+                    hasHeader = true,
+                    clickCloseListener = {},
+                )
+
+            assertThat(result).isInstanceOf(TaskThumbnailUiState.SnapshotSplash::class.java)
+            result as TaskThumbnailUiState.SnapshotSplash
+            assertThat(result.snapshot).isEqualTo(expected)
+        }
+    }
+
     @Test
     fun taskData_thumbnailIsNull_returns_BackgroundOnly() {
         val result =
             TaskUiStateMapper.toTaskThumbnailUiState(
                 taskData = TASK_DATA.copy(thumbnailData = null),
                 isLiveTile = false,
+                hasHeader = false,
+                clickCloseListener = null,
             )
 
         val expected = TaskThumbnailUiState.BackgroundOnly(TASK_BACKGROUND_COLOR)
@@ -184,6 +216,8 @@
             TaskUiStateMapper.toTaskThumbnailUiState(
                 taskData = TASK_DATA.copy(isLocked = true),
                 isLiveTile = false,
+                hasHeader = false,
+                clickCloseListener = null,
             )
 
         val expected = TaskThumbnailUiState.BackgroundOnly(TASK_BACKGROUND_COLOR)
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 76d36d3..c325af4 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/util/AppPairsControllerTest.kt
@@ -17,8 +17,9 @@
 package com.android.quickstep.util
 
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
-import android.content.Context
 import android.content.res.Resources
+import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.launcher3.apppairs.AppPairIcon
 import com.android.launcher3.logging.StatsLogManager
@@ -26,6 +27,7 @@
 import com.android.launcher3.taskbar.TaskbarActivityContext
 import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
 import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
+import com.android.launcher3.views.ActivityContext
 import com.android.quickstep.TopTaskTracker
 import com.android.quickstep.TopTaskTracker.CachedTaskInfo
 import com.android.systemui.shared.recents.model.Task
@@ -56,7 +58,7 @@
 
 @RunWith(AndroidJUnit4::class)
 class AppPairsControllerTest {
-    @Mock lateinit var context: Context
+    @Mock lateinit var context: ActivityContext
     @Mock lateinit var resources: Resources
     @Mock lateinit var splitSelectStateController: SplitSelectStateController
     @Mock lateinit var statsLogManager: StatsLogManager
@@ -83,6 +85,7 @@
     }
 
     @Mock lateinit var mockAppPairIcon: AppPairIcon
+    @Mock lateinit var mockDisplay: Display
     @Mock lateinit var mockTaskbarActivityContext: TaskbarActivityContext
     @Mock lateinit var mockTopTaskTracker: TopTaskTracker
     @Mock lateinit var mockCachedTaskInfo: CachedTaskInfo
@@ -105,8 +108,10 @@
         // Stub methods on appPairsController so that they return mocks
         spyAppPairsController = spy(appPairsController)
         whenever(mockAppPairIcon.context).thenReturn(mockTaskbarActivityContext)
+        whenever(mockAppPairIcon.display).thenReturn(mockDisplay)
+        whenever(mockDisplay.displayId).thenReturn(DEFAULT_DISPLAY)
         doReturn(mockTopTaskTracker).whenever(spyAppPairsController).topTaskTracker
-        whenever(mockTopTaskTracker.getCachedTopTask(any())).thenReturn(mockCachedTaskInfo)
+        whenever(mockTopTaskTracker.getCachedTopTask(any(), any())).thenReturn(mockCachedTaskInfo)
         whenever(mockTask1.getKey()).thenReturn(mockTaskKey1)
         whenever(mockTask2.getKey()).thenReturn(mockTaskKey2)
         doNothing().whenever(spyAppPairsController).launchAppPair(any(), any())
diff --git a/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt b/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
index 61a6975..8f26795 100644
--- a/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
+++ b/quickstep/tests/src/com/android/launcher3/taskbar/bubbles/animation/BubbleBarViewAnimatorTest.kt
@@ -316,43 +316,6 @@
     }
 
     @Test
-    fun animateBubbleInForStashed_showAnimationCanceled() {
-        setUpBubbleBar()
-
-        val handle = View(context)
-        val handleAnimator = PhysicsAnimator.getInstance(handle)
-        bubbleStashController.handleAnimator = handleAnimator
-
-        val animator =
-            BubbleBarViewAnimator(
-                bubbleBarView,
-                bubbleStashController,
-                flyoutController,
-                bubbleBarParentViewController,
-                onExpanded = emptyRunnable,
-                onBubbleBarVisible = emptyRunnable,
-                animatorScheduler,
-            )
-
-        InstrumentationRegistry.getInstrumentation().runOnMainSync {
-            animator.animateBubbleInForStashed(bubble, isExpanding = false)
-        }
-
-        // wait for the animation to start
-        InstrumentationRegistry.getInstrumentation().runOnMainSync {}
-        PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(handleAnimator) { true }
-
-        handleAnimator.assertIsRunning()
-        assertThat(animator.isAnimating).isTrue()
-        assertThat(animatorScheduler.delayedBlock).isNotNull()
-
-        handleAnimator.cancel()
-        handleAnimator.assertIsNotRunning()
-        assertThat(animator.isAnimating).isFalse()
-        assertThat(animatorScheduler.delayedBlock).isNull()
-    }
-
-    @Test
     fun animateBubbleInForStashed_autoExpanding() {
         setUpBubbleBar()
 
diff --git a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index 746f8bb..5f61ba2 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -37,7 +37,6 @@
 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
@@ -254,7 +253,6 @@
         TaskContainer(
             taskView,
             task,
-            mock<TaskContentView>(),
             if (enableRefactorTaskThumbnail()) mock<TaskThumbnailView>()
             else mock<TaskThumbnailViewDeprecated>(),
             mock<TaskViewIcon>(),
diff --git a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
index 5aaed7d..2db94f6 100644
--- a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
@@ -37,7 +37,6 @@
 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
@@ -247,7 +246,6 @@
         TaskContainer(
             taskView,
             task,
-            mock<TaskContentView>(),
             if (enableRefactorTaskThumbnail()) mock<TaskThumbnailView>()
             else mock<TaskThumbnailViewDeprecated>(),
             mock<TaskViewIcon>(),
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 090208a..e17f5b2 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -1950,13 +1950,18 @@
             int remainingSpaceOnSide = (availableWidthPxForHotseat - hotseatPlusQSBWidth) / 2;
 
             hotseatBarPadding.set(
-                    (remainingSpaceOnSide + qsbWidth) + mInsets.left + workspacePadding.left
+                    remainingSpaceOnSide + mInsets.left + workspacePadding.left
                             + cellLayoutPaddingPx.left,
                     hotseatBarSizePx - hotseatBarBottomPadding - hotseatCellHeightPx,
                     remainingSpaceOnSide + mInsets.right + workspacePadding.right
                             + cellLayoutPaddingPx.right,
                     hotseatBarBottomPadding
             );
+            if (Utilities.isRtl(context.getResources())) {
+                hotseatBarPadding.right += qsbWidth;
+            } else {
+                hotseatBarPadding.left += qsbWidth;
+            }
         } else if (isTaskbarPresent) {
             // Center the QSB vertically with hotseat
             int hotseatBarBottomPadding = getHotseatBarBottomPadding();
@@ -2077,7 +2082,7 @@
      * Returns the number of pixels the hotseat is translated from the bottom of the screen.
      */
     private int getHotseatBarBottomPadding() {
-        if (isTaskbarPresent) { // QSB on top or inline
+        if (isTaskbarPresent || isQsbInline) { // QSB on top or inline
             return hotseatBarBottomSpacePx - (Math.abs(hotseatCellHeightPx - iconSizePx) / 2);
         } else {
             return hotseatBarSizePx - hotseatCellHeightPx;
diff --git a/src/com/android/launcher3/ModelCallbacks.kt b/src/com/android/launcher3/ModelCallbacks.kt
index 32b47d0..f38dc41 100644
--- a/src/com/android/launcher3/ModelCallbacks.kt
+++ b/src/com/android/launcher3/ModelCallbacks.kt
@@ -95,7 +95,7 @@
         synchronouslyBoundPages = boundPages
         pagesToBindSynchronously = LIntSet()
         clearPendingBinds()
-        if (!launcher.isInState(LauncherState.ALL_APPS)) {
+        if (!launcher.isInState(LauncherState.ALL_APPS) && !Flags.enableWorkspaceInflation()) {
             launcher.appsView.appsStore.enableDeferUpdates(AllAppsStore.DEFER_UPDATES_NEXT_DRAW)
             pendingTasks.add {
                 launcher.appsView.appsStore.disableDeferUpdates(
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index f60896e..3d8ebbc 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -575,6 +575,7 @@
     }
 
     protected void rebindAdapters(boolean force) {
+        Log.d(TAG, "rebindAdapters: force: " + force);
         if (mSearchTransitionController.isRunning()) {
             mRebindAdaptersAfterSearchAnimation = true;
             return;
@@ -583,6 +584,7 @@
 
         boolean showTabs = shouldShowTabs();
         if (showTabs == mUsingTabs && !force) {
+            Log.d(TAG, "rebindAdapters: Not needed.");
             return;
         }
 
@@ -678,6 +680,7 @@
     }
 
     private void replaceAppsRVContainer(boolean showTabs) {
+        Log.d(TAG, "replaceAppsRVContainer: showTabs: " + showTabs);
         for (int i = AdapterHolder.MAIN; i <= AdapterHolder.WORK; i++) {
             AdapterHolder adapterHolder = mAH.get(i);
             if (adapterHolder.mRecyclerView != null) {
@@ -711,7 +714,6 @@
 
             mWorkManager.reset();
             post(() -> mAH.get(AdapterHolder.WORK).applyPadding());
-
         } else {
             mWorkManager.detachWorkUtilityViews();
             mViewPager = null;
@@ -1017,6 +1019,7 @@
 
     @VisibleForTesting
     public void onAppsUpdated() {
+        Log.d(TAG, "onAppsUpdated; number of apps: " + mAllAppsStore.getApps().length);
         mHasWorkApps = Stream.of(mAllAppsStore.getApps())
                 .anyMatch(mWorkManager.getItemInfoMatcher());
         mHasPrivateApps = Stream.of(mAllAppsStore.getApps())
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index d5a4022..821027e 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.os.UserHandle;
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -53,6 +54,7 @@
  */
 public class AllAppsStore<T extends Context & ActivityContext> {
 
+    private static final String TAG = "AllAppsStore";
     // Defer updates flag used to defer all apps updates to the next draw.
     public static final int DEFER_UPDATES_NEXT_DRAW = 1 << 0;
     // Defer updates flag used to defer all apps updates by a test's request.
@@ -102,6 +104,7 @@
     public void setApps(@Nullable AppInfo[] apps, int flags, Map<PackageUserKey, Integer> map,
             boolean shouldPreinflate) {
         mApps = apps == null ? EMPTY_ARRAY : apps;
+        Log.d(TAG, "setApps: apps.length=" + mApps.length);
         mModelFlags = flags;
         notifyUpdate();
         mPackageUserKeytoUidMap = map;
@@ -159,10 +162,12 @@
 
     public void enableDeferUpdates(int flag) {
         mDeferUpdatesFlags |= flag;
+        Log.d(TAG, "enableDeferUpdates: " + flag + " mDeferUpdatesFlags=" + mDeferUpdatesFlags);
     }
 
     public void disableDeferUpdates(int flag) {
         mDeferUpdatesFlags &= ~flag;
+        Log.d(TAG, "disableDeferUpdates: " + flag + " mDeferUpdatesFlags=" + mDeferUpdatesFlags);
         if (mDeferUpdatesFlags == 0 && mUpdatePending) {
             notifyUpdate();
             mUpdatePending = false;
@@ -171,6 +176,9 @@
 
     public void disableDeferUpdatesSilently(int flag) {
         mDeferUpdatesFlags &= ~flag;
+        Log.d(TAG, "disableDeferUpdatesSilently: " + flag
+                + " mDeferUpdatesFlags=" + mDeferUpdatesFlags);
+
     }
 
     public int getDeferUpdatesFlags() {
@@ -179,9 +187,11 @@
 
     private void notifyUpdate() {
         if (mDeferUpdatesFlags != 0) {
+            Log.d(TAG, "notifyUpdate: deferring update");
             mUpdatePending = true;
             return;
         }
+        Log.d(TAG, "notifyUpdate: notifying listeners");
         for (OnUpdateListener listener : mUpdateListeners) {
             listener.onAppsUpdated();
         }
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 929e52e..813ed3e 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -38,7 +38,6 @@
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.folder.PreviewBackground;
 import com.android.launcher3.icons.BitmapRenderer;
 import com.android.launcher3.util.Preconditions;
 import com.android.launcher3.views.ActivityContext;
@@ -143,7 +142,6 @@
         icon.getPreviewBounds(sTmpRect);
         final int previewSize = sTmpRect.width();
 
-        PreviewBackground bg = icon.getFolderBackground();
         final int margin = (size - previewSize) / 2;
         final float previewShiftX = -sTmpRect.left + margin;
         final float previewShiftY = -sTmpRect.top + margin;
@@ -162,11 +160,10 @@
         foregroundCanvas.restore();
 
         // Draw background
-        Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-        backgroundPaint.setColor(bg.getBgColor());
-        bg.drawShadow(backgroundCanvas);
-        backgroundCanvas.drawPaint(backgroundPaint);
-        bg.drawBackgroundStroke(backgroundCanvas);
+        backgroundCanvas.save();
+        backgroundCanvas.translate(previewShiftX, previewShiftY);
+        icon.getFolderBackground().drawBackground(backgroundCanvas);
+        backgroundCanvas.restore();
     }
 
     @Override
diff --git a/src/com/android/launcher3/model/BaseLauncherBinder.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
index c4bbae4..1d19a17 100644
--- a/src/com/android/launcher3/model/BaseLauncherBinder.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -323,8 +323,10 @@
             Executor pendingExecutor = pendingTasks::add;
 
             RunnableList onCompleteSignal = new RunnableList();
+            onCompleteSignal.add(() -> Log.d(TAG, "Calling onCompleteSignal"));
 
             if (enableWorkspaceInflation() && inflater != null) {
+                Log.d(TAG, "Starting async inflation");
                 MODEL_EXECUTOR.execute(() ->  {
                     inflateAsyncAndBind(otherWorkspaceItems, inflater, pendingExecutor);
                     inflateAsyncAndBind(otherAppWidgets, inflater, pendingExecutor);
@@ -335,6 +337,7 @@
                     MAIN_EXECUTOR.execute(onCompleteSignal::executeAllAndDestroy);
                 });
             } else {
+                Log.d(TAG, "Starting sync inflation");
                 bindItemsInChunks(otherWorkspaceItems, ITEMS_CHUNK, pendingExecutor);
                 bindItemsInChunks(otherAppWidgets, 1, pendingExecutor);
                 setupPendingBind(currentScreenIds, pendingExecutor);
diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
index 26bfd36..dad7629 100644
--- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java
+++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.util;
 
+import android.util.Log;
 import android.view.View;
 import android.view.View.OnAttachStateChangeListener;
 import android.view.ViewTreeObserver.OnDrawListener;
@@ -32,6 +33,7 @@
 public class ViewOnDrawExecutor implements OnDrawListener, Runnable,
         OnAttachStateChangeListener {
 
+    private static final String TAG = "ViewOnDrawExecutor";
     private final RunnableList mTasks;
     private final Consumer<ViewOnDrawExecutor> mOnClearCallback;
     private View mAttachedView;
@@ -88,7 +90,10 @@
      * Executes all tasks immediately
      */
     public void markCompleted() {
-        if (!mCancelled) {
+        if (mCancelled) {
+            Log.d(TAG, "markCompleted ignored: cancelled");
+        } else {
+            Log.d(TAG, "markCompleted: executing tasks");
             mTasks.executeAllAndDestroy();
         }
         mCompleted = true;
@@ -101,6 +106,7 @@
     }
 
     public void cancel() {
+        Log.d(TAG, "Cancelling tasks");
         mCancelled = true;
         markCompleted();
     }
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 02c6630..8fbb5e3 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -73,18 +73,16 @@
             return getCombinedSplitTaskHeight();
         }
 
-        if (isDesktop()) {
-            return getTaskSnapshot(DESKTOP).getVisibleBounds().height();
-        }
-        return getTaskSnapshot(DEFAULT).getVisibleBounds().height();
+        UiObject2 taskSnapshot1 = findObjectInTask((isDesktop() ? DESKTOP : DEFAULT).snapshotRes);
+        return taskSnapshot1.getVisibleBounds().height();
     }
 
     /**
      * Calculates the visible height for split tasks, containing 2 snapshot tiles and a divider.
      */
     private int getCombinedSplitTaskHeight() {
-        UiObject2 taskSnapshot1 = getTaskSnapshot(SPLIT_TOP_OR_LEFT);
-        UiObject2 taskSnapshot2 = getTaskSnapshot(SPLIT_BOTTOM_OR_RIGHT);
+        UiObject2 taskSnapshot1 = findObjectInTask(SPLIT_TOP_OR_LEFT.snapshotRes);
+        UiObject2 taskSnapshot2 = findObjectInTask(SPLIT_BOTTOM_OR_RIGHT.snapshotRes);
 
         // If the split task is partly off screen, taskSnapshot1 can be invisible.
         if (taskSnapshot1 == null) {
@@ -99,6 +97,34 @@
         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;
+    }
+
     public int getTaskCenterX() {
         return mTask.getVisibleCenter().x;
     }
@@ -116,22 +142,6 @@
     }
 
     /**
-     * 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() {
@@ -294,13 +304,17 @@
         }
     }
 
+    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.
      */
     public boolean containsContentDescription(String expected,
             OverviewTaskContainer overviewTaskContainer) {
-        String actual = getTaskSnapshot(overviewTaskContainer).getContentDescription();
+        String actual = findObjectInTask(overviewTaskContainer.snapshotRes).getContentDescription();
         if (actual == null && expected == null) {
             return true;
         }
@@ -346,19 +360,19 @@
      */
     public enum OverviewTaskContainer {
         // The main task when the task is not split.
-        DEFAULT("task_content_view", "icon"),
+        DEFAULT("snapshot", "icon"),
         // The first task in split task.
-        SPLIT_TOP_OR_LEFT("task_content_view", "icon"),
+        SPLIT_TOP_OR_LEFT("snapshot", "icon"),
         // The second task in split task.
-        SPLIT_BOTTOM_OR_RIGHT("bottomright_task_content_view", "bottomRight_icon"),
+        SPLIT_BOTTOM_OR_RIGHT("bottomright_snapshot", "bottomRight_icon"),
         // The desktop task.
         DESKTOP("background", "icon");
 
-        public final String taskContentViewRes;
+        public final String snapshotRes;
         public final String iconAppRes;
 
-        OverviewTaskContainer(String taskContentViewRes, String iconAppRes) {
-            this.taskContentViewRes = taskContentViewRes;
+        OverviewTaskContainer(String snapshotRes, String iconAppRes) {
+            this.snapshotRes = snapshotRes;
             this.iconAppRes = iconAppRes;
         }
     }