Merge "Fix Taskbar not present in Desktop Mode after unlocking" into main
diff --git a/quickstep/res/layout/task.xml b/quickstep/res/layout/task.xml
index 3aac1b6..4abfbbe 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/snapshot"
-        android:inflatedId="@id/snapshot"
+        android:id="@+id/task_content_view"
+        android:inflatedId="@id/task_content_view"
         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
new file mode 100644
index 0000000..9055ccd
--- /dev/null
+++ b/quickstep/res/layout/task_content_view.xml
@@ -0,0 +1,20 @@
+<?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 3e6f5ed..a7c4856 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/snapshot"
-        android:inflatedId="@id/snapshot"
+        android:id="@+id/task_content_view"
+        android:inflatedId="@id/task_content_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
     <ViewStub
-        android:id="@+id/bottomright_snapshot"
-        android:inflatedId="@id/bottomright_snapshot"
+        android:id="@+id/bottomright_task_content_view"
+        android:inflatedId="@id/bottomright_task_content_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
diff --git a/quickstep/res/layout/task_thumbnail_view_header.xml b/quickstep/res/layout/task_header_view.xml
similarity index 78%
rename from quickstep/res/layout/task_thumbnail_view_header.xml
rename to quickstep/res/layout/task_header_view.xml
index 70e4a42..ea5c24e 100644
--- a/quickstep/res/layout/task_thumbnail_view_header.xml
+++ b/quickstep/res/layout/task_header_view.xml
@@ -13,12 +13,11 @@
      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"
+<com.android.quickstep.views.TaskHeaderView xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:id="@+id/task_thumbnail_view_header"
+    android:id="@+id/task_header_view"
     android:background="@drawable/task_thumbnail_header_bg">
 
     <androidx.constraintlayout.widget.ConstraintLayout
@@ -38,22 +37,17 @@
             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" />
+            app:layout_constraintBottom_toBottomOf="parent" />
         <TextView
             android:id="@+id/header_app_title"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginStart="@dimen/task_thumbnail_header_margin_between_views"
-            android:layout_marginEnd="@dimen/task_thumbnail_header_margin_between_views"
+            android:maxLines="1"
             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" />
+            app:layout_constraintStart_toEndOf="@id/header_app_icon"
+            app:layout_constraintTop_toTopOf="parent" />
         <ImageButton
             android:id="@+id/header_close_button"
             android:contentDescription="@string/header_close_icon_description"
@@ -66,7 +60,7 @@
             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" />
+            app:layout_constraintStart_toEndOf="@id/header_app_title"
+            app:layout_constraintHorizontal_bias="1" />
     </androidx.constraintlayout.widget.ConstraintLayout>
-</com.android.quickstep.views.TaskThumbnailViewHeader>
+</com.android.quickstep.views.TaskHeaderView>
diff --git a/quickstep/res/layout/task_thumbnail.xml b/quickstep/res/layout/task_thumbnail.xml
index 3b96615..8280e13 100644
--- a/quickstep/res/layout/task_thumbnail.xml
+++ b/quickstep/res/layout/task_thumbnail.xml
@@ -17,7 +17,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/snapshot"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
+    android:layout_height="0dp"
+    android:layout_weight="1" >
 
     <com.android.quickstep.views.FixedSizeImageView
         android:id="@+id/task_thumbnail"
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
index 688018b..1438edf 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchAnimatorHelper.kt
@@ -64,18 +64,13 @@
 
     fun createAnimators(info: TransitionInfo, finishCallback: (Animator) -> Unit): List<Animator> {
         val launchChange = getLaunchChange(info)
-        requireNotNull(launchChange) {
-            val changesString =
+        if (launchChange == null) {
+            val tasksInfo =
                 info.changes.joinToString(", ") { change ->
-                    "Change: mode=${change.mode}, " +
-                        "taskId=${change.taskInfo?.id}, " +
-                        "isFreeform=${change.taskInfo?.isFreeform}"
+                    "${change.taskInfo?.taskId}:${change.taskInfo?.isFreeform}"
                 }
-            Log.e(
-                TAG,
-                "No launch change found: Transition type=${info.type}, changes=$changesString",
-            )
-            "expected an app launch Change"
+            Log.e(TAG, "No launch change found: Transition info=$info, tasks state=$tasksInfo")
+            return emptyList()
         }
 
         val transaction = transactionSupplier.get()
@@ -105,10 +100,14 @@
     }
 
     private fun getLaunchChange(info: TransitionInfo): Change? =
-        info.changes.firstOrNull { change -> change.mode in LAUNCH_CHANGE_MODES }
+        info.changes.firstOrNull { change ->
+            change.mode in LAUNCH_CHANGE_MODES && change.taskInfo?.isFreeform == true
+        }
 
     private fun getMinimizeChange(info: TransitionInfo): Change? =
-        info.changes.firstOrNull { change -> change.mode == TRANSIT_TO_BACK }
+        info.changes.firstOrNull { change ->
+            change.mode == TRANSIT_TO_BACK && change.taskInfo?.isFreeform == true
+        }
 
     private fun getTrampolineCloseChange(info: TransitionInfo): Change? {
         if (
diff --git a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
index 79072a6..5a8934b 100644
--- a/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
+++ b/quickstep/src/com/android/launcher3/desktop/DesktopAppLaunchTransition.kt
@@ -84,6 +84,10 @@
             if (animators.isEmpty()) finishedCallback.run()
         }
         animators += animatorHelper.createAnimators(info, animatorFinishedCallback)
+        if (animators.isEmpty()) {
+            finishedCallback.run()
+            return
+        }
         animators.forEach { it.start() }
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index fc8ea87..8aab76f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -1567,6 +1567,7 @@
     public boolean canUnminimizeDesktopTask(int taskId) {
         BubbleTextView.RunningAppState runningAppState =
                 mControllers.taskbarRecentAppsController.getRunningAppState(taskId);
+        Log.d(TAG, "Task id=" + taskId + ", Running app state=" + runningAppState);
         return runningAppState == RunningAppState.MINIMIZED
                 && DesktopModeFlags.ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX.isTrue();
     }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 0566f09..7574c7f 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -413,7 +413,7 @@
         mMSDLPlayerWrapper = msdlPlayerWrapper;
 
         initTransitionEndpoints(mRemoteTargetHandles[0].getTaskViewSimulator()
-                .getOrientationState().getLauncherDeviceProfile());
+                .getOrientationState().getLauncherDeviceProfile(gestureState.getDisplayId()));
         initStateCallbacks();
 
         mIsTransientTaskbar = mDp.isTaskbarPresent
@@ -991,7 +991,8 @@
             // both split and non-split
             RecentsOrientedState orientationState = mRemoteTargetHandles[0].getTaskViewSimulator()
                     .getOrientationState();
-            DeviceProfile dp = orientationState.getLauncherDeviceProfile();
+            DeviceProfile dp = orientationState.getLauncherDeviceProfile(
+                    mGestureState.getDisplayId());
             if (targets.minimizedHomeBounds != null && primaryTaskTarget != null) {
                 Rect overviewStackBounds = mContainerInterface
                         .getOverviewWindowBounds(targets.minimizedHomeBounds, primaryTaskTarget);
diff --git a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
index d70d7eb..333571c 100644
--- a/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
+++ b/quickstep/src/com/android/quickstep/fallback/window/RecentsWindowContext.kt
@@ -18,7 +18,7 @@
 
 import android.content.Context
 import android.graphics.PixelFormat
-import android.view.Display
+import android.view.Display.DEFAULT_DISPLAY
 import android.view.ViewGroup
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
@@ -53,7 +53,7 @@
 
     fun initDeviceProfile() {
         deviceProfile =
-            if (displayId == Display.DEFAULT_DISPLAY)
+            if (displayId == DEFAULT_DISPLAY)
                 InvariantDeviceProfile.INSTANCE[this].getDeviceProfile(this)
             else InvariantDeviceProfile.INSTANCE[this].createDeviceProfileForSecondaryDisplay(this)
     }
diff --git a/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt b/quickstep/src/com/android/quickstep/orientation/LandscapePagedViewHandler.kt
index e3c9b2b..c4e343e 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 FrameLayout.LayoutParams
+        val snapshotParams = thumbnailViews[0].layoutParams as LinearLayout.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 1883649..1bbe005 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 FrameLayout.LayoutParams
+                    val snapshotParams = thumbnailViews[0].layoutParams as LinearLayout.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 1f9f752..67358bbb 100644
--- a/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
+++ b/quickstep/src/com/android/quickstep/orientation/SeascapePagedViewHandler.kt
@@ -26,6 +26,7 @@
 import android.view.View
 import android.view.View.MeasureSpec
 import android.widget.FrameLayout
+import android.widget.LinearLayout
 import androidx.core.util.component1
 import androidx.core.util.component2
 import androidx.core.view.updateLayoutParams
@@ -151,7 +152,7 @@
         desiredTaskId: Int,
         banner: View,
     ): Pair<Float, Float> {
-        val snapshotParams = thumbnailViews[0].layoutParams as FrameLayout.LayoutParams
+        val snapshotParams = thumbnailViews[0].layoutParams as LinearLayout.LayoutParams
         val translationX: Float = (taskViewWidth - banner.height).toFloat()
         val translationY: Float
         if (splitBounds == null) {
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 619075f..aa1c236 100644
--- a/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
+++ b/quickstep/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapper.kt
@@ -18,17 +18,49 @@
 
 import android.view.View.OnClickListener
 import com.android.quickstep.recents.ui.viewmodel.TaskData
+import com.android.quickstep.task.thumbnail.TaskHeaderUiState
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.LiveTile
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.Snapshot
 import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.SnapshotSplash
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.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
@@ -36,46 +68,24 @@
      *
      * @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,
-        hasHeader: Boolean,
-        clickCloseListener: OnClickListener?,
-    ): TaskThumbnailUiState =
+    fun toTaskThumbnailUiState(taskData: TaskData?, isLiveTile: Boolean): TaskThumbnailUiState =
         when {
             taskData !is TaskData.Data -> Uninitialized
-            isLiveTile -> createLiveTileState(taskData, hasHeader, clickCloseListener)
+            isLiveTile -> LiveTile
             isBackgroundOnly(taskData) -> BackgroundOnly(taskData.backgroundColor)
             isSnapshotSplash(taskData) ->
                 SnapshotSplash(
-                    createSnapshotState(taskData, hasHeader, clickCloseListener),
+                    Snapshot(
+                        taskData.thumbnailData?.thumbnail!!,
+                        taskData.thumbnailData.rotation,
+                        taskData.backgroundColor,
+                    ),
                     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,
-            )
+            else -> Uninitialized
         }
 
     private fun isBackgroundOnly(taskData: TaskData.Data) =
@@ -93,17 +103,4 @@
             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
new file mode 100644
index 0000000..2dbd811
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskContentView.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.thumbnail
+
+import android.content.Context
+import android.graphics.Outline
+import android.graphics.Path
+import android.graphics.Rect
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewOutlineProvider
+import android.widget.LinearLayout
+import androidx.core.view.isInvisible
+import com.android.launcher3.Flags.enableDesktopExplodedView
+import com.android.launcher3.Flags.enableRefactorTaskThumbnail
+import com.android.launcher3.R
+import com.android.launcher3.util.ViewPool
+import com.android.quickstep.views.TaskHeaderView
+import com.android.quickstep.views.TaskThumbnailViewDeprecated
+
+/**
+ * TaskContentView is a wrapper around the TaskHeaderView and TaskThumbnailView. It is a sibling to
+ * DWB, AiAi (TaskOverlay).
+ */
+class TaskContentView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+    LinearLayout(context, attrs), ViewPool.Reusable {
+
+    private var taskHeaderView: TaskHeaderView? = null
+    private var taskThumbnailView: TaskThumbnailView? = null
+    private var taskThumbnailViewDeprecated: TaskThumbnailViewDeprecated? = null
+    private var onSizeChanged: ((width: Int, height: Int) -> Unit)? = null
+    private val outlinePath = Path()
+
+    /**
+     * Sets the outline bounds of the view. Default to use view's bound as outline when set to null.
+     */
+    var outlineBounds: Rect? = null
+        set(value) {
+            field = value
+            invalidateOutline()
+        }
+
+    private val bounds = Rect()
+
+    var cornerRadius: Float = 0f
+        set(value) {
+            field = value
+            invalidateOutline()
+        }
+
+    override fun onFinishInflate() {
+        super.onFinishInflate()
+        maybeCreateHeader()
+        createTaskThumbnailView()
+    }
+
+    override fun setScaleX(scaleX: Float) {
+        super.setScaleX(scaleX)
+        taskThumbnailView?.parentScaleXUpdated(scaleX)
+    }
+
+    override fun setScaleY(scaleY: Float) {
+        super.setScaleY(scaleY)
+        taskThumbnailView?.parentScaleYUpdated(scaleY)
+    }
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+        clipToOutline = true
+        outlineProvider =
+            object : ViewOutlineProvider() {
+                override fun getOutline(view: View, outline: Outline) {
+                    val outlineRect = outlineBounds ?: bounds
+                    outlinePath.apply {
+                        rewind()
+                        addRoundRect(
+                            outlineRect.left.toFloat(),
+                            outlineRect.top.toFloat(),
+                            outlineRect.right.toFloat(),
+                            outlineRect.bottom.toFloat(),
+                            cornerRadius / scaleX,
+                            cornerRadius / scaleY,
+                            Path.Direction.CW,
+                        )
+                    }
+                    outline.setPath(outlinePath)
+                }
+            }
+    }
+
+    override fun onRecycle() {
+        taskHeaderView?.isInvisible = true
+        onSizeChanged = null
+        outlineBounds = null
+        taskThumbnailView?.onRecycle()
+        taskThumbnailViewDeprecated?.onRecycle()
+    }
+
+    fun doOnSizeChange(action: (width: Int, height: Int) -> Unit) {
+        onSizeChanged = action
+    }
+
+    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+        super.onSizeChanged(w, h, oldw, oldh)
+        onSizeChanged?.invoke(width, height)
+        bounds.set(0, 0, w, h)
+        invalidateOutline()
+    }
+
+    private fun maybeCreateHeader() {
+        if (enableDesktopExplodedView() && taskHeaderView == null) {
+            taskHeaderView =
+                LayoutInflater.from(context).inflate(R.layout.task_header_view, this, false)
+                    as TaskHeaderView
+            addView(taskHeaderView)
+        }
+    }
+
+    private fun createTaskThumbnailView() {
+        if (taskThumbnailView == null) {
+            if (enableRefactorTaskThumbnail()) {
+                taskThumbnailView =
+                    LayoutInflater.from(context).inflate(R.layout.task_thumbnail, this, false)
+                        as TaskThumbnailView
+                addView(taskThumbnailView)
+            } else {
+                taskThumbnailViewDeprecated =
+                    LayoutInflater.from(context)
+                        .inflate(R.layout.task_thumbnail_deprecated, this, false)
+                        as TaskThumbnailViewDeprecated
+                addView(taskThumbnailViewDeprecated)
+            }
+        }
+    }
+
+    fun setState(
+        taskHeaderState: TaskHeaderUiState,
+        taskThumbnailUiState: TaskThumbnailUiState,
+        taskId: Int?,
+    ) {
+        taskHeaderView?.setState(taskHeaderState)
+        taskThumbnailView?.setState(taskThumbnailUiState, taskId)
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskHeaderUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskHeaderUiState.kt
new file mode 100644
index 0000000..09fb540
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskHeaderUiState.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.thumbnail
+
+import android.graphics.drawable.Drawable
+import android.view.View
+
+sealed class TaskHeaderUiState {
+    data class ShowHeader(val header: ThumbnailHeader) : TaskHeaderUiState()
+
+    data object HideHeader : TaskHeaderUiState()
+
+    data class ThumbnailHeader(
+        val icon: Drawable,
+        val title: String,
+        val clickCloseListener: View.OnClickListener,
+    )
+}
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
index db593d3..a5c9ac0 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailUiState.kt
@@ -19,7 +19,6 @@
 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 {
@@ -27,37 +26,14 @@
 
     data class BackgroundOnly(@ColorInt val backgroundColor: Int) : TaskThumbnailUiState()
 
+    data object LiveTile : TaskThumbnailUiState()
+
     data class SnapshotSplash(val snapshot: Snapshot, val splash: Drawable?) :
         TaskThumbnailUiState()
 
-    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,
+    data class Snapshot(
+        val bitmap: Bitmap,
+        @Surface.Rotation val thumbnailRotation: Int,
+        @ColorInt val backgroundColor: Int,
     )
 }
diff --git a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
index 0edbacc..78a16f1 100644
--- a/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
+++ b/quickstep/src/com/android/quickstep/task/thumbnail/TaskThumbnailView.kt
@@ -19,32 +19,24 @@
 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, ViewPool.Reusable {
+class TaskThumbnailView : FrameLayout {
     private val scrimView: View by lazy { findViewById(R.id.task_thumbnail_scrim) }
     private val liveTileView: LiveTileView by lazy { findViewById(R.id.task_thumbnail_live_tile) }
     private val thumbnailView: FixedSizeImageView by lazy { findViewById(R.id.task_thumbnail) }
@@ -53,30 +45,9 @@
     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)
@@ -87,39 +58,8 @@
         defStyleAttr: Int,
     ) : super(context, attrs, defStyleAttr)
 
-    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() {
+    fun onRecycle() {
         uiState = Uninitialized
-        onSizeChanged = null
-        outlineBounds = null
         resetViews()
     }
 
@@ -130,7 +70,7 @@
         resetViews()
         when (state) {
             is Uninitialized -> {}
-            is LiveTile -> drawLiveWindow(state)
+            is LiveTile -> drawLiveWindow()
             is SnapshotSplash -> drawSnapshotSplash(state)
             is BackgroundOnly -> drawBackground(state.backgroundColor)
         }
@@ -155,25 +95,12 @@
         splashIcon.alpha = value
     }
 
-    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)
+    fun parentScaleXUpdated(scaleX: Float) {
         // Splash icon should ignore scale on TTV
         splashIcon.scaleX = 1 / scaleX
     }
 
-    override fun setScaleY(scaleY: Float) {
-        super.setScaleY(scaleY)
+    fun parentScaleYUpdated(scaleY: Float) {
         // Splash icon should ignore scale on TTV
         splashIcon.scaleY = 1 / scaleY
     }
@@ -187,20 +114,14 @@
         splashIcon.setImageDrawable(null)
         scrimView.alpha = 0f
         setBackgroundColor(Color.BLACK)
-        taskThumbnailViewHeader?.isInvisible = true
     }
 
     private fun drawBackground(@ColorInt background: Int) {
         setBackgroundColor(background)
     }
 
-    private fun drawLiveWindow(liveTile: LiveTile) {
+    private fun drawLiveWindow() {
         liveTileView.isInvisible = false
-
-        if (liveTile is LiveTile.WithHeader) {
-            taskThumbnailViewHeader?.isInvisible = false
-            taskThumbnailViewHeader?.setHeader(liveTile.header)
-        }
     }
 
     private fun drawSnapshotSplash(snapshotSplash: SnapshotSplash) {
@@ -212,11 +133,6 @@
     }
 
     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
@@ -232,16 +148,6 @@
         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/RecentsOrientedState.java b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
index a5be89a..8954d80 100644
--- a/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
+++ b/quickstep/src/com/android/quickstep/util/RecentsOrientedState.java
@@ -22,6 +22,7 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
+import static com.android.launcher3.Flags.enableOverviewOnConnectedDisplays;
 import static com.android.launcher3.LauncherPrefs.ALLOW_ROTATION;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SettingsCache.ROTATION_SETTING_URI;
@@ -53,6 +54,7 @@
 import com.android.quickstep.BaseContainerInterface;
 import com.android.quickstep.SystemUiProxy;
 import com.android.quickstep.TaskAnimationManager;
+import com.android.quickstep.fallback.window.RecentsDisplayModel;
 import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
 
 import java.lang.annotation.Retention;
@@ -571,19 +573,25 @@
     /**
      * Returns the device profile based on expected launcher rotation
      */
-    public DeviceProfile getLauncherDeviceProfile() {
-        InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
-        Point currentSize = DisplayController.INSTANCE.get(mContext).getInfo().currentSize;
-
-        int width, height;
-        if ((mRecentsActivityRotation == ROTATION_90 || mRecentsActivityRotation == ROTATION_270)) {
-            width = Math.max(currentSize.x, currentSize.y);
-            height = Math.min(currentSize.x, currentSize.y);
+    public DeviceProfile getLauncherDeviceProfile(int displayId) {
+        if (enableOverviewOnConnectedDisplays()) {
+            return RecentsDisplayModel.getINSTANCE().get(mContext).getRecentsWindowManager(
+                    displayId).getDeviceProfile();
         } else {
-            width = Math.min(currentSize.x, currentSize.y);
-            height = Math.max(currentSize.x, currentSize.y);
+            InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(mContext);
+            Point currentSize = DisplayController.INSTANCE.get(mContext).getInfo().currentSize;
+
+            int width, height;
+            if ((mRecentsActivityRotation == ROTATION_90
+                    || mRecentsActivityRotation == ROTATION_270)) {
+                width = Math.max(currentSize.x, currentSize.y);
+                height = Math.min(currentSize.x, currentSize.y);
+            } else {
+                width = Math.min(currentSize.x, currentSize.y);
+                height = Math.max(currentSize.x, currentSize.y);
+            }
+            return idp.getBestMatch(width, height, mRecentsActivityRotation);
         }
-        return idp.getBestMatch(width, height, mRecentsActivityRotation);
     }
 
     private static String nameAndAddress(Object obj) {
diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt
index 96a5733..d6e553d 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 snapshot = taskContainer.snapshotView
+        val taskContentView = taskContainer.taskContentView
         val iconView: View = taskContainer.iconView.asView()
         if (enableRefactorTaskThumbnail()) {
             builder.add(
@@ -241,7 +241,11 @@
             val centerThumbnailTranslationX: Float = (taskViewWidth - snapshotViewSize.x) / 2f
             val finalScaleX: Float = taskViewWidth.toFloat() / snapshotViewSize.x
             builder.add(
-                ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_X, centerThumbnailTranslationX)
+                ObjectAnimator.ofFloat(
+                    taskContentView,
+                    View.TRANSLATION_X,
+                    centerThumbnailTranslationX,
+                )
             )
             if (!enableOverviewIconMenu()) {
                 // icons are anchored from Gravity.END, so need to use negative translation
@@ -250,15 +254,17 @@
                     ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, -centerIconTranslationX)
                 )
             }
-            builder.add(ObjectAnimator.ofFloat(snapshot, View.SCALE_X, finalScaleX))
+            builder.add(ObjectAnimator.ofFloat(taskContentView, View.SCALE_X, finalScaleX))
 
             // Reset other dimensions
             // TODO(b/271468547), can't set Y translate to 0, need to account for top space
-            snapshot.scaleY = 1f
+            taskContentView.scaleY = 1f
             val translateYResetVal: Float =
                 if (!isPrimaryTaskSplitting) 0f
                 else deviceProfile.overviewTaskThumbnailTopMarginPx.toFloat()
-            builder.add(ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_Y, translateYResetVal))
+            builder.add(
+                ObjectAnimator.ofFloat(taskContentView, View.TRANSLATION_Y, translateYResetVal)
+            )
         } else {
             val thumbnailSize = taskViewHeight - deviceProfile.overviewTaskThumbnailTopMarginPx
             // Center view first so scaling happens uniformly, alternatively we can move pivotY to 0
@@ -281,18 +287,22 @@
             }
             val finalScaleY: Float = thumbnailSize.toFloat() / snapshotViewSize.y
             builder.add(
-                ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_Y, centerThumbnailTranslationY)
+                ObjectAnimator.ofFloat(
+                    taskContentView,
+                    View.TRANSLATION_Y,
+                    centerThumbnailTranslationY,
+                )
             )
 
             if (!enableOverviewIconMenu()) {
                 // icons are anchored from Gravity.END, so need to use negative translation
                 builder.add(ObjectAnimator.ofFloat(iconView, View.TRANSLATION_X, 0f))
             }
-            builder.add(ObjectAnimator.ofFloat(snapshot, View.SCALE_Y, finalScaleY))
+            builder.add(ObjectAnimator.ofFloat(taskContentView, View.SCALE_Y, finalScaleY))
 
             // Reset other dimensions
-            snapshot.scaleX = 1f
-            builder.add(ObjectAnimator.ofFloat(snapshot, View.TRANSLATION_X, 0f))
+            taskContentView.scaleX = 1f
+            builder.add(ObjectAnimator.ofFloat(taskContentView, View.TRANSLATION_X, 0f))
         }
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
index e0ea518..1d035e9 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.kt
@@ -54,6 +54,7 @@
 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
@@ -69,27 +70,14 @@
     ) {
     private val contentViewFullscreenParams = FullscreenDrawParams(context)
 
-    private val taskThumbnailViewDeprecatedPool =
-        if (!enableRefactorTaskThumbnail()) {
-            ViewPool<TaskThumbnailViewDeprecated>(
-                context,
-                this,
-                R.layout.task_thumbnail_deprecated,
-                VIEW_POOL_MAX_SIZE,
-                VIEW_POOL_INITIAL_SIZE,
-            )
-        } else null
-
-    private val taskThumbnailViewPool =
-        if (enableRefactorTaskThumbnail()) {
-            ViewPool<TaskThumbnailView>(
-                context,
-                this,
-                R.layout.task_thumbnail,
-                VIEW_POOL_MAX_SIZE,
-                VIEW_POOL_INITIAL_SIZE,
-            )
-        } else null
+    private val taskContentViewPool =
+        ViewPool<TaskContentView>(
+            context,
+            this,
+            R.layout.task_content_view,
+            VIEW_POOL_MAX_SIZE,
+            VIEW_POOL_INITIAL_SIZE,
+        )
 
     private val tempPointF = PointF()
     private val lastComputedTaskSize = Rect()
@@ -239,7 +227,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.snapshotView.apply {
+                it.taskContentView.apply {
                     scaleX = 1.0f
                     scaleY = 1.0f
                     translationX = 0.0f
@@ -247,7 +235,7 @@
                 }
 
                 // Position the task to the same position as it would be on the desktop
-                it.snapshotView.updateLayoutParams<LayoutParams> {
+                it.taskContentView?.updateLayoutParams<LayoutParams> {
                     gravity = Gravity.LEFT or Gravity.TOP
                     width = taskWidth.toInt()
                     height = taskHeight.toInt()
@@ -258,7 +246,7 @@
                 if (
                     enableDesktopRecentsTransitionsCornersBugfix() && enableRefactorTaskThumbnail()
                 ) {
-                    it.thumbnailView.outlineBounds =
+                    it.taskContentView?.outlineBounds =
                         if (intersects(overviewTaskPosition, screenRect))
                             Rect(overviewTaskPosition).apply {
                                 intersectUnchecked(screenRect)
@@ -275,7 +263,7 @@
             } else {
                 // During the animation, apply translation and scale such that the view is
                 // transformed to where we want, without triggering layout.
-                it.snapshotView.apply {
+                it.taskContentView.apply {
                     pivotX = 0.0f
                     pivotY = 0.0f
                     translationX = taskLeft - left
@@ -308,17 +296,19 @@
         val backgroundViewIndex = contentView.indexOfChild(backgroundView)
         taskContainers =
             tasks.map { task ->
+                val taskContentView = taskContentViewPool.view
+                contentView.addView(taskContentView, backgroundViewIndex + 1)
                 val snapshotView =
                     if (enableRefactorTaskThumbnail()) {
-                        taskThumbnailViewPool!!.view
+                        taskContentView.findViewById<TaskThumbnailView>(R.id.snapshot)
                     } else {
-                        taskThumbnailViewDeprecatedPool!!.view
+                        taskContentView.findViewById<TaskThumbnailViewDeprecated>(R.id.snapshot)
                     }
-                contentView.addView(snapshotView, backgroundViewIndex + 1)
 
                 TaskContainer(
                     this,
                     task,
+                    taskContentView,
                     snapshotView,
                     iconView,
                     TransformingTouchDelegate(iconView.asView()),
@@ -465,12 +455,8 @@
     }
 
     private fun removeAndRecycleThumbnailView(taskContainer: TaskContainer) {
-        contentView.removeView(taskContainer.snapshotView)
-        if (enableRefactorTaskThumbnail()) {
-            taskThumbnailViewPool!!.recycle(taskContainer.thumbnailView)
-        } else {
-            taskThumbnailViewDeprecatedPool!!.recycle(taskContainer.thumbnailViewDeprecated)
-        }
+        contentView.removeView(taskContainer.taskContentView)
+        taskContentViewPool.recycle(taskContainer.taskContentView)
     }
 
     private fun updateTaskPositions() {
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
index a8eee0a..71a4dde 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.kt
@@ -24,7 +24,6 @@
 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
@@ -78,8 +77,8 @@
         val splitBoundsConfig = splitBoundsConfig ?: return
         val inSplitSelection = getThisTaskCurrentlyInSplitSelection() != INVALID_TASK_ID
         pagedOrientationHandler.measureGroupedTaskViewThumbnailBounds(
-            leftTopTaskContainer.snapshotView,
-            rightBottomTaskContainer.snapshotView,
+            leftTopTaskContainer.taskContentView,
+            rightBottomTaskContainer.taskContentView,
             widthSize,
             heightSize,
             splitBoundsConfig,
@@ -95,12 +94,8 @@
 
     override fun inflateViewStubs() {
         super.inflateViewStubs()
-        findViewById<ViewStub>(R.id.bottomright_snapshot)
-            ?.apply {
-                layoutResource =
-                    if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail
-                    else R.layout.task_thumbnail_deprecated
-            }
+        findViewById<ViewStub>(R.id.bottomright_task_content_view)
+            ?.apply { layoutResource = R.layout.task_content_view }
             ?.inflate()
         findViewById<ViewStub>(R.id.bottomRight_icon)
             ?.apply {
@@ -128,6 +123,7 @@
             listOf(
                 createTaskContainer(
                     primaryTask,
+                    R.id.task_content_view,
                     R.id.snapshot,
                     R.id.icon,
                     R.id.show_windows,
@@ -137,7 +133,8 @@
                 ),
                 createTaskContainer(
                     secondaryTask,
-                    R.id.bottomright_snapshot,
+                    R.id.bottomright_task_content_view,
+                    R.id.snapshot,
                     R.id.bottomRight_icon,
                     R.id.show_windows_right,
                     R.id.bottomRight_digital_wellbeing_toast,
@@ -240,8 +237,8 @@
                 leftTopTaskContainer.iconView.asView(),
                 rightBottomTaskContainer.iconView.asView(),
                 taskIconHeight,
-                leftTopTaskContainer.snapshotView.measuredWidth,
-                leftTopTaskContainer.snapshotView.measuredHeight,
+                leftTopTaskContainer.taskContentView.measuredWidth,
+                leftTopTaskContainer.taskContentView.measuredHeight,
                 measuredHeight,
                 measuredWidth,
                 isRtl,
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 2c84e52..3b94380 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -4948,6 +4948,10 @@
         }
         setPivotX(mTempPointF.x);
         setPivotY(mTempPointF.y);
+        if (enableGridOnlyOverview()) {
+            runActionOnRemoteHandles(remoteTargetHandle ->
+                    remoteTargetHandle.getTaskViewSimulator().setPivotOverride(mTempPointF));
+        }
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/views/TaskContainer.kt b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
index 2b9d036..6d7ae70 100644
--- a/quickstep/src/com/android/quickstep/views/TaskContainer.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskContainer.kt
@@ -28,6 +28,7 @@
 import com.android.quickstep.ViewUtils.addAccessibleChildToList
 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
@@ -36,6 +37,7 @@
 class TaskContainer(
     val taskView: TaskView,
     val task: Task,
+    val taskContentView: TaskContentView,
     val snapshotView: View,
     val iconView: TaskViewIcon,
     /**
@@ -104,8 +106,8 @@
 
     fun destroy() {
         digitalWellBeingToast?.destroy()
-        snapshotView.scaleX = 1f
-        snapshotView.scaleY = 1f
+        taskContentView.scaleX = 1f
+        taskContentView.scaleY = 1f
         overlay.destroy()
         if (enableRefactorTaskThumbnail()) {
             isThumbnailValid = false
@@ -136,13 +138,9 @@
         hasHeader: Boolean,
         clickCloseListener: OnClickListener?,
     ) {
-        thumbnailView.setState(
-            TaskUiStateMapper.toTaskThumbnailUiState(
-                state,
-                liveTile,
-                hasHeader,
-                clickCloseListener,
-            ),
+        taskContentView.setState(
+            TaskUiStateMapper.toTaskHeaderState(state, hasHeader, clickCloseListener),
+            TaskUiStateMapper.toTaskThumbnailUiState(state, liveTile),
             state?.taskId,
         )
         thumbnailData = if (state is TaskData.Data) state.thumbnailData else null
diff --git a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt b/quickstep/src/com/android/quickstep/views/TaskHeaderView.kt
similarity index 66%
rename from quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt
rename to quickstep/src/com/android/quickstep/views/TaskHeaderView.kt
index 9a8805b..0427402 100644
--- a/quickstep/src/com/android/quickstep/views/TaskThumbnailViewHeader.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskHeaderView.kt
@@ -22,19 +22,29 @@
 import android.widget.ImageButton
 import android.widget.ImageView
 import android.widget.TextView
+import androidx.core.view.isGone
 import com.android.launcher3.R
-import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.ThumbnailHeader
+import com.android.quickstep.task.thumbnail.TaskHeaderUiState
 
-class TaskThumbnailViewHeader
-@JvmOverloads
-constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) {
+class TaskHeaderView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
+    FrameLayout(context, attrs) {
 
     private val headerTitleView: TextView by lazy { findViewById(R.id.header_app_title) }
     private val headerIconView: ImageView by lazy { findViewById(R.id.header_app_icon) }
     private val headerCloseButton: ImageButton by lazy { findViewById(R.id.header_close_button) }
 
-    fun setHeader(header: ThumbnailHeader) {
-        headerTitleView.setText(header.title)
+    fun setState(taskHeaderState: TaskHeaderUiState) {
+        when (taskHeaderState) {
+            is TaskHeaderUiState.ShowHeader -> {
+                setHeader(taskHeaderState.header)
+                isGone = false
+            }
+            TaskHeaderUiState.HideHeader -> isGone = true
+        }
+    }
+
+    private fun setHeader(header: TaskHeaderUiState.ThumbnailHeader) {
+        headerTitleView.text = header.title
         headerIconView.setImageDrawable(header.icon)
         headerCloseButton.setOnClickListener(header.clickCloseListener)
     }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.kt b/quickstep/src/com/android/quickstep/views/TaskView.kt
index 86e1305..91a4273 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.kt
+++ b/quickstep/src/com/android/quickstep/views/TaskView.kt
@@ -85,6 +85,7 @@
 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
@@ -721,13 +722,10 @@
     }
 
     protected open fun inflateViewStubs() {
-        findViewById<ViewStub>(R.id.snapshot)
-            ?.apply {
-                layoutResource =
-                    if (enableRefactorTaskThumbnail()) R.layout.task_thumbnail
-                    else R.layout.task_thumbnail_deprecated
-            }
+        findViewById<ViewStub>(R.id.task_content_view)
+            ?.apply { layoutResource = R.layout.task_content_view }
             ?.inflate()
+
         findViewById<ViewStub>(R.id.icon)
             ?.apply {
                 layoutResource =
@@ -845,6 +843,7 @@
             listOf(
                 createTaskContainer(
                     task,
+                    R.id.task_content_view,
                     R.id.snapshot,
                     R.id.icon,
                     R.id.show_windows,
@@ -876,8 +875,9 @@
         taskContainers.forEach { container ->
             container.bind()
             if (enableRefactorTaskThumbnail()) {
-                container.thumbnailView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
-                container.thumbnailView.doOnSizeChange { width, height ->
+                container.taskContentView.cornerRadius =
+                    thumbnailFullscreenParams.currentCornerRadius
+                container.taskContentView.doOnSizeChange { width, height ->
                     updateThumbnailValidity(container)
                     updateThumbnailMatrix(container, width, height)
                 }
@@ -903,6 +903,7 @@
 
     protected fun createTaskContainer(
         task: Task,
+        @IdRes taskContentViewId: Int,
         @IdRes thumbnailViewId: Int,
         @IdRes iconViewId: Int,
         @IdRes showWindowViewId: Int,
@@ -911,10 +912,12 @@
         taskOverlayFactory: TaskOverlayFactory,
     ): TaskContainer {
         val iconView = findViewById<View>(iconViewId) as TaskViewIcon
+        val taskContentView = findViewById<TaskContentView>(taskContentViewId)
         return TaskContainer(
             this,
             task,
-            findViewById(thumbnailViewId),
+            taskContentView,
+            taskContentView.findViewById(thumbnailViewId),
             iconView,
             TransformingTouchDelegate(iconView.asView()),
             stagePosition,
@@ -1002,7 +1005,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?.snapshotView?.updateLayoutParams<LayoutParams> {
+        firstTaskContainer?.taskContentView?.updateLayoutParams<LayoutParams> {
             topMargin = container.deviceProfile.overviewTaskThumbnailTopMarginPx
         }
         taskContainers.forEach { it.digitalWellBeingToast?.setupLayout() }
@@ -1016,11 +1019,11 @@
             val thumbnailBounds = Rect()
             if (relativeToDragLayer) {
                 container.dragLayer.getDescendantRectRelativeToSelf(
-                    it.snapshotView,
+                    it.taskContentView,
                     thumbnailBounds,
                 )
             } else {
-                thumbnailBounds.set(it.snapshotView)
+                thumbnailBounds.set(it.taskContentView)
             }
             bounds.union(thumbnailBounds)
         }
@@ -1709,7 +1712,7 @@
         updateFullscreenParams(thumbnailFullscreenParams)
         taskContainers.forEach {
             if (enableRefactorTaskThumbnail()) {
-                it.thumbnailView.cornerRadius = thumbnailFullscreenParams.currentCornerRadius
+                it.taskContentView.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
new file mode 100644
index 0000000..8cc09d4
--- /dev/null
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/SplashHelper.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.thumbnail
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.Paint
+
+object SplashHelper {
+    private val BITMAP_RECT_COLORS = listOf(Color.GREEN, Color.RED, Color.BLUE, Color.CYAN)
+
+    fun createSplash(): Bitmap = createBitmap(width = 20, height = 20, rectColorRotation = 1)
+
+    fun createBitmap(width: Int, height: Int, rectColorRotation: Int = 0): Bitmap =
+        Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).apply {
+            Canvas(this).apply {
+                val paint = Paint()
+                paint.color = BITMAP_RECT_COLORS[rectColorRotation % 4]
+                drawRect(0f, 0f, width / 2f, height / 2f, paint)
+                paint.color = BITMAP_RECT_COLORS[(1 + rectColorRotation) % 4]
+                drawRect(width / 2f, 0f, width.toFloat(), height / 2f, paint)
+                paint.color = BITMAP_RECT_COLORS[(2 + rectColorRotation) % 4]
+                drawRect(0f, height / 2f, width / 2f, height.toFloat(), paint)
+                paint.color = BITMAP_RECT_COLORS[(3 + rectColorRotation) % 4]
+                drawRect(width / 2f, height / 2f, width.toFloat(), height.toFloat(), paint)
+            }
+        }
+}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskContentViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskContentViewScreenshotTest.kt
new file mode 100644
index 0000000..d36faa2
--- /dev/null
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskContentViewScreenshotTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.task.thumbnail
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.BitmapDrawable
+import android.view.LayoutInflater
+import com.android.launcher3.R
+import com.android.quickstep.task.thumbnail.SplashHelper.createSplash
+import com.android.quickstep.task.thumbnail.TaskThumbnailUiState.BackgroundOnly
+import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.Displays
+import platform.test.screenshot.ViewScreenshotTestRule
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+/** Screenshot tests for [TaskContentView]. */
+@RunWith(ParameterizedAndroidJunit4::class)
+class TaskContentViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+
+    @get:Rule
+    val screenshotRule =
+        ViewScreenshotTestRule(
+            emulationSpec,
+            ViewScreenshotGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)),
+        )
+
+    @Test
+    fun taskContentView_recyclesToUninitialized() {
+        screenshotRule.screenshotTest("taskContentView_uninitialized") { activity ->
+            activity.actionBar?.hide()
+            val taskContentView = createTaskContentView(activity)
+            taskContentView.setState(
+                TaskHeaderUiState.HideHeader,
+                BackgroundOnly(Color.YELLOW),
+                null,
+            )
+            taskContentView.onRecycle()
+            taskContentView
+        }
+    }
+
+    @Test
+    fun taskContentView_shows_thumbnail_and_header() {
+        screenshotRule.screenshotTest("taskContentView_shows_thumbnail_and_header") { activity ->
+            activity.actionBar?.hide()
+            createTaskContentView(activity).apply {
+                setState(
+                    TaskHeaderUiState.ShowHeader(
+                        TaskHeaderUiState.ThumbnailHeader(
+                            BitmapDrawable(activity.resources, createSplash()),
+                            "test",
+                        ) {}
+                    ),
+                    BackgroundOnly(Color.YELLOW),
+                    null,
+                )
+            }
+        }
+    }
+
+    @Test
+    fun taskContentView_scaled_roundRoundedCorners() {
+        screenshotRule.screenshotTest("taskContentView_scaledRoundedCorners") { activity ->
+            activity.actionBar?.hide()
+            createTaskContentView(activity).apply {
+                scaleX = 0.75f
+                scaleY = 0.3f
+                setState(TaskHeaderUiState.HideHeader, BackgroundOnly(Color.YELLOW), null)
+            }
+        }
+    }
+
+    private fun createTaskContentView(context: Context): TaskContentView {
+        val taskContentView =
+            LayoutInflater.from(context).inflate(R.layout.task_content_view, null, false)
+                as TaskContentView
+        taskContentView.cornerRadius = CORNER_RADIUS
+        return taskContentView
+    }
+
+    companion object {
+        @Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() =
+            DeviceEmulationSpec.forDisplays(
+                Displays.Phone,
+                isDarkTheme = false,
+                isLandscape = false,
+            )
+
+        const val CORNER_RADIUS = 56f
+    }
+}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskHeaderViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskHeaderViewScreenshotTest.kt
new file mode 100644
index 0000000..e30554e
--- /dev/null
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskHeaderViewScreenshotTest.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep.task.thumbnail
+
+import android.content.Context
+import android.graphics.drawable.BitmapDrawable
+import android.view.LayoutInflater
+import com.android.launcher3.R
+import com.android.quickstep.task.thumbnail.SplashHelper.createSplash
+import com.android.quickstep.views.TaskHeaderView
+import com.google.android.apps.nexuslauncher.imagecomparison.goldenpathmanager.ViewScreenshotGoldenPathManager
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.Displays
+import platform.test.screenshot.ViewScreenshotTestRule
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+/** Screenshot tests for [TaskHeaderView]. */
+@RunWith(ParameterizedAndroidJunit4::class)
+class TaskHeaderViewScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    @get:Rule
+    val screenshotRule =
+        ViewScreenshotTestRule(
+            emulationSpec,
+            ViewScreenshotGoldenPathManager(getEmulatedDevicePathConfig(emulationSpec)),
+        )
+
+    @Test
+    fun taskHeaderView_showHeader() {
+        screenshotRule.screenshotTest("taskHeaderView_showHeader") { activity ->
+            activity.actionBar?.hide()
+            createTaskHeaderView(activity).apply {
+                setState(
+                    TaskHeaderUiState.ShowHeader(
+                        TaskHeaderUiState.ThumbnailHeader(
+                            BitmapDrawable(activity.resources, createSplash()),
+                            "Example",
+                        ) {}
+                    )
+                )
+            }
+        }
+    }
+
+    private fun createTaskHeaderView(context: Context): TaskHeaderView {
+        val taskHeaderView =
+            LayoutInflater.from(context).inflate(R.layout.task_header_view, null, false)
+                as TaskHeaderView
+        return taskHeaderView
+    }
+
+    companion object {
+        @Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() =
+            DeviceEmulationSpec.forDisplays(
+                Displays.Tablet,
+                isDarkTheme = false,
+                isLandscape = true,
+            )
+    }
+}
diff --git a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
index 80b2c16..45df735 100644
--- a/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
+++ b/quickstep/tests/multivalentScreenshotTests/src/com/android/quickstep/task/thumbnail/TaskThumbnailViewScreenshotTest.kt
@@ -16,16 +16,14 @@
 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
@@ -90,23 +88,25 @@
     }
 
     @Test
-    fun taskThumbnailView_liveTile_withoutHeader() {
+    fun taskThumbnailView_liveTile() {
         screenshotRule.screenshotTest("taskThumbnailView_liveTile") { activity ->
             activity.actionBar?.hide()
-            createTaskThumbnailView(activity).apply {
-                setState(TaskThumbnailUiState.LiveTile.WithoutHeader)
-            }
+            createTaskThumbnailView(activity).apply { setState(TaskThumbnailUiState.LiveTile) }
         }
     }
 
     @Test
-    fun taskThumbnailView_image_withoutHeader() {
+    fun taskThumbnailView_image() {
         screenshotRule.screenshotTest("taskThumbnailView_image") { activity ->
             activity.actionBar?.hide()
             createTaskThumbnailView(activity).apply {
                 setState(
                     SnapshotSplash(
-                        Snapshot.WithoutHeader(createBitmap(), ROTATION_0, Color.DKGRAY),
+                        Snapshot(
+                            createBitmap(VIEW_ENV_WIDTH, VIEW_ENV_HEIGHT),
+                            ROTATION_0,
+                            Color.DKGRAY,
+                        ),
                         null,
                     )
                 )
@@ -115,14 +115,14 @@
     }
 
     @Test
-    fun taskThumbnailView_image_withoutHeader_withImageMatrix() {
+    fun taskThumbnailView_image_withImageMatrix() {
         screenshotRule.screenshotTest("taskThumbnailView_image_withMatrix") { activity ->
             activity.actionBar?.hide()
             createTaskThumbnailView(activity).apply {
                 val lessThanHeightMatchingAspectRatio = (VIEW_ENV_HEIGHT / 2) - 200
                 setState(
                     SnapshotSplash(
-                        Snapshot.WithoutHeader(
+                        Snapshot(
                             createBitmap(
                                 width = VIEW_ENV_WIDTH / 2,
                                 height = lessThanHeightMatchingAspectRatio,
@@ -139,13 +139,17 @@
     }
 
     @Test
-    fun taskThumbnailView_splash_withoutHeader() {
+    fun taskThumbnailView_splash() {
         screenshotRule.screenshotTest("taskThumbnailView_partial_splash") { activity ->
             activity.actionBar?.hide()
             createTaskThumbnailView(activity).apply {
                 setState(
                     SnapshotSplash(
-                        Snapshot.WithoutHeader(createBitmap(), ROTATION_0, Color.DKGRAY),
+                        Snapshot(
+                            createBitmap(VIEW_ENV_WIDTH, VIEW_ENV_HEIGHT),
+                            ROTATION_0,
+                            Color.DKGRAY,
+                        ),
                         BitmapDrawable(activity.resources, createSplash()),
                     )
                 )
@@ -155,14 +159,14 @@
     }
 
     @Test
-    fun taskThumbnailView_splash_withoutHeader_withImageMatrix() {
+    fun taskThumbnailView_splash_withImageMatrix() {
         screenshotRule.screenshotTest("taskThumbnailView_partial_splash_withMatrix") { activity ->
             activity.actionBar?.hide()
             createTaskThumbnailView(activity).apply {
                 val lessThanHeightMatchingAspectRatio = (VIEW_ENV_HEIGHT / 2) - 200
                 setState(
                     SnapshotSplash(
-                        Snapshot.WithoutHeader(
+                        Snapshot(
                             createBitmap(
                                 width = VIEW_ENV_WIDTH / 2,
                                 height = lessThanHeightMatchingAspectRatio,
@@ -229,31 +233,9 @@
         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
@@ -264,8 +246,6 @@
                 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 42adfec..d2abed8 100644
--- a/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
+++ b/quickstep/tests/multivalentTests/src/com/android/launcher3/model/data/TaskViewItemInfoTest.kt
@@ -35,6 +35,7 @@
 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
@@ -198,6 +199,7 @@
         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/recents/ui/mapper/TaskUiStateMapperTest.kt b/quickstep/tests/multivalentTests/src/com/android/quickstep/recents/ui/mapper/TaskUiStateMapperTest.kt
index 7ca194a..b49923f 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
@@ -25,10 +25,10 @@
 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.Test
@@ -37,37 +37,21 @@
 @RunWith(AndroidJUnit4::class)
 class TaskUiStateMapperTest {
 
+    /** TaskHeaderUiState */
     @Test
-    fun taskData_isNull_returns_Uninitialized() {
+    fun taskData_isNull_returns_HideHeader() {
         val result =
-            TaskUiStateMapper.toTaskThumbnailUiState(
+            TaskUiStateMapper.toTaskHeaderState(
                 taskData = null,
-                isLiveTile = false,
                 hasHeader = false,
                 clickCloseListener = null,
             )
-        assertThat(result).isEqualTo(TaskThumbnailUiState.Uninitialized)
-    }
-
-    @Test
-    fun taskData_isLiveTile_returns_LiveTile() {
-        val inputs =
-            listOf(TASK_DATA, TASK_DATA.copy(thumbnailData = null), TASK_DATA.copy(isLocked = true))
-        inputs.forEach { input ->
-            val result =
-                TaskUiStateMapper.toTaskThumbnailUiState(
-                    taskData = input,
-                    isLiveTile = true,
-                    hasHeader = false,
-                    clickCloseListener = null,
-                )
-            assertThat(result).isEqualTo(LiveTile.WithoutHeader)
-        }
+        assertThat(result).isEqualTo(TaskHeaderUiState.HideHeader)
     }
 
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
     @Test
-    fun taskData_isLiveTileWithHeader_returns_LiveTileWithHeader() {
+    fun taskData_hasHeader_and_taskData_returnsShowHeader() {
         val inputs =
             listOf(
                 TASK_DATA,
@@ -77,14 +61,18 @@
             )
         val closeCallback = View.OnClickListener {}
         val expected =
-            LiveTile.WithHeader(
-                header = ThumbnailHeader(TASK_ICON, TASK_TITLE_DESCRIPTION, closeCallback)
+            TaskHeaderUiState.ShowHeader(
+                header =
+                    TaskHeaderUiState.ThumbnailHeader(
+                        TASK_ICON,
+                        TASK_TITLE_DESCRIPTION,
+                        closeCallback,
+                    )
             )
         inputs.forEach { taskData ->
             val result =
-                TaskUiStateMapper.toTaskThumbnailUiState(
+                TaskUiStateMapper.toTaskHeaderState(
                     taskData = taskData,
-                    isLiveTile = true,
                     hasHeader = true,
                     clickCloseListener = closeCallback,
                 )
@@ -94,7 +82,7 @@
 
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_EXPLODED_VIEW)
     @Test
-    fun taskData_isLiveTileWithHeader_missingHeaderData_returns_LiveTileWithoutHeader() {
+    fun taskData_hasHeader_emptyTaskData_returns_HideHeader() {
         val inputs =
             listOf(
                 TASK_DATA.copy(icon = null),
@@ -104,30 +92,42 @@
 
         inputs.forEach { taskData ->
             val result =
-                TaskUiStateMapper.toTaskThumbnailUiState(
+                TaskUiStateMapper.toTaskHeaderState(
                     taskData = taskData,
-                    isLiveTile = true,
                     hasHeader = true,
                     clickCloseListener = {},
                 )
-            assertThat(result).isEqualTo(LiveTile.WithoutHeader)
+            assertThat(result).isEqualTo(TaskHeaderUiState.HideHeader)
+        }
+    }
+
+    /** TaskThumbnailUiState */
+    @Test
+    fun taskData_isNull_returns_Uninitialized() {
+        val result = TaskUiStateMapper.toTaskThumbnailUiState(taskData = null, isLiveTile = false)
+        assertThat(result).isEqualTo(TaskThumbnailUiState.Uninitialized)
+    }
+
+    @Test
+    fun taskData_isLiveTile_returns_LiveTile() {
+        val inputs =
+            listOf(TASK_DATA, TASK_DATA.copy(thumbnailData = null), TASK_DATA.copy(isLocked = true))
+        inputs.forEach { input ->
+            val result =
+                TaskUiStateMapper.toTaskThumbnailUiState(taskData = input, isLiveTile = true)
+            assertThat(result).isEqualTo(LiveTile)
         }
     }
 
     @Test
     fun taskData_isStaticTile_returns_SnapshotSplash() {
         val result =
-            TaskUiStateMapper.toTaskThumbnailUiState(
-                taskData = TASK_DATA,
-                isLiveTile = false,
-                hasHeader = false,
-                clickCloseListener = null,
-            )
+            TaskUiStateMapper.toTaskThumbnailUiState(taskData = TASK_DATA, isLiveTile = false)
 
         val expected =
             TaskThumbnailUiState.SnapshotSplash(
                 snapshot =
-                    Snapshot.WithoutHeader(
+                    Snapshot(
                         backgroundColor = TASK_BACKGROUND_COLOR,
                         bitmap = TASK_THUMBNAIL,
                         thumbnailRotation = Surface.ROTATION_0,
@@ -138,72 +138,12 @@
         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)
@@ -216,8 +156,6 @@
             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/src/com/android/quickstep/DesktopSystemShortcutTest.kt b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
index a523e02..7b73be7 100644
--- a/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/DesktopSystemShortcutTest.kt
@@ -36,6 +36,7 @@
 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
@@ -248,6 +249,7 @@
         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 2db94f6..5aaed7d 100644
--- a/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/ExternalDisplaySystemShortcutTest.kt
@@ -37,6 +37,7 @@
 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
@@ -246,6 +247,7 @@
         TaskContainer(
             taskView,
             task,
+            mock<TaskContentView>(),
             if (enableRefactorTaskThumbnail()) mock<TaskThumbnailView>()
             else mock<TaskThumbnailViewDeprecated>(),
             mock<TaskViewIcon>(),
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 1af48a9..b207d4a 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -100,11 +100,9 @@
 
     @After
     public void tearDown() {
-        runOnRecentsView(recentsView -> {
-            if (recentsView != null) {
-                recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false);
-            }
-        });
+        runOnRecentsView(recentsView ->
+                recentsView.getPagedViewOrientedState().forceAllowRotationForTesting(false),
+                /* forTearDown= */ true);
     }
 
     public static void startTestApps() throws Exception {
@@ -661,18 +659,31 @@
     }
 
     private <T> T getFromRecentsView(Function<RecentsView, T> f) {
+        return getFromRecentsView(f, false);
+    }
+
+    private <T> T getFromRecentsView(Function<RecentsView, T> f, boolean forTearDown) {
         if (enableLauncherOverviewInWindow()) {
-            return getFromRecentsWindow(
-                    recentsWindowManager -> f.apply(recentsWindowManager.getOverviewPanel()));
+            return getFromRecentsWindow(recentsWindowManager ->
+                    (forTearDown && recentsWindowManager == null)
+                            ? null :  f.apply(recentsWindowManager.getOverviewPanel()));
         } else {
-            return getFromLauncher(launcher -> f.apply(launcher.getOverviewPanel()));
+            return getFromLauncher(launcher -> (forTearDown && launcher == null)
+                    ? null : f.apply(launcher.getOverviewPanel()));
         }
     }
 
     private void runOnRecentsView(Consumer<RecentsView> f) {
+        runOnRecentsView(f, false);
+    }
+
+    private void runOnRecentsView(Consumer<RecentsView> f, boolean forTearDown) {
         getFromRecentsView(recentsView -> {
+            if (forTearDown && recentsView == null) {
+                return null;
+            }
             f.accept(recentsView);
             return null;
-        });
+        }, forTearDown);
     }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt b/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt
index b4d9f5b..47108e0 100644
--- a/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt
+++ b/quickstep/tests/src/com/android/quickstep/desktop/DesktopAppLaunchAnimatorHelperTest.kt
@@ -93,6 +93,21 @@
     }
 
     @Test
+    fun noLaunchTransition_returnsEmptyAnimatorsList() {
+        val pipChange =
+            TransitionInfo.Change(mock(), mock()).apply {
+                mode = WindowManager.TRANSIT_PIP
+                taskInfo = TASK_INFO_FREEFORM
+            }
+        val transitionInfo = TransitionInfo(WindowManager.TRANSIT_NONE, 0)
+        transitionInfo.addChange(pipChange)
+
+        val actual = helper.createAnimators(transitionInfo, finishCallback = {})
+
+        assertThat(actual).hasSize(0)
+    }
+
+    @Test
     fun minimizeTransition_returnsLaunchAndMinimizeAnimator() {
         val openChange =
             TransitionInfo.Change(mock(), mock()).apply {
diff --git a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index ad6afcf..a30261e 100644
--- a/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/multivalentTests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -39,6 +39,7 @@
 import com.android.launcher3.util.SandboxContext
 import com.android.launcher3.util.WindowBounds
 import com.android.launcher3.util.rule.TestStabilityRule
+import com.android.launcher3.util.rule.ZipFilesRule
 import com.android.launcher3.util.rule.setFlags
 import com.android.launcher3.util.window.CachedDisplayInfo
 import com.android.launcher3.util.window.WindowManagerProxy
@@ -51,6 +52,7 @@
 import java.io.StringWriter
 import kotlin.math.max
 import kotlin.math.min
+import org.junit.ClassRule
 import org.junit.Rule
 import org.mockito.kotlin.any
 import org.mockito.kotlin.doReturn
@@ -80,6 +82,13 @@
 
     @Rule @JvmField val limitDevicesRule = LimitDevicesRule()
 
+    companion object {
+        @ClassRule
+        @JvmField
+        val resultZipRule =
+            ZipFilesRule(InstrumentationRegistry.getInstrumentation().targetContext, "DumpTest")
+    }
+
     class DeviceSpec(
         val naturalSize: Pair<Int, Int>,
         var densityDpi: Int,
@@ -364,13 +373,9 @@
         context.assets.open("dumpTests/$fileName").bufferedReader().use(BufferedReader::readText)
 
     private fun writeToDevice(context: Context, fileName: String, content: String) {
-        val dir =
-            File(context.filesDir, "dumpTests").also {
-                if (!it.exists()) {
-                    it.mkdirs()
-                }
-            }
-        File(dir, fileName).writeText(content)
+        val file = File(context.getDir("dumpTests", Context.MODE_PRIVATE), fileName)
+        file.writeText(content)
+        resultZipRule.write(file)
     }
 
     protected fun Float.dpToPx(): Float {
diff --git a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
index 1e54603..38fad6b 100644
--- a/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
+++ b/tests/src/com/android/launcher3/backuprestore/BackupAndRestoreDBSelectionTest.kt
@@ -32,7 +32,6 @@
 import com.android.launcher3.util.rule.BackAndRestoreRule
 import com.android.launcher3.util.rule.setFlags
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -70,7 +69,6 @@
     }
 
     @Test
-    @Ignore("b/385147987")
     fun oldDatabasesNotPresentAfterRestore() {
         val dbController = ModelDbController(getInstrumentation().targetContext)
         if (Flags.gridMigrationRefactor()) {
diff --git a/tests/src/com/android/launcher3/util/rule/ZipFilesRule.kt b/tests/src/com/android/launcher3/util/rule/ZipFilesRule.kt
new file mode 100644
index 0000000..d12de76
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/rule/ZipFilesRule.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.util.rule
+
+import android.content.Context
+import android.os.FileUtils
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import java.util.zip.ZipEntry
+import java.util.zip.ZipOutputStream
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+class ZipFilesRule(val context: Context, val name: String) : TestRule {
+
+    var resultsZip: ZipOutputStream? = null
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return object : Statement() {
+            @Throws(Throwable::class)
+            override fun evaluate() {
+                resultsZip =
+                    ZipOutputStream(
+                        FileOutputStream(
+                            File(context.filesDir, "${description.testClass.simpleName}-$name.zip")
+                        )
+                    )
+                try {
+                    base.evaluate() // This will run the test.
+                } finally {
+                    resultsZip?.close()
+                }
+            }
+        }
+    }
+
+    fun write(file: File) {
+        if (resultsZip !is ZipOutputStream) {
+            throw RuntimeException(
+                "Cannot save files before the test rule starts! We need the rule to start to get the name of the test"
+            )
+        }
+        resultsZip!!.let {
+            it.putNextEntry(ZipEntry(file.name))
+            FileUtils.copy(FileInputStream(file), it)
+            it.closeEntry()
+        }
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
index 8fbb5e3..02c6630 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTask.java
@@ -73,16 +73,18 @@
             return getCombinedSplitTaskHeight();
         }
 
-        UiObject2 taskSnapshot1 = findObjectInTask((isDesktop() ? DESKTOP : DEFAULT).snapshotRes);
-        return taskSnapshot1.getVisibleBounds().height();
+        if (isDesktop()) {
+            return getTaskSnapshot(DESKTOP).getVisibleBounds().height();
+        }
+        return getTaskSnapshot(DEFAULT).getVisibleBounds().height();
     }
 
     /**
      * Calculates the visible height for split tasks, containing 2 snapshot tiles and a divider.
      */
     private int getCombinedSplitTaskHeight() {
-        UiObject2 taskSnapshot1 = findObjectInTask(SPLIT_TOP_OR_LEFT.snapshotRes);
-        UiObject2 taskSnapshot2 = findObjectInTask(SPLIT_BOTTOM_OR_RIGHT.snapshotRes);
+        UiObject2 taskSnapshot1 = getTaskSnapshot(SPLIT_TOP_OR_LEFT);
+        UiObject2 taskSnapshot2 = getTaskSnapshot(SPLIT_BOTTOM_OR_RIGHT);
 
         // If the split task is partly off screen, taskSnapshot1 can be invisible.
         if (taskSnapshot1 == null) {
@@ -97,34 +99,6 @@
         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;
     }
@@ -142,6 +116,22 @@
     }
 
     /**
+     * Returns the task snapshot (thumbnail) for the given `OverviewTaskContainer`.
+     *
+     * For some reason `BySelector` does not work with `hasChild` or `hasParent` so instead we
+     * grab all the views matching the id: "snapshot" and filter for the correct parent.
+     */
+    private UiObject2 getTaskSnapshot(OverviewTaskContainer overviewTaskContainer) {
+        BySelector snapshotSelector = mLauncher.getOverviewObjectSelector("snapshot");
+        List<UiObject2> snapshots = mTask.findObjects(snapshotSelector);
+        return snapshots.stream()
+                .filter(snapshot -> snapshot.getParent().getResourceName()
+                        .contains(overviewTaskContainer.taskContentViewRes))
+                .findFirst()
+                .orElse(snapshots.getFirst());
+    }
+
+    /**
      * Dismisses the task by swiping up.
      */
     public void dismiss() {
@@ -304,17 +294,13 @@
         }
     }
 
-    private UiObject2 findObjectInTask(String resName) {
-        return mTask.findObject(mLauncher.getOverviewObjectSelector(resName));
-    }
-
     /**
      * Returns whether the given String is contained in this Task's contentDescription. Also returns
      * true if both Strings are null.
      */
     public boolean containsContentDescription(String expected,
             OverviewTaskContainer overviewTaskContainer) {
-        String actual = findObjectInTask(overviewTaskContainer.snapshotRes).getContentDescription();
+        String actual = getTaskSnapshot(overviewTaskContainer).getContentDescription();
         if (actual == null && expected == null) {
             return true;
         }
@@ -360,19 +346,19 @@
      */
     public enum OverviewTaskContainer {
         // The main task when the task is not split.
-        DEFAULT("snapshot", "icon"),
+        DEFAULT("task_content_view", "icon"),
         // The first task in split task.
-        SPLIT_TOP_OR_LEFT("snapshot", "icon"),
+        SPLIT_TOP_OR_LEFT("task_content_view", "icon"),
         // The second task in split task.
-        SPLIT_BOTTOM_OR_RIGHT("bottomright_snapshot", "bottomRight_icon"),
+        SPLIT_BOTTOM_OR_RIGHT("bottomright_task_content_view", "bottomRight_icon"),
         // The desktop task.
         DESKTOP("background", "icon");
 
-        public final String snapshotRes;
+        public final String taskContentViewRes;
         public final String iconAppRes;
 
-        OverviewTaskContainer(String snapshotRes, String iconAppRes) {
-            this.snapshotRes = snapshotRes;
+        OverviewTaskContainer(String taskContentViewRes, String iconAppRes) {
+            this.taskContentViewRes = taskContentViewRes;
             this.iconAppRes = iconAppRes;
         }
     }